Dwayne Harris 5 years ago
parent
commit
5d824f1c1b
  1. 0
      config/config.json
  2. 11
      package-lock.json
  3. 3
      package.json
  4. 6
      src/actions/directory.ts
  5. 6
      src/components/app/app.tsx
  6. 4
      src/components/app/index.ts
  7. 6
      src/components/notification-container/index.ts
  8. 2
      src/components/notification-container/notification-container.tsx
  9. 21
      src/components/notification/index.tsx
  10. 3
      src/components/pages/directory/directory.tsx
  11. 6
      src/components/pages/directory/index.ts
  12. 29
      src/components/pages/register-group/index.ts
  13. 36
      src/components/pages/register-group/register-group.tsx
  14. 9
      src/components/pages/register/index.ts
  15. 26
      src/components/pages/register/register.tsx
  16. 24
      src/components/register-form/index.ts
  17. 21
      src/components/register-form/register-form.tsx
  18. 15
      src/components/text-field/index.ts
  19. 49
      src/components/text-field/text-field.tsx
  20. 4
      src/components/user-info/index.ts
  21. 4
      src/components/user-info/user-info.tsx
  22. 19
      src/selectors/index.ts
  23. 9
      src/utils/index.ts
  24. 6
      webpack.config.ts

0
config/local.json → config/config.json

11
package-lock.json

@ -58,6 +58,12 @@
"@types/node": "*"
}
},
"@types/classnames": {
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.9.tgz",
"integrity": "sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A==",
"dev": true
},
"@types/clean-css": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.1.tgz",
@ -1513,6 +1519,11 @@
}
}
},
"classnames": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
},
"clean-css": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",

3
package.json

@ -14,6 +14,7 @@
"deploy:prod": "run-s deploy:batch:prod deploy:config:prod"
},
"devDependencies": {
"@types/classnames": "^2.2.9",
"@types/html-webpack-plugin": "^3.2.1",
"@types/lodash": "^4.14.138",
"@types/mini-css-extract-plugin": "^0.8.0",
@ -41,9 +42,11 @@
"webpack-dev-server": "^3.8.0"
},
"dependencies": {
"@fortawesome/fontawesome-common-types": "^0.2.22",
"@fortawesome/fontawesome-svg-core": "^1.2.22",
"@fortawesome/free-solid-svg-icons": "^5.10.2",
"@fortawesome/react-fontawesome": "^0.1.4",
"classnames": "^2.2.6",
"lodash": "^4.17.15",
"normalizr": "^3.4.1",
"react": "^16.9.0",

6
src/actions/directory.ts

@ -43,7 +43,7 @@ export const setContinuation = (continuation: string): SetContinuationAction =>
payload: continuation,
})
export const fetchGroup = (id: string) => {
export const fetchGroup = (id: string): ThunkAction<Promise<void>, AppState, void, AnyAction> => {
return async (dispatch: ThunkDispatch<AppState, void, AnyAction>) => {
dispatch(startRequest(FETCH_GROUP_ID))
@ -52,8 +52,8 @@ export const fetchGroup = (id: string) => {
dispatch(setEntity('group', group))
dispatch(finishRequest(FETCH_GROUP_ID, true))
} catch (err) {
console.error(err)
dispatch(finishRequest(FETCH_GROUP_ID, false))
throw err
}
}
}
@ -75,8 +75,8 @@ export const fetchGroups = (sort?: string, continuation?: string): ThunkAction<P
dispatch(finishRequest(FETCH_GROUPS_ID, true))
} catch (err) {
console.error(err)
dispatch(finishRequest(FETCH_GROUPS_ID, false))
throw err
}
}
}

6
src/components/app/app.tsx

@ -1,12 +1,12 @@
import React, { FC } from 'react'
import { HashRouter as Router, Route, Link } from 'react-router-dom'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
import NotificationContainer from '../notification-container'
import Spinner from '../spinner'
import UserInfo from '../user-info'
import Home from '../pages/home'
import Register from '../pages/register'
import RegisterGroup from '../pages/register-group'
import Directory from '../pages/directory'
import './app.scss'
@ -53,7 +53,7 @@ const App: FC<Props> = ({ menuCollapsed, fetching }) => {
<div id="main-column" style={{ marginLeft: mainColumnLeftMargin }}>
<Route exact path="/" component={Home} />
<Route path="/login" component={Home} />
<Route path="/c/:group/register" component={Register} />
<Route path="/c/:id/register" component={RegisterGroup} />
<Route path="/communities" component={Directory} />
</div>

4
src/components/app/index.ts

@ -1,7 +1,7 @@
import { connect } from 'react-redux'
import { getMenuCollapsed, getFetching } from '../../selectors'
import { AppState } from '../../types'
import { getMenuCollapsed, getFetching } from 'src/selectors'
import { AppState } from 'src/types'
import App from './app'

6
src/components/notification-container/index.ts

@ -1,9 +1,9 @@
import { Dispatch } from 'redux'
import { connect } from 'react-redux'
import { setNotificationAuto, removeNotification } from '../../actions/notifications'
import { getNotifications } from '../../selectors'
import { AppState } from '../../types'
import { setNotificationAuto, removeNotification } from 'src/actions/notifications'
import { getNotifications } from 'src/selectors'
import { AppState } from 'src/types'
import NotificationContainer from './notification-container'

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

@ -1,5 +1,5 @@
import React, { FC } from 'react'
import { Notification as INotification } from '../../types'
import { Notification as INotification } from 'src/types'
import Notification from '../notification'

21
src/components/notification/index.tsx

@ -1,5 +1,8 @@
import React, { FC, MouseEventHandler } from 'react'
import { NotificationType } from '../../types'
import classNames from 'classnames'
import { notificationTypeToClassName } from 'src/utils'
import { NotificationType } from 'src/types'
interface Props {
id: string
@ -9,19 +12,11 @@ interface Props {
dismiss: (id: string) => void
}
const getClassName = (type: NotificationType) => {
switch (type) {
case 'info': return 'is-info'
case 'success': return 'is-success'
case 'error': return 'is-danger'
}
}
const Notification: FC<Props> = ({ id, type, auto, setAuto, dismiss, children }) => {
const classnames = [
'notification',
getClassName(type),
].join(' ')
const classnames = classNames({
notification: true,
[notificationTypeToClassName(type)]: true,
})
const handleDismiss: MouseEventHandler = e => {
e.stopPropagation()

3
src/components/pages/directory/directory.tsx

@ -1,7 +1,6 @@
import React, { FC, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Entity } from '../../../types'
import { Entity } from 'src/types'
import './directory.scss'
interface Props {

6
src/components/pages/directory/index.ts

@ -2,9 +2,9 @@ import { connect } from 'react-redux'
import { ThunkDispatch } from 'redux-thunk'
import { AnyAction } from 'redux'
import { fetchGroups } from '../../../actions/directory'
import { getDirectoryGroups } from '../../../selectors'
import { AppState } from '../../../types'
import { fetchGroups } from 'src/actions/directory'
import { getDirectoryGroups } from 'src/selectors'
import { AppState } from 'src/types'
import Directory from './directory'

29
src/components/pages/register-group/index.ts

@ -0,0 +1,29 @@
import { AnyAction } from 'redux'
import { ThunkDispatch } from 'redux-thunk'
import { connect } from 'react-redux'
import { fetchGroup } from 'src/actions/directory'
import { getEntity } from 'src/selectors'
import { AppState } from 'src/types'
import RegisterGroup, { Props } from './register-group'
const mapStateToProps = (state: AppState, ownProps: Props) => ({
group: getEntity(state, 'group', ownProps.match.params.id),
})
const mapDispatchToProps = (dispatch: ThunkDispatch<AppState, void, AnyAction>, ownProps: Props) => ({
fetchGroup: async () => {
try {
await dispatch(fetchGroup(ownProps.match.params.id))
} catch (err) {
console.error('Expected error')
console.error(err)
}
},
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(RegisterGroup)

36
src/components/pages/register-group/register-group.tsx

@ -0,0 +1,36 @@
import React, { FC, useEffect } from 'react'
import { RouteComponentProps } from 'react-router'
import { Entity } from 'src/types'
interface Params {
id: string
}
export interface Props extends RouteComponentProps<Params> {
group?: Entity
fetchGroup: () => void
}
const RegisterGroup: FC<Props> = ({ group, fetchGroup }) => {
useEffect(() => {
fetchGroup()
}, [])
if (!group) {
return (
<div>
<h1 className="title">Community Not Found</h1>
</div>
)
}
return (
<div>
<h1 className="title">{group.name}</h1>
<h2 className="subtitle">Create a new Account.</h2>
</div>
)
}
export default RegisterGroup

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

@ -1,9 +0,0 @@
import { connect } from 'react-redux'
import Register from './register'
const mapStateToProps = () => {
}
export default connect(
mapStateToProps
)(Register)

26
src/components/pages/register/register.tsx

@ -1,26 +0,0 @@
import React, { FC, useEffect } from 'react'
import { RouteComponentProps } from 'react-router'
import { Entity } from '../../../types'
interface Params {
group: string
}
type Props = RouteComponentProps<Params> & {
group?: Entity
}
const Register: FC<Props> = ({ group, match }) => {
useEffect(() => {
})
return (
<div>
<h1 className="title">Create a new Account</h1>
</div>
)
}
export default Register

24
src/components/register-form/index.ts

@ -0,0 +1,24 @@
import { Dispatch } from 'redux'
import { connect } from 'react-redux'
import { initForm, initField } from 'src/actions/forms'
import { AppState } from 'src/types'
import RegisterForm from './register-form'
const mapStateToProps = (state: AppState) => ({
})
const mapDispatchToProps = (dispatch: Dispatch) => ({
initForm: () => {
dispatch(initForm())
dispatch(initField('username'))
dispatch(initField('name'))
dispatch(initField('email'))
}
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(RegisterForm)

21
src/components/register-form/register-form.tsx

@ -0,0 +1,21 @@
import React, { FC, useEffect } from 'react'
import TextField from '../text-field'
import './register-form.scss'
interface Props {
initForm: () => void
}
const RegisterForm: FC<Props> = ({ initForm }) => {
useEffect(() => {
initForm()
}, [])
return (
<div className="container">
<TextField name="username" label="Username" />
</div>
)
}
export default RegisterForm

15
src/components/text-field/index.ts

@ -0,0 +1,15 @@
import { connect } from 'react-redux'
import { getFieldValue, getFieldNotification } from 'src/selectors'
import { AppState } from 'src/types'
import TextField, { Props } from './text-field'
const mapStateToProps = (state: AppState, ownProps: Props) => ({
value: getFieldValue(state, ownProps.name),
notification: getFieldNotification(state, ownProps.name),
})
export default connect(
mapStateToProps
)(TextField)

49
src/components/text-field/text-field.tsx

@ -0,0 +1,49 @@
import React, { FC } from 'react'
import classNames from 'classnames'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { IconDefinition } from '@fortawesome/fontawesome-common-types'
import { notificationTypeToClassName } from 'src/utils'
import { FormNotification } from 'src/types'
import './register-form.scss'
export interface Props {
name: string
label: string
placeholder?: string
icon?: IconDefinition
value?: string
notification?: FormNotification
}
const RegisterForm: FC<Props> = ({ label, placeholder, icon, value, notification }) => {
const controlClassNames = classNames({ control: true, 'has-icons-left': !!icon })
let helpClassNames: string | undefined
if (notification) {
helpClassNames = classNames({
help: true,
[notificationTypeToClassName(notification.type)]: true,
})
}
return (
<div className="field">
<label className="label">{label}</label>
<div className={controlClassNames}>
<input className="input" type="text" placeholder={placeholder} value={value} />
{icon &&
<span className="icon is-small is-left">
<FontAwesomeIcon icon={icon} />
</span>
}
</div>
{notification &&
<p className={helpClassNames}>{notification.message}</p>
}
</div>
)
}
export default RegisterForm

4
src/components/user-info/index.ts

@ -1,7 +1,7 @@
import { connect } from 'react-redux'
import { getAuthenticated } from '../../selectors'
import { AppState } from '../../types'
import { getAuthenticated } from 'src/selectors'
import { AppState } from 'src/types'
import UserInfo from './user-info'

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

@ -1,7 +1,7 @@
import React, { FC } from 'react'
import { Link } from 'react-router-dom'
import { User } from '../../types'
import { User } from 'src/types'
import './user-info.scss'
@ -32,7 +32,7 @@ const UserInfo: FC<Props> = ({ authenticated, user }) => {
<p>
<Link to="/login" className="is-size-5 has-text-white has-text-weight-bold">Log In</Link>
<br />
<Link to="/communities" className="is-size-7 has-text-primary">Sign Up</Link>
<Link to="/communities" className="is-size-7 has-text-primary">Register</Link>
</p>
</div>
</div>

19
src/selectors/index.ts

@ -7,6 +7,25 @@ import { AppState, Entity } from '../types'
export const getEntityStore = (state: AppState) => state.entities
export const getEntity = (state: AppState, type: string, id: string) => {
const store = getEntityStore(state)
const collection = store[type]
return collection ? collection[id] : undefined
}
export const getForm = (state: AppState) => state.forms.form
export const getFieldValue = (state: AppState, name: string) => {
const field = getForm(state)[name]
return field.value
}
export const getFieldNotification = (state: AppState, name: string) => {
const field = getForm(state)[name]
return field.notification
}
export const getMenuCollapsed = (state: AppState) => state.menu.collapsed
export const getAuthenticated = (state: AppState) => state.authentication.authenticated
export const getNotifications = (state: AppState) => state.notifications

9
src/utils/index.ts

@ -0,0 +1,9 @@
import { NotificationType } from '../types'
export function notificationTypeToClassName(type: NotificationType): string {
switch (type) {
case 'info': return 'is-info'
case 'success': return 'is-success'
case 'error': return 'is-danger'
}
}

6
webpack.config.ts

@ -2,7 +2,7 @@ import { Configuration } from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import localConfig from './config/local.json'
import c from './config/config.json'
const config: Configuration = {
mode: 'development',
@ -17,15 +17,17 @@ const config: Configuration = {
},
devServer: {
contentBase: `${__dirname}/dist`,
historyApiFallback: true,
before: app => {
app.get('/config.json', (req, res) => {
res.json(localConfig)
res.json(c)
})
},
},
resolve: {
extensions: ['.ts', '.tsx', '.js'],
alias: {
src: `${__dirname}/src`,
actions: `${__dirname}/src/actions/`,
components: `${__dirname}/src/components/`,
reducers: `${__dirname}/src/reducers/`,

Loading…
Cancel
Save