Dwayne Harris 5 years ago
parent
commit
a7933ce849
  1. 18
      src/actions/forms.ts
  2. 9
      src/components/app/app.scss
  3. 8
      src/components/app/app.tsx
  4. 2
      src/components/create-group-form/create-group-form.tsx
  5. 8
      src/components/create-group-step/create-group-step.tsx
  6. 8
      src/components/create-user-step/create-user-step.tsx
  7. 10
      src/components/forms/select-field/select-field.tsx
  8. 2
      src/components/notification-container/notification-container.scss
  9. 11
      src/components/pages/login/login.tsx
  10. 14
      src/components/pages/register/index.ts
  11. 10
      src/components/pages/self/index.ts
  12. 10
      src/components/pages/self/self.tsx
  13. 4
      src/components/user-info/user-info.tsx
  14. 20
      src/reducers/forms.ts
  15. 1
      src/types/store.ts
  16. 2
      src/utils/index.ts

18
src/actions/forms.ts

@ -1,5 +1,5 @@
import { Action } from 'redux'
import { NotificationType } from '../types'
import { NotificationType, FormValue } from '../types'
export interface InitFormAction extends Action {
type: 'FORMS_INIT'
@ -7,14 +7,17 @@ export interface InitFormAction extends Action {
export interface InitFieldAction extends Action {
type: 'FORMS_INIT_FIELD'
payload: string
payload: {
name: string
apiName?: string
}
}
export interface SetFieldValueAction extends Action {
type: 'FORMS_SET_FIELD_VALUE'
payload: {
name: string
value: any
value: FormValue
}
}
@ -41,12 +44,15 @@ export const initForm = (): InitFormAction => ({
type: 'FORMS_INIT',
})
export const initField = (name: string): InitFieldAction => ({
export const initField = (name: string, apiName?: string): InitFieldAction => ({
type: 'FORMS_INIT_FIELD',
payload: name,
payload: {
name,
apiName,
},
})
export const setFieldValue = (name: string, value: any): SetFieldValueAction => ({
export const setFieldValue = (name: string, value: FormValue): SetFieldValueAction => ({
type: 'FORMS_SET_FIELD_VALUE',
payload: {
name,

9
src/components/app/app.scss

@ -17,6 +17,7 @@ $family-sans-serif: "Open Sans", sans-serif;
$primary: $blue;
$title-weight: 400;
$body-background-color: $white-ter;
$body-size: 14px;
@import "../../../node_modules/bulma/sass/utilities/_all.sass";
@import "../../../node_modules/bulma/sass/base/_all.sass";
@ -33,11 +34,12 @@ $body-background-color: $white-ter;
div#main-menu {
background-color: $primary;
border-left: 2px solid $purple;
bottom: 0;
display: flex;
flex-direction: column;
left: 0;
position: absolute;
right: 0;
top: 0;
}
@ -55,6 +57,11 @@ div.centered-content {
margin: 1rem auto;
padding: 2rem;
width: 80%;
div.centered-content-icon {
margin-top: -20px;
text-align: center;
}
}
div#navigation {

8
src/components/app/app.tsx

@ -28,8 +28,8 @@ interface Props {
}
const App: FC<Props> = ({ collapsed, fetching, fetchSelf }) => {
const mainMenuWidth = 250
const mainColumnLeftMargin = collapsed ? 0 : mainMenuWidth
const mainMenuWidth = 275
const mainColumnMargin = collapsed ? 0 : mainMenuWidth
useEffect(() => {
if (localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY)) {
@ -42,7 +42,7 @@ const App: FC<Props> = ({ collapsed, fetching, fetchSelf }) => {
<div>
<div id="main-menu" style={{ width: mainMenuWidth }}>
<div id="header">
<Link className="has-text-white is-size-4" to="/">Flexor</Link>
<Link className="has-text-white is-size-3" to="/">Flexor</Link>
<hr className="has-background-grey-lighter" />
</div>
@ -53,7 +53,7 @@ const App: FC<Props> = ({ collapsed, fetching, fetchSelf }) => {
<Footer />
</div>
<div id="main-column" style={{ marginLeft: mainColumnLeftMargin }}>
<div id="main-column" style={{ marginRight: mainColumnMargin }}>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />

2
src/components/create-group-form/create-group-form.tsx

@ -28,7 +28,7 @@ const CreateGroupForm: FC<Props> = ({ checkAvailability }) => {
<div className="field">
<label className="label">Community Image</label>
<div className="file is-primary is-small">
<div className="file is-primary">
<label className="file-label">
<input className="file-input" type="file" name="image" />
<span className="file-cta">

8
src/components/create-group-step/create-group-step.tsx

@ -1,7 +1,7 @@
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 { faBuilding, faArrowRight } from '@fortawesome/free-solid-svg-icons'
import CreateGroupForm from '../create-group-form'
@ -14,6 +14,12 @@ export interface Props {
const CreateGroupStep: FC<Props> = ({ name, agree, next = noop }) => (
<div className="centered-content">
<div className="centered-content-icon">
<span className="icon is-large has-text-primary">
<FontAwesomeIcon icon={faBuilding} size="lg" />
</span>
</div>
<CreateGroupForm />
<hr />

8
src/components/create-user-step/create-user-step.tsx

@ -1,7 +1,7 @@
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 { faUser, faArrowLeft } from '@fortawesome/free-solid-svg-icons'
import CreateUserForm from '../create-user-form'
@ -26,6 +26,12 @@ const CreateUserStep: FC<Props> = ({
next = noop,
}) => (
<div className="centered-content">
<div className="centered-content-icon">
<span className="icon is-large has-text-primary">
<FontAwesomeIcon icon={faUser} size="lg" />
</span>
</div>
<CreateUserForm />
<hr />

10
src/components/forms/select-field/select-field.tsx

@ -20,17 +20,15 @@ export interface Props {
const SelectField: FC<Props> = ({
label,
options = {},
value = '',
options,
value,
notification,
setValue = noop,
}) => {
const opts = Object.entries(options)
const controlClassDictionary: ClassDictionary = { select: true, 'is-small': true }
const controlClassDictionary: ClassDictionary = { select: true }
const helpClassDictionary: ClassDictionary = { help: true }
console.log('notification', notification)
if (notification) {
const ncn = notificationTypeToClassName(notification.type)
@ -42,7 +40,7 @@ const SelectField: FC<Props> = ({
<div className="field">
<label className="label">{label}</label>
<div className={classNames(controlClassDictionary)}>
<select value={value} onChange={(e) => setValue(e.target.value)}>
<select value={value} onChange={e => setValue(e.target.value)}>
{opts.map(([key, value]) => <option key={key} value={key}>{value}</option>)}
</select>
</div>

2
src/components/notification-container/notification-container.scss

@ -1,6 +1,6 @@
div#notification-container {
bottom: 10px;
position: absolute;
right: 10px;
left: 10px;
width: 40%;
}

11
src/components/pages/login/login.tsx

@ -1,5 +1,6 @@
import React, { FC, useEffect } from 'react'
import { faIdCard } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faIdCard, faKey } from '@fortawesome/free-solid-svg-icons'
import PageHeader from 'src/components/page-header'
import TextField from 'src/components/forms/text-field'
@ -16,10 +17,16 @@ const Login: FC<Props> = ({ initForm }) => {
return (
<div>
<PageHeader title="Log In" />
<PageHeader title="Log In to Flexor" />
<div className="main-content">
<div className="centered-content">
<div className="centered-content-icon">
<span className="icon is-large has-text-primary">
<FontAwesomeIcon icon={faKey} size="lg" />
</span>
</div>
<TextField icon={faIdCard} name="name" label="Username" placeholder="Your Username/ID" />
<br />
<PasswordField placeholder="Your password" />

14
src/components/pages/register/index.ts

@ -3,7 +3,7 @@ import { connect } from 'react-redux'
import { handleApiError } from 'src/api/errors'
import { getForm } from 'src/selectors/forms'
import { getStep } from 'src/selectors/registration'
import { initForm, initField } from 'src/actions/forms'
import { initForm, initField, setFieldValue } from 'src/actions/forms'
import { showNotification } from 'src/actions/notifications'
import { createGroup, register } from 'src/actions/registration'
@ -20,14 +20,16 @@ const mapStateToProps = (state: AppState) => ({
const mapDispatchToProps = (dispatch: AppThunkDispatch, ownProps: Props) => ({
initForm: () => {
dispatch(initForm())
dispatch(initField('group-name'))
dispatch(initField('group-registration'))
dispatch(initField('group-name', 'name'))
dispatch(initField('group-registration', 'registration'))
dispatch(initField('group-agree'))
dispatch(initField('user-id'))
dispatch(initField('user-name'))
dispatch(initField('user-email'))
dispatch(initField('user-id', 'id'))
dispatch(initField('user-name', 'name'))
dispatch(initField('user-email', 'email'))
dispatch(initField('password'))
dispatch(initField('user-agree'))
dispatch(setFieldValue('group-registration', 'open'))
},
register: async (form: Form) => {
const groupAgree = valueFromForm<boolean>(form, 'group-agree', false)

10
src/components/pages/self/index.ts

@ -1,8 +1,8 @@
import { connect } from 'react-redux'
import { getAuthenticatedUserId } from 'src/selectors/authentication'
import { getAuthenticated, getAuthenticatedUserId } from 'src/selectors/authentication'
import { getEntity } from 'src/selectors/entities'
import { AppState, AppThunkDispatch } from 'src/types'
import { AppState } from 'src/types'
import Self from './self'
@ -11,13 +11,11 @@ const mapStateToProps = (state: AppState) => {
const user = userId ? getEntity(state, 'user', userId) : undefined
return {
authenticated: getAuthenticated(state),
user,
}
}
const mapDispatchToProps = (dispatch: AppThunkDispatch) => ({})
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps
)(Self)

10
src/components/pages/self/self.tsx

@ -1,15 +1,21 @@
import React, { FC, useEffect } from 'react'
import { RouteComponentProps } from 'react-router-dom'
import { setTitle } from 'src/utils'
import { Entity } from 'src/types'
import PageHeader from 'src/components/page-header'
interface Props {
interface Props extends RouteComponentProps {
authenticated: boolean
user?: Entity
}
const Self: FC<Props> = ({ user }) => {
const Self: FC<Props> = ({ authenticated, user, history }) => {
useEffect(() => {
if (!authenticated) history.push('/login')
}, [authenticated])
useEffect(() => {
if (user) setTitle(user.name as string)
}, [user])

4
src/components/user-info/user-info.tsx

@ -27,11 +27,11 @@ const UserInfo: FC<Props> = ({ authenticated, user }) => {
<div className="media-content">
<div className="content">
<div className="has-text-centered">
<Link to="/login" className="is-size-6 has-text-white">Log In to Flexor</Link>
<Link to="/login" className="is-size-5 has-text-white">Log In to Flexor</Link>
<p className="is-size-7 has-text-primary is-uppercase">
or
</p>
<Link to="/communities" className="is-size-7 has-text-light">Create an Account</Link>
<Link to="/communities" className="is-size-6 has-text-light">Create an Account</Link>
</div>
</div>
</div>

20
src/reducers/forms.ts

@ -2,6 +2,7 @@ import { Reducer } from 'redux'
import { FormsActions } from '../actions/forms'
import { FormsState } from '../types'
import { valueFromForm } from 'src/utils'
const initialState: FormsState = {
form: {},
@ -16,12 +17,15 @@ const reducer: Reducer<FormsState, FormsActions> = (state = initialState, action
notification: undefined,
}
case 'FORMS_INIT_FIELD':
const { name, apiName } = action.payload
return {
...state,
form: {
...state.form,
[action.payload]: {
name: action.payload,
[name]: {
name,
apiName,
},
},
}
@ -52,13 +56,21 @@ const reducer: Reducer<FormsState, FormsActions> = (state = initialState, action
}
case 'FORMS_SET_FIELD_NOTIFICATION': {
const { name, type, message } = action.payload
const field = state.form[name]
const getField = () => {
const field = Object.entries(state.form).find(([_, value]) => value.apiName ? name === value.apiName : false)
if (field) return field[1]
return state.form[name]
}
const field = getField()
return {
...state,
form: {
...state.form,
[name]: {
[field.name]: {
...field,
notification: {
type,

1
src/types/store.ts

@ -39,6 +39,7 @@ export interface MenuState {
export interface FormField {
name: string
apiName?: string
value?: FormValue
notification?: FormNotification
}

2
src/utils/index.ts

@ -8,7 +8,7 @@ export function notificationTypeToClassName(type: NotificationType): string {
}
}
export const objectToQuerystring = (obj: object) => Object.entries(obj).filter(([name, value]) => value !== undefined).map(([name, value]) => `${name}=${value}`).join('&')
export const objectToQuerystring = (obj: object) => Object.entries(obj).filter(([_, value]) => value !== undefined).map(([name, value]) => `${name}=${value}`).join('&')
export function setTitle(title: string, decorate: boolean = true) {
if (decorate) {

Loading…
Cancel
Save