Dwayne Harris 5 years ago
parent
commit
4ee80cc987
  1. 12
      package-lock.json
  2. 4
      package.json
  3. 9
      src/actions/authentication.ts
  4. 43
      src/actions/directory.ts
  5. 9
      src/actions/entities.ts
  6. 18
      src/actions/forms.ts
  7. 0
      src/actions/index.ts
  8. 6
      src/actions/menu.ts
  9. 70
      src/actions/notifications.ts
  10. 9
      src/actions/requests.ts
  11. 6
      src/api/groups.ts
  12. 7
      src/components/app/app.tsx
  13. 6
      src/components/notification-container/index.ts
  14. 11
      src/components/notification-container/notification-container.scss
  15. 1
      src/components/notification-container/notification-container.tsx
  16. 9
      src/components/notification/index.tsx
  17. 2
      src/components/pages/directory/index.ts
  18. 15
      src/components/pages/register/register.tsx
  19. 2
      src/components/user-info/user-info.tsx
  20. 23
      src/reducers/notifications.ts
  21. 2
      src/store/index.ts
  22. 9
      src/store/schemas.ts
  23. 1
      src/types/store.ts

12
package-lock.json

@ -347,6 +347,15 @@
}
}
},
"@types/uuid": {
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz",
"integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/webpack": {
"version": "4.39.1",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.39.1.tgz",
@ -8120,8 +8129,7 @@
"uuid": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
"dev": true
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
},
"v8-compile-cache": {
"version": "2.0.3",

4
package.json

@ -22,6 +22,7 @@
"@types/react-redux": "^7.1.2",
"@types/react-router-dom": "^4.3.5",
"@types/redux-logger": "^3.0.7",
"@types/uuid": "^3.4.5",
"@types/webpack": "^4.39.1",
"@types/webpack-dev-server": "^3.1.7",
"bulma": "^0.7.5",
@ -52,6 +53,7 @@
"redux": "^4.0.4",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0"
"reselect": "^4.0.0",
"uuid": "^3.3.3"
}
}

9
src/actions/authentication.ts

@ -12,17 +12,12 @@ export interface SetUserAction extends Action {
export type AuthenticationActions = SetAuthenticatedAction | SetUserAction
const setAuthenticated = (authenticated: boolean): SetAuthenticatedAction => ({
export const setAuthenticated = (authenticated: boolean): SetAuthenticatedAction => ({
type: 'AUTHENTICATION_SET_AUTHENTICATED',
payload: authenticated,
})
const setUser = (userId: string): SetUserAction => ({
export const setUser = (userId: string): SetUserAction => ({
type: 'AUTHENTICATION_SET_USER',
payload: userId,
})
export {
setAuthenticated,
setUser,
}

43
src/actions/directory.ts

@ -2,13 +2,14 @@ import { Action, AnyAction } from 'redux'
import { ThunkAction, ThunkDispatch } from 'redux-thunk'
import { normalize } from 'normalizr'
import { getGroups } from '../api/groups'
import { setEntities } from '../actions/entities'
import { getGroup, getGroups } from '../api/groups'
import { setEntity, setEntities } from '../actions/entities'
import { startRequest, finishRequest } from '../actions/requests'
import { groupSchema } from '../store/schemas'
import { AppState } from '../types'
const FETCH_ID = 'groups'
const FETCH_GROUP_ID = 'FETCH_GROUP'
const FETCH_GROUPS_ID = 'FETCH_GROUPS'
export interface SetGroupsAction extends Action {
type: 'DIRECTORY_SET_GROUPS'
@ -27,24 +28,39 @@ export interface SetContinuationAction extends Action {
export type DirectoryActions = SetGroupsAction | AppendGroupsAction | SetContinuationAction
const setGroups = (groups: string[]): SetGroupsAction => ({
export const setGroups = (groups: string[]): SetGroupsAction => ({
type: 'DIRECTORY_SET_GROUPS',
payload: groups,
})
const appendGroups = (groups: string[]): AppendGroupsAction => ({
export const appendGroups = (groups: string[]): AppendGroupsAction => ({
type: 'DIRECTORY_APPEND_GROUPS',
payload: groups,
})
const setContinuation = (continuation: string): SetContinuationAction => ({
export const setContinuation = (continuation: string): SetContinuationAction => ({
type: 'DIRECTORY_SET_CONTINUATION',
payload: continuation,
})
const fetchGroups = (sort?: string, continuation?: string): ThunkAction<Promise<void>, AppState, void, AnyAction> => {
export const fetchGroup = (id: string) => {
return async (dispatch: ThunkDispatch<AppState, void, AnyAction>) => {
dispatch(startRequest(FETCH_ID))
dispatch(startRequest(FETCH_GROUP_ID))
try {
const group = await getGroup(id)
dispatch(setEntity('group', group))
dispatch(finishRequest(FETCH_GROUP_ID, true))
} catch (err) {
console.error(err)
dispatch(finishRequest(FETCH_GROUP_ID, false))
}
}
}
export const fetchGroups = (sort?: string, continuation?: string): ThunkAction<Promise<void>, AppState, void, AnyAction> => {
return async (dispatch: ThunkDispatch<AppState, void, AnyAction>) => {
dispatch(startRequest(FETCH_GROUPS_ID))
try {
const response = await getGroups(sort, continuation)
@ -57,17 +73,10 @@ const fetchGroups = (sort?: string, continuation?: string): ThunkAction<Promise<
dispatch(setContinuation(response.continuation))
}
dispatch(finishRequest(FETCH_ID, true))
dispatch(finishRequest(FETCH_GROUPS_ID, true))
} catch (err) {
console.error(err)
dispatch(finishRequest(FETCH_ID, false))
dispatch(finishRequest(FETCH_GROUPS_ID, false))
}
}
}
export {
setGroups,
appendGroups,
setContinuation,
fetchGroups,
}

9
src/actions/entities.ts

@ -18,7 +18,7 @@ export interface SetEntitiesAction extends Action {
export type EntitiesActions = SetEntityAction | SetEntitiesAction
const setEntity = (type: string, entity: Entity): SetEntityAction => ({
export const setEntity = (type: string, entity: Entity): SetEntityAction => ({
type: 'ENTITIES_SET_ENTITY',
payload: {
type,
@ -26,14 +26,9 @@ const setEntity = (type: string, entity: Entity): SetEntityAction => ({
}
})
const setEntities = (entities: EntityStore): SetEntitiesAction => ({
export const setEntities = (entities: EntityStore): SetEntitiesAction => ({
type: 'ENTITIES_SET_ENTITIES',
payload: {
entities,
}
})
export {
setEntity,
setEntities,
}

18
src/actions/forms.ts

@ -37,16 +37,16 @@ export interface SetFieldNotificationAction extends Action {
export type FormsActions = InitFormAction | InitFieldAction | SetFieldValueAction | SetFormNotificationAction | SetFieldNotificationAction
const initForm = (): InitFormAction => ({
export const initForm = (): InitFormAction => ({
type: 'FORMS_INIT',
})
const initField = (name: string): InitFieldAction => ({
export const initField = (name: string): InitFieldAction => ({
type: 'FORMS_INIT_FIELD',
payload: name,
})
const setFieldValue = (name: string, value: any): SetFieldValueAction => ({
export const setFieldValue = (name: string, value: any): SetFieldValueAction => ({
type: 'FORMS_SET_FIELD_VALUE',
payload: {
name,
@ -54,7 +54,7 @@ const setFieldValue = (name: string, value: any): SetFieldValueAction => ({
},
})
const setFormNotification = (type: NotificationType, message: string): SetFormNotificationAction => ({
export const setFormNotification = (type: NotificationType, message: string): SetFormNotificationAction => ({
type: 'FORMS_SET_FORM_NOTIFICATION',
payload: {
type,
@ -62,7 +62,7 @@ const setFormNotification = (type: NotificationType, message: string): SetFormNo
}
})
const setFieldNotification = (name: string, type: NotificationType, message: string): SetFieldNotificationAction => ({
export const setFieldNotification = (name: string, type: NotificationType, message: string): SetFieldNotificationAction => ({
type: 'FORMS_SET_FIELD_NOTIFICATION',
payload: {
name,
@ -70,11 +70,3 @@ const setFieldNotification = (name: string, type: NotificationType, message: str
message,
}
})
export {
initForm,
initField,
setFieldValue,
setFormNotification,
setFieldNotification,
}

0
src/actions/index.ts

6
src/actions/menu.ts

@ -7,11 +7,7 @@ export interface SetCollapsedAction extends Action {
export type MenuActions = SetCollapsedAction
const setCollapsed = (collapsed: boolean): SetCollapsedAction => ({
export const setCollapsed = (collapsed: boolean): SetCollapsedAction => ({
type: 'MENU_SET_COLLAPSED',
payload: collapsed
})
export {
setCollapsed,
}

70
src/actions/notifications.ts

@ -1,36 +1,66 @@
import { Action } from 'redux'
import { Action, AnyAction } from 'redux'
import { ThunkAction, ThunkDispatch } from 'redux-thunk'
import { v1 } from 'uuid'
export interface SetAutoAction extends Action {
type: 'NOTIFICATIONS_SET_AUTO'
import { AppState, NotificationType } from '../types'
export interface AddNotificationAction extends Action {
type: 'NOTIFICATIONS_ADD_NOTIFICATION'
payload: {
id: string
type: NotificationType
content: string
}
}
export interface DismissAction extends Action {
type: 'NOTIFICATIONS_DISMISS'
payload: {
id: string
}
export interface RemoveNotificationAction extends Action {
type: 'NOTIFICATIONS_REMOVE_NOTIFICATION'
payload: string
}
export type NotificationActions = SetAutoAction | DismissAction
export interface ClearNotificationAction extends Action {
type: 'NOTIFICATIONS_CLEAR_NOTIFICATION'
payload: string
}
const setAuto = (id: string): SetAutoAction => ({
type: 'NOTIFICATIONS_SET_AUTO',
export interface SetNotificationAutoAction extends Action {
type: 'NOTIFICATIONS_SET_AUTO'
payload: string
}
export type NotificationActions = AddNotificationAction | RemoveNotificationAction | ClearNotificationAction | SetNotificationAutoAction
export const addNotification = (id: string, type: NotificationType, content: string): AddNotificationAction => ({
type: 'NOTIFICATIONS_ADD_NOTIFICATION',
payload: {
id,
},
type,
content,
}
})
const dismiss = (id: string): DismissAction => ({
type: 'NOTIFICATIONS_DISMISS',
payload: {
id,
},
export const removeNotification = (id: string): RemoveNotificationAction => ({
type: 'NOTIFICATIONS_REMOVE_NOTIFICATION',
payload: id,
})
export const clearNotification = (id: string): ClearNotificationAction => ({
type: 'NOTIFICATIONS_CLEAR_NOTIFICATION',
payload: id,
})
export {
setAuto,
dismiss,
export const setNotificationAuto = (id: string): SetNotificationAutoAction => ({
type: 'NOTIFICATIONS_SET_AUTO',
payload: id,
})
export const showNotification = (type: NotificationType, content: string, expiration: number = 5000): ThunkAction<Promise<void>, AppState, void, AnyAction> => {
return async (dispatch: ThunkDispatch<AppState, void, AnyAction>) => {
const id = v1()
dispatch(addNotification(id, type, content))
setTimeout(() => {
dispatch(clearNotification(id))
}, expiration)
}
}

9
src/actions/requests.ts

@ -17,22 +17,17 @@ export interface FinishRequestAction extends Action {
export type RequestsActions = StartRequestAction | FinishRequestAction
const startRequest = (id: string): StartRequestAction => ({
export const startRequest = (id: string): StartRequestAction => ({
type: 'REQUESTS_START_REQUEST',
payload: {
id,
},
})
const finishRequest = (id: string, succeeded: boolean): FinishRequestAction => ({
export const finishRequest = (id: string, succeeded: boolean): FinishRequestAction => ({
type: 'REQUESTS_FINISH_REQUEST',
payload: {
id,
succeeded,
},
})
export {
startRequest,
finishRequest,
}

6
src/api/groups.ts

@ -6,6 +6,12 @@ interface GroupsResponse {
continuation?: string
}
export async function getGroup(id: string) {
return await fetch<Entity>({
path: `/api/group/${id}`
})
}
export async function getGroups(sort: string = 'members', continuation?: string) {
const params = {
sort,

7
src/components/app/app.tsx

@ -1,6 +1,7 @@
import React, { FC } from 'react'
import { HashRouter as Router, Route, Link } from 'react-router-dom'
import NotificationContainer from '../notification-container'
import Spinner from '../spinner'
import UserInfo from '../user-info'
@ -52,9 +53,11 @@ 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="/register/:group" component={Register} />
<Route path="/directory" component={Directory} />
<Route path="/c/:group/register" component={Register} />
<Route path="/communities" component={Directory} />
</div>
<NotificationContainer />
</div>
</Router>
)

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

@ -1,7 +1,7 @@
import { Dispatch } from 'redux'
import { connect } from 'react-redux'
import { setAuto, dismiss } from '../../actions/notifications'
import { setNotificationAuto, removeNotification } from '../../actions/notifications'
import { getNotifications } from '../../selectors'
import { AppState } from '../../types'
@ -13,10 +13,10 @@ const mapStateToProps = (state: AppState) => ({
const mapDispatchToProps = (dispatch: Dispatch) => ({
setAuto: (id: string) => {
dispatch(setAuto(id))
dispatch(setNotificationAuto(id))
},
dismiss: (id: string) => {
dispatch(dismiss(id))
dispatch(removeNotification(id))
}
})

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

@ -1,6 +1,11 @@
@import "../../../node_modules/bulma/sass/utilities/_all.sass";
@import "../../../node_modules/bulma/sass/base/_all.sass";
@import "../../../node_modules/bulma/sass/elements/button.sass";
@import "../../../node_modules/bulma/sass/elements/notification.sass";
div#notification-container {
bottom: 0;
bottom: 10px;
position: absolute;
right: 0;
width: 25%;
right: 10px;
width: 40%;
}

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

@ -17,6 +17,7 @@ const NotificationContainer: FC<Props> = ({ notifications, setAuto, dismiss }) =
{notifications.map(notification => {
return (
<Notification
key={notification.id}
id={notification.id}
type={notification.type}
auto={notification.auto}

9
src/components/notification/index.tsx

@ -1,4 +1,4 @@
import React, { FC } from 'react'
import React, { FC, MouseEventHandler } from 'react'
import { NotificationType } from '../../types'
interface Props {
@ -23,9 +23,14 @@ const Notification: FC<Props> = ({ id, type, auto, setAuto, dismiss, children })
getClassName(type),
].join(' ')
const handleDismiss: MouseEventHandler = e => {
e.stopPropagation()
dismiss(id)
}
return (
<div className={classnames} onClick={() => setAuto(id)}>
{!auto && <button className="delete" onClick={() => dismiss(id)}></button>}
{!auto && <button className="delete" onClick={handleDismiss}></button>}
{children}
</div>
)

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

@ -15,7 +15,7 @@ const mapStateToProps = (state: AppState) => ({
const mapDispatchToProps = (dispatch: ThunkDispatch<AppState, void, AnyAction>) => ({
fetchGroups: () => {
dispatch(fetchGroups())
}
},
})
export default connect(

15
src/components/pages/register/register.tsx

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

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

@ -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="/directory" className="is-size-7 has-text-primary">Sign Up</Link>
<Link to="/communities" className="is-size-7 has-text-primary">Sign Up</Link>
</p>
</div>
</div>

23
src/reducers/notifications.ts

@ -7,9 +7,28 @@ const initialState: NotificationsState = []
const reducer: Reducer<NotificationsState, NotificationActions> = (state = initialState, action) => {
switch (action.type) {
case 'NOTIFICATIONS_ADD_NOTIFICATION':
const { id, type, content } = action.payload
return [
...state,
{
id,
type,
content,
auto: true,
},
]
case 'NOTIFICATIONS_REMOVE_NOTIFICATION':
return state.filter(notification => notification.id !== action.payload)
case 'NOTIFICATIONS_CLEAR_NOTIFICATION':
return state.filter(notification => {
if (notification.id === action.payload) return !notification.auto
return true
})
case 'NOTIFICATIONS_SET_AUTO':
return state.map(notification => {
if (notification.id === action.payload.id) {
if (notification.id === action.payload) {
return {
...notification,
auto: false,
@ -18,8 +37,6 @@ const reducer: Reducer<NotificationsState, NotificationActions> = (state = initi
return notification
})
case 'NOTIFICATIONS_DISMISS':
return state.filter(notification => notification.id !== action.payload.id)
default:
return state
}

2
src/store/index.ts

@ -22,7 +22,7 @@ const store = createStore(
notifications,
requests,
}),
applyMiddleware(logger, thunk)
applyMiddleware(thunk, logger)
)
export default store

9
src/store/schemas.ts

@ -1,12 +1,7 @@
import { schema } from 'normalizr'
const groupSchema = new schema.Entity('groups')
export const groupSchema = new schema.Entity('groups')
const userSchema = new schema.Entity('users', {
export const userSchema = new schema.Entity('users', {
groupSchema,
})
export {
groupSchema,
userSchema,
}

1
src/types/store.ts

@ -25,7 +25,6 @@ export interface Notification {
type: NotificationType
content: string
auto: boolean
expiration: number
}
export interface AuthenticationState {

Loading…
Cancel
Save