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 { 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 |
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 { 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' |
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( |
export default connect( |
||||
mapStateToProps, |
|
||||
|
null, |
||||
mapDispatchToProps |
mapDispatchToProps |
||||
)(CreateGroupForm) |
)(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 { 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' |
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( |
export default connect( |
||||
mapStateToProps, |
|
||||
|
null, |
||||
mapDispatchToProps |
mapDispatchToProps |
||||
)(CreateUserForm) |
)(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 { connect } from 'react-redux' |
||||
|
|
||||
|
import { setFieldValue } from 'src/actions/forms' |
||||
import { getFieldValue, getFieldNotification } from 'src/selectors/forms' |
import { getFieldValue, getFieldNotification } from 'src/selectors/forms' |
||||
import { AppState } from 'src/types' |
import { AppState } from 'src/types' |
||||
|
|
||||
import TextField, { Props } from './text-field' |
import TextField, { Props } from './text-field' |
||||
|
|
||||
const mapStateToProps = (state: AppState, ownProps: Props) => ({ |
const mapStateToProps = (state: AppState, ownProps: Props) => ({ |
||||
value: getFieldValue(state, ownProps.name), |
|
||||
|
value: getFieldValue<string>(state, ownProps.name, ''), |
||||
notification: getFieldNotification(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( |
export default connect( |
||||
mapStateToProps |
|
||||
|
mapStateToProps, |
||||
|
mapDispatchToProps |
||||
)(TextField) |
)(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' |
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