Dwayne Harris
5 years ago
37 changed files with 758 additions and 169 deletions
-
28package-lock.json
-
10package.json
-
61src/actions/registration.ts
-
26src/api/registration.ts
-
6src/components/app/app.scss
-
26src/components/app/app.tsx
-
55src/components/create-group-form/create-group-form.tsx
-
25src/components/create-group-form/index.ts
-
30src/components/create-group-step/create-group-step.tsx
-
37src/components/create-group-step/index.ts
-
31src/components/create-user-form/create-user-form.tsx
-
27src/components/create-user-form/index.ts
-
29src/components/create-user-step/create-user-step.tsx
-
20src/components/create-user-step/index.ts
-
20src/components/footer/index.tsx
-
24src/components/forms/checkbox-field/checkbox-field.tsx
-
23src/components/forms/checkbox-field/index.ts
-
24src/components/forms/password-field/index.ts
-
89src/components/forms/password-field/password-field.tsx
-
24src/components/forms/select-field/index.ts
-
39src/components/forms/select-field/select-field.tsx
-
13src/components/forms/text-field/index.ts
-
31src/components/forms/text-field/text-field.tsx
-
26src/components/navigation-menu/index.tsx
-
24src/components/pages/login/index.ts
-
16src/components/pages/login/index.tsx
-
41src/components/pages/login/login.tsx
-
18src/components/pages/register/index.ts
-
61src/components/pages/register/register.tsx
-
4src/components/pages/test/index.ts
-
5src/reducers/forms.ts
-
6src/reducers/registration.ts
-
11src/selectors/forms.ts
-
2src/selectors/registration.ts
-
5src/types/index.ts
-
5src/types/store.ts
-
5webpack.config.ts
@ -1,13 +1,62 @@ |
|||
import { Action } from 'redux' |
|||
import { setFieldNotification } from 'src/actions/forms' |
|||
import { startRequest, finishRequest } from 'src/actions/requests' |
|||
import { fetchGroupAvailability } from 'src/api/registration' |
|||
import { AppThunkAction } from 'src/types' |
|||
|
|||
export interface SetPageAction extends Action { |
|||
type: 'REGISTRATION_SET_PAGE' |
|||
const FETCH_GROUP_AVAILABILITY_ID = 'FETCH_GROUP_AVAILABILITY' |
|||
const FETCH_USER_AVAILABILITY_ID = 'FETCH_USER_AVAILABILITY' |
|||
|
|||
export interface SetStepAction extends Action { |
|||
type: 'REGISTRATION_SET_STEP' |
|||
payload: number |
|||
} |
|||
|
|||
export type RegistrationActions = SetPageAction |
|||
export type RegistrationActions = SetStepAction |
|||
|
|||
export const setPage = (page: number): SetPageAction => ({ |
|||
type: 'REGISTRATION_SET_PAGE', |
|||
payload: page, |
|||
export const setStep = (step: number): SetStepAction => ({ |
|||
type: 'REGISTRATION_SET_STEP', |
|||
payload: step, |
|||
}) |
|||
|
|||
export const checkGroupAvailability = (name: string): AppThunkAction => { |
|||
return async dispatch => { |
|||
dispatch(startRequest(FETCH_GROUP_AVAILABILITY_ID)) |
|||
|
|||
try { |
|||
const response = await fetchGroupAvailability(name) |
|||
|
|||
if (response.available) { |
|||
dispatch(setFieldNotification('group-name', 'success', `${response.id} is available`)) |
|||
} else { |
|||
dispatch(setFieldNotification('group-name', 'error', `${response.id} isn't available`)) |
|||
} |
|||
|
|||
dispatch(finishRequest(FETCH_GROUP_AVAILABILITY_ID, true)) |
|||
} catch (err) { |
|||
dispatch(finishRequest(FETCH_GROUP_AVAILABILITY_ID, false)) |
|||
throw err |
|||
} |
|||
} |
|||
} |
|||
|
|||
export const checkUserAvailability = (name: string): AppThunkAction => { |
|||
return async dispatch => { |
|||
dispatch(startRequest(FETCH_USER_AVAILABILITY_ID)) |
|||
|
|||
try { |
|||
const response = await fetchGroupAvailability(name) |
|||
|
|||
if (response.available) { |
|||
dispatch(setFieldNotification('user-id', 'success', `${response.id} is available`)) |
|||
} else { |
|||
dispatch(setFieldNotification('user-id', 'error', `${response.id} isn't available`)) |
|||
} |
|||
|
|||
dispatch(finishRequest(FETCH_USER_AVAILABILITY_ID, true)) |
|||
} catch (err) { |
|||
dispatch(finishRequest(FETCH_USER_AVAILABILITY_ID, false)) |
|||
throw err |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,26 @@ |
|||
import { fetch } from './fetch' |
|||
|
|||
interface AvailabilityResponse { |
|||
id: string |
|||
available: boolean |
|||
} |
|||
|
|||
export async function fetchGroupAvailability(name: string) { |
|||
return await fetch<AvailabilityResponse>({ |
|||
path: '/api/group/available', |
|||
method: 'post', |
|||
body: { |
|||
name, |
|||
}, |
|||
}) |
|||
} |
|||
|
|||
export async function fetchUserAvailability(name: string) { |
|||
return await fetch<AvailabilityResponse>({ |
|||
path: '/api/user/available', |
|||
method: 'post', |
|||
body: { |
|||
name, |
|||
}, |
|||
}) |
|||
} |
@ -1,21 +1,26 @@ |
|||
import { Dispatch } from 'redux' |
|||
import { FocusEventHandler } from 'react' |
|||
import { connect } from 'react-redux' |
|||
|
|||
import { initField } from 'src/actions/forms' |
|||
import { AppState } from 'src/types' |
|||
import { checkGroupAvailability } from 'src/actions/registration' |
|||
import { AppState, AppThunkDispatch } from 'src/types' |
|||
|
|||
import CreateGroupForm from './create-group-form' |
|||
|
|||
const mapStateToProps = (state: AppState) => ({ |
|||
}) |
|||
const mapDispatchToProps = (dispatch: AppThunkDispatch) => { |
|||
const checkAvailability: FocusEventHandler<HTMLInputElement> = event => { |
|||
const value = event.target.value |
|||
|
|||
const mapDispatchToProps = (dispatch: Dispatch) => ({ |
|||
initForm: () => { |
|||
dispatch(initField('name')) |
|||
if (value.length > 3) { |
|||
dispatch(checkGroupAvailability(event.target.value)) |
|||
} |
|||
} |
|||
}) |
|||
|
|||
return { |
|||
checkAvailability, |
|||
} |
|||
} |
|||
|
|||
export default connect( |
|||
mapStateToProps, |
|||
null, |
|||
mapDispatchToProps |
|||
)(CreateGroupForm) |
@ -0,0 +1,30 @@ |
|||
import React, { FC } from 'react' |
|||
import noop from 'lodash/noop' |
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' |
|||
import { faArrowRight } from '@fortawesome/free-solid-svg-icons' |
|||
|
|||
import CreateGroupForm from '../create-group-form' |
|||
|
|||
export interface Props { |
|||
name: string |
|||
registration: string |
|||
next?: (name: string, registration: string) => void |
|||
} |
|||
|
|||
const CreateGroupStep: FC<Props> = ({ name, registration, next = noop }) => ( |
|||
<div className="columns"> |
|||
<div className="column is-8 is-offset-2"> |
|||
<CreateGroupForm /> |
|||
<br /><hr /> |
|||
|
|||
<button className="button is-pulled-right is-success" onClick={() => next(name, registration)}> |
|||
<span>Your Account</span> |
|||
<span className="icon is-small"> |
|||
<FontAwesomeIcon icon={faArrowRight} /> |
|||
</span> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
) |
|||
|
|||
export default CreateGroupStep |
@ -0,0 +1,37 @@ |
|||
import { Dispatch } from 'redux' |
|||
import { connect } from 'react-redux' |
|||
|
|||
import { setFieldNotification } from 'src/actions/forms' |
|||
import { setStep } from 'src/actions/registration' |
|||
import { getFieldValue } from 'src/selectors/forms' |
|||
import { AppState } from 'src/types' |
|||
|
|||
import CreateGroupStep from './create-group-step' |
|||
|
|||
const MAX_ID_LENGTH = 40 |
|||
|
|||
const mapStateToProps = (state: AppState) => ({ |
|||
name: getFieldValue<string>(state, 'group-name', ''), |
|||
registration: getFieldValue<string>(state, 'group-registration', ''), |
|||
}) |
|||
|
|||
const mapDispatchToProps = (dispatch: Dispatch) => ({ |
|||
next: (name: string) => { |
|||
if (!name) { |
|||
dispatch(setFieldNotification('group-name', 'error', 'This is required')) |
|||
return |
|||
} |
|||
|
|||
if (name.length > MAX_ID_LENGTH) { |
|||
dispatch(setFieldNotification('group-name', 'error', `This must be less than ${MAX_ID_LENGTH} characters`)) |
|||
return |
|||
} |
|||
|
|||
dispatch(setStep(1)) |
|||
}, |
|||
}) |
|||
|
|||
export default connect( |
|||
mapStateToProps, |
|||
mapDispatchToProps |
|||
)(CreateGroupStep) |
@ -1,23 +1,26 @@ |
|||
import { Dispatch } from 'redux' |
|||
import { FocusEventHandler } from 'react' |
|||
import { connect } from 'react-redux' |
|||
|
|||
import { initField } from 'src/actions/forms' |
|||
import { AppState } from 'src/types' |
|||
import { checkUserAvailability } from 'src/actions/registration' |
|||
import { AppThunkDispatch } from 'src/types' |
|||
|
|||
import CreateUserForm from './create-user-form' |
|||
|
|||
const mapStateToProps = (state: AppState) => ({ |
|||
}) |
|||
const mapDispatchToProps = (dispatch: AppThunkDispatch) => { |
|||
const checkAvailability: FocusEventHandler<HTMLInputElement> = event => { |
|||
const value = event.target.value |
|||
|
|||
const mapDispatchToProps = (dispatch: Dispatch) => ({ |
|||
initForm: () => { |
|||
dispatch(initField('username')) |
|||
dispatch(initField('name')) |
|||
dispatch(initField('email')) |
|||
if (value.length > 3) { |
|||
dispatch(checkUserAvailability(event.target.value)) |
|||
} |
|||
} |
|||
}) |
|||
|
|||
return { |
|||
checkAvailability, |
|||
} |
|||
} |
|||
|
|||
export default connect( |
|||
mapStateToProps, |
|||
null, |
|||
mapDispatchToProps |
|||
)(CreateUserForm) |
@ -0,0 +1,29 @@ |
|||
import React, { FC } from 'react' |
|||
import noop from 'lodash/noop' |
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' |
|||
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons' |
|||
|
|||
import CreateUserForm from '../create-user-form' |
|||
|
|||
export interface Props { |
|||
previous?: () => void |
|||
next?: () => void |
|||
} |
|||
|
|||
const CreateUserStep: FC<Props> = ({ previous = noop, next = noop }) => ( |
|||
<div className="columns"> |
|||
<div className="column is-8 is-offset-2"> |
|||
<CreateUserForm /> |
|||
<br /><hr /> |
|||
<button className="button is-pulled-left is-outlined" onClick={() => previous()}> |
|||
<span className="icon is-small"> |
|||
<FontAwesomeIcon icon={faArrowLeft} /> |
|||
</span> |
|||
<span>Community</span> |
|||
</button> |
|||
<button className="button is-pulled-right is-success" onClick={() => next()}>Finish</button> |
|||
</div> |
|||
</div> |
|||
) |
|||
|
|||
export default CreateUserStep |
@ -0,0 +1,20 @@ |
|||
import { Dispatch } from 'redux' |
|||
import { connect } from 'react-redux' |
|||
|
|||
import { setStep } from 'src/actions/registration' |
|||
|
|||
import CreateUserStep from './create-user-step' |
|||
|
|||
const mapDispatchToProps = (dispatch: Dispatch) => ({ |
|||
previous: () => { |
|||
dispatch(setStep(0)) |
|||
}, |
|||
next: () => { |
|||
|
|||
}, |
|||
}) |
|||
|
|||
export default connect( |
|||
null, |
|||
mapDispatchToProps |
|||
)(CreateUserStep) |
@ -0,0 +1,20 @@ |
|||
import React, { FC } from 'react' |
|||
import { Link } from 'react-router-dom' |
|||
|
|||
const Divider: FC = () => <> ⚬ </> |
|||
|
|||
const Footer: FC = () => ( |
|||
<footer> |
|||
<div className="content has-text-centered has-text-white is-size-7"> |
|||
<Link className="has-text-white is-inline-block" to="/">Home</Link> |
|||
<Divider /> |
|||
<Link className="has-text-white is-inline-block" to="/developers">Developers</Link> |
|||
<Divider /> |
|||
<Link className="has-text-white is-inline-block" to="/about">About</Link> |
|||
|
|||
<p>© 2019 Flexor.cc</p> |
|||
</div> |
|||
</footer> |
|||
) |
|||
|
|||
export default Footer |
@ -0,0 +1,24 @@ |
|||
import React, { FC } from 'react' |
|||
import noop from 'lodash/noop' |
|||
|
|||
export interface Props { |
|||
name: string |
|||
value?: boolean |
|||
setValue?: (value: boolean) => void |
|||
} |
|||
|
|||
const PasswordField: FC<Props> = ({ |
|||
value = false, |
|||
setValue = noop, |
|||
children, |
|||
}) => { |
|||
return ( |
|||
<label className="checkbox"> |
|||
<input type="checkbox" checked={value} onChange={(e) => setValue(e.target.checked)} /> |
|||
|
|||
{children} |
|||
</label> |
|||
) |
|||
} |
|||
|
|||
export default PasswordField |
@ -0,0 +1,23 @@ |
|||
import { Dispatch } from 'redux' |
|||
import { connect } from 'react-redux' |
|||
|
|||
import { setFieldValue } from 'src/actions/forms' |
|||
import { getFieldValue } from 'src/selectors/forms' |
|||
import { AppState } from 'src/types' |
|||
|
|||
import CheckboxField, { Props } from './checkbox-field' |
|||
|
|||
const mapStateToProps = (state: AppState, ownProps: Props) => ({ |
|||
value: getFieldValue<boolean>(state, ownProps.name, false), |
|||
}) |
|||
|
|||
const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => ({ |
|||
setValue: (value: boolean) => { |
|||
dispatch(setFieldValue(ownProps.name, value)) |
|||
} |
|||
}) |
|||
|
|||
export default connect( |
|||
mapStateToProps, |
|||
mapDispatchToProps |
|||
)(CheckboxField) |
@ -0,0 +1,24 @@ |
|||
import { Dispatch } from 'redux' |
|||
import { connect } from 'react-redux' |
|||
|
|||
import { setFieldValue } from 'src/actions/forms' |
|||
import { getFieldValue, getFieldNotification } from 'src/selectors/forms' |
|||
import { AppState } from 'src/types' |
|||
|
|||
import PasswordField from './password-field' |
|||
|
|||
const mapStateToProps = (state: AppState) => ({ |
|||
value: getFieldValue<string>(state, 'password', ''), |
|||
notification: getFieldNotification(state, 'password'), |
|||
}) |
|||
|
|||
const mapDispatchToProps = (dispatch: Dispatch) => ({ |
|||
setValue: (value: string) => { |
|||
dispatch(setFieldValue('password', value)) |
|||
} |
|||
}) |
|||
|
|||
export default connect( |
|||
mapStateToProps, |
|||
mapDispatchToProps |
|||
)(PasswordField) |
@ -0,0 +1,89 @@ |
|||
import React, { FC, ReactNode } from 'react' |
|||
import classNames from 'classnames' |
|||
import noop from 'lodash/noop' |
|||
import zxcvbn from 'zxcvbn' |
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' |
|||
import { IconDefinition } from '@fortawesome/fontawesome-common-types' |
|||
import { faKey, faExclamationTriangle, faCheckCircle } from '@fortawesome/free-solid-svg-icons' |
|||
|
|||
import { FormNotification, ClassDictionary } from 'src/types' |
|||
|
|||
export interface Props { |
|||
placeholder?: string |
|||
userInputs?: string[] |
|||
value?: string |
|||
notification?: FormNotification |
|||
setValue?: (value: string) => void |
|||
} |
|||
|
|||
const PasswordField: FC<Props> = ({ |
|||
placeholder, |
|||
userInputs = [], |
|||
value = '', |
|||
notification, |
|||
setValue = noop, |
|||
}) => { |
|||
const inputClassDictionary: ClassDictionary = { input: true } |
|||
const controlClassDictionary: ClassDictionary = { control: true, 'has-icons-left': true } |
|||
let icon: IconDefinition | undefined |
|||
let passwordMessage: ReactNode | undefined |
|||
|
|||
if (value) { |
|||
const { score } = zxcvbn(value, userInputs) |
|||
|
|||
switch (score) { |
|||
case 0: |
|||
inputClassDictionary['is-danger'] = true |
|||
controlClassDictionary['has-icons-right'] = true |
|||
icon = faExclamationTriangle |
|||
passwordMessage = <span>Strength: <span className="has-text-danger">Unusable</span></span> |
|||
break |
|||
case 1: |
|||
inputClassDictionary['is-danger'] = true |
|||
passwordMessage = <span>Strength: <span className="has-text-danger">Not good</span></span> |
|||
break |
|||
case 2: |
|||
inputClassDictionary['is-warning'] = true |
|||
passwordMessage = <span>Strength: <span className="has-text-warning">OK</span></span> |
|||
break |
|||
case 3: |
|||
inputClassDictionary['is-success'] = true |
|||
passwordMessage = <span>Strength: <span className="has-text-success">Good!</span></span> |
|||
break |
|||
case 4: |
|||
inputClassDictionary['is-success'] = true |
|||
controlClassDictionary['has-icons-right'] = true |
|||
icon = faCheckCircle |
|||
passwordMessage = <span>Strength: <span className="has-text-success">🔥</span></span> |
|||
break |
|||
} |
|||
} |
|||
|
|||
const helpText = () => { |
|||
if (notification) return <p className="help">{notification.message}</p> |
|||
if (passwordMessage) return <p className="help">{passwordMessage}</p> |
|||
} |
|||
|
|||
return ( |
|||
<div className="field"> |
|||
<label className="label">Password</label> |
|||
<div className={classNames(controlClassDictionary)}> |
|||
<input className={classNames(inputClassDictionary)} type="password" placeholder={placeholder} value={value} onChange={(e) => setValue(e.target.value)} /> |
|||
<span className="icon is-small is-left"> |
|||
<FontAwesomeIcon icon={faKey} /> |
|||
</span> |
|||
{icon && |
|||
<span className="icon is-small is-right"> |
|||
<FontAwesomeIcon icon={icon} /> |
|||
</span> |
|||
} |
|||
</div> |
|||
{notification && |
|||
<p className="help">{notification.message}</p> |
|||
} |
|||
{helpText()} |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
export default PasswordField |
@ -0,0 +1,24 @@ |
|||
import { Dispatch } from 'redux' |
|||
import { connect } from 'react-redux' |
|||
|
|||
import { setFieldValue } from 'src/actions/forms' |
|||
import { getFieldValue, getFieldNotification } from 'src/selectors/forms' |
|||
import { AppState } from 'src/types' |
|||
|
|||
import SelectField, { Props } from './select-field' |
|||
|
|||
const mapStateToProps = (state: AppState, ownProps: Props) => ({ |
|||
value: getFieldValue<string>(state, ownProps.name, ''), |
|||
notification: getFieldNotification(state, ownProps.name), |
|||
}) |
|||
|
|||
const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => ({ |
|||
setValue: (value: string) => { |
|||
dispatch(setFieldValue(ownProps.name, value)) |
|||
} |
|||
}) |
|||
|
|||
export default connect( |
|||
mapStateToProps, |
|||
mapDispatchToProps |
|||
)(SelectField) |
@ -0,0 +1,39 @@ |
|||
import React, { FC } from 'react' |
|||
import noop from 'lodash/noop' |
|||
|
|||
import { FormNotification } from 'src/types' |
|||
|
|||
interface SelectOptions { |
|||
[value: string]: string |
|||
} |
|||
|
|||
export interface Props { |
|||
name: string |
|||
label: string |
|||
options: SelectOptions |
|||
value?: string |
|||
notification?: FormNotification |
|||
setValue?: (value: string) => void |
|||
} |
|||
|
|||
const SelectField: FC<Props> = ({ |
|||
label, |
|||
options = {}, |
|||
value = '', |
|||
setValue = noop, |
|||
}) => { |
|||
const opts = Object.entries(options) |
|||
|
|||
return ( |
|||
<div className="field"> |
|||
<label className="label">{label}</label> |
|||
<div className="select is-small"> |
|||
<select value={value} onChange={(e) => setValue(e.target.value)}> |
|||
{opts.map(([key, value]) => <option key={key} value={key}>{value}</option>)} |
|||
</select> |
|||
</div> |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
export default SelectField |
@ -1,15 +1,24 @@ |
|||
import { Dispatch } from 'redux' |
|||
import { connect } from 'react-redux' |
|||
|
|||
import { setFieldValue } from 'src/actions/forms' |
|||
import { getFieldValue, getFieldNotification } from 'src/selectors/forms' |
|||
import { AppState } from 'src/types' |
|||
|
|||
import TextField, { Props } from './text-field' |
|||
|
|||
const mapStateToProps = (state: AppState, ownProps: Props) => ({ |
|||
value: getFieldValue(state, ownProps.name), |
|||
value: getFieldValue<string>(state, ownProps.name, ''), |
|||
notification: getFieldNotification(state, ownProps.name), |
|||
}) |
|||
|
|||
const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => ({ |
|||
setValue: (value: string) => { |
|||
dispatch(setFieldValue(ownProps.name, value)) |
|||
} |
|||
}) |
|||
|
|||
export default connect( |
|||
mapStateToProps |
|||
mapStateToProps, |
|||
mapDispatchToProps |
|||
)(TextField) |
@ -0,0 +1,26 @@ |
|||
import React, { FC } from 'react' |
|||
import { Link } from 'react-router-dom' |
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' |
|||
import { faStream, faPaperPlane } from '@fortawesome/free-solid-svg-icons' |
|||
|
|||
const NavigationMenu: FC = () => ( |
|||
<div id="navigation"> |
|||
<div> |
|||
<span className="icon has-text-white"> |
|||
<FontAwesomeIcon icon={faStream} /> |
|||
</span> |
|||
|
|||
<Link className="has-text-white" to="/">Timeline</Link> |
|||
</div> |
|||
|
|||
<div> |
|||
<span className="icon has-text-white"> |
|||
<FontAwesomeIcon icon={faPaperPlane} /> |
|||
</span> |
|||
|
|||
<Link className="has-text-white" to="/">Apps</Link> |
|||
</div> |
|||
</div> |
|||
) |
|||
|
|||
export default NavigationMenu |
@ -0,0 +1,24 @@ |
|||
|
|||
import { connect } from 'react-redux' |
|||
import { getStep } from 'src/selectors/registration' |
|||
import { initForm, initField } from 'src/actions/forms' |
|||
import { AppState, AppThunkDispatch } from 'src/types' |
|||
|
|||
import Login from './login' |
|||
|
|||
const mapStateToProps = (state: AppState) => ({ |
|||
step: getStep(state), |
|||
}) |
|||
|
|||
const mapDispatchToProps = (dispatch: AppThunkDispatch) => ({ |
|||
initForm: () => { |
|||
dispatch(initForm()) |
|||
dispatch(initField('name')) |
|||
dispatch(initField('password')) |
|||
}, |
|||
}) |
|||
|
|||
export default connect( |
|||
mapStateToProps, |
|||
mapDispatchToProps |
|||
)(Login) |
@ -1,16 +0,0 @@ |
|||
import React, { FC } from 'react' |
|||
import TextField from 'src/components/text-field' |
|||
|
|||
const Login: FC = () => { |
|||
return ( |
|||
<div className="main-content"> |
|||
<h1 className="title">Login</h1> |
|||
|
|||
<div> |
|||
<TextField name="username" label="Username" placeholder="Your Username/ID" /> |
|||
</div> |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
export default Login |
@ -0,0 +1,41 @@ |
|||
import React, { FC, useEffect } from 'react' |
|||
import { faIdCard } from '@fortawesome/free-solid-svg-icons' |
|||
|
|||
import TextField from 'src/components/forms/text-field' |
|||
import PasswordField from 'src/components/forms/password-field' |
|||
|
|||
interface Props { |
|||
initForm: () => void |
|||
} |
|||
|
|||
const Login: FC<Props> = ({ initForm }) => { |
|||
useEffect(() => { |
|||
initForm() |
|||
}, []) |
|||
|
|||
return ( |
|||
<div> |
|||
<section className="hero is-success is-bold"> |
|||
<div className="hero-body"> |
|||
<div className="container"> |
|||
<h1 className="title">Login</h1> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
|
|||
<div className="main-content"> |
|||
<div className="columns"> |
|||
<div className="column is-8 is-offset-2"> |
|||
<TextField icon={faIdCard} name="name" label="Username" placeholder="Your Username/ID" /> |
|||
<br /> |
|||
<PasswordField placeholder="Your password" /> |
|||
<br /> |
|||
<button className="button is-primary">Log In</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
export default Login |
@ -1,3 +1,3 @@ |
|||
import { AppState } from 'src/types' |
|||
|
|||
export const getPage = (state: AppState) => state.registration.page |
|||
export const getStep = (state: AppState) => state.registration.step |
Write
Preview
Loading…
Cancel
Save
Reference in new issue