Dwayne Harris 5 years ago
parent
commit
e3bd932fd1
  1. 4
      src/actions/directory.ts
  2. 6
      src/api/errors.ts
  3. 4
      src/components/app/index.ts
  4. 3
      src/components/pages/directory/directory.scss
  5. 14
      src/components/pages/directory/directory.tsx
  6. 6
      src/components/pages/directory/index.ts
  7. 14
      src/components/pages/register/index.ts
  8. 4
      src/components/pages/register/register.tsx
  9. 2
      src/components/spinner/index.tsx
  10. 8
      src/components/spinner/spinner.scss
  11. 4
      src/components/user-info/index.ts
  12. 20
      src/routes/index.ts
  13. 25
      src/selectors/index.ts
  14. 4
      src/store/index.ts
  15. 10
      src/store/schemas.ts
  16. 41
      src/types/store.ts
  17. 4
      tsconfig.json
  18. 16
      webpack.config.ts

4
src/actions/directory.ts

@ -5,7 +5,7 @@ import { normalize } from 'normalizr'
import { getGroups } from '../api/groups'
import { setEntities } from '../actions/entities'
import { startRequest, finishRequest } from '../actions/requests'
import { group } from '../store/schemas'
import { groupSchema } from '../store/schemas'
import { AppState } from '../types'
const FETCH_ID = 'groups'
@ -48,7 +48,7 @@ const fetchGroups = (sort?: string, continuation?: string): ThunkAction<Promise<
try {
const response = await getGroups(sort, continuation)
const groups = normalize(response.groups, group)
const groups = normalize(response.groups, [groupSchema])
dispatch(setEntities(groups.entities))
dispatch(setGroups(groups.result))

6
src/api/errors.ts

@ -1,8 +1,8 @@
import { IFormNotification } from '../types'
import { FormNotification } from '../types'
export class HttpError extends Error {
statusCode: number
errors: IFormNotification[]
errors: FormNotification[]
constructor(message = 'Unknown Error') {
super(message)
@ -23,7 +23,7 @@ export class ServerError extends HttpError {
}
export class BadRequestError extends HttpError {
constructor(message = 'Bad Request', errors: IFormNotification[] = []) {
constructor(message = 'Bad Request', errors: FormNotification[] = []) {
super(message)
this.name = 'BadRequestError'

4
src/components/app/index.ts

@ -1,11 +1,11 @@
import { connect } from 'react-redux'
import { getMenuCollapsed, getFetching } from '../../selectors'
import { IAppState } from '../../types'
import { AppState } from '../../types'
import App from './app'
const mapStateToProps = (state: IAppState) => ({
const mapStateToProps = (state: AppState) => ({
menuCollapsed: getMenuCollapsed(state),
fetching: getFetching(state),
})

3
src/components/pages/directory/directory.scss

@ -0,0 +1,3 @@
@import "../../../../node_modules/bulma/sass/utilities/_all.sass";
@import "../../../../node_modules/bulma/sass/base/_all.sass";
@import "../../../../node_modules/bulma/sass/elements/title.sass";

14
src/components/pages/directory/directory.tsx

@ -1,18 +1,28 @@
import React, { FC } from 'react'
import React, { FC, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Entity } from '../../../types'
import './directory.scss'
interface Props {
groups: Entity[]
fetchGroups: () => void
}
const Directory: FC<Props> = ({ groups, fetchGroups }) => {
useEffect(() => {
fetchGroups()
}, [])
return (
<div>
<h1 className="title">Communities</h1>
{groups.length === 0 && <p>No Communities</p>}
<p className="has-text-centered">
<Link className="has-text-weight-bold has-text-primary" to="/register">Create your own Community</Link>
</p>
</div>
)
}

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

@ -3,12 +3,14 @@ import { ThunkDispatch } from 'redux-thunk'
import { AnyAction } from 'redux'
import { fetchGroups } from '../../../actions/directory'
import { getDirectoryGroups } from '../../../selectors'
import { AppState } from '../../../types'
import Directory from './directory'
const mapStateToProps = () => {
}
const mapStateToProps = (state: AppState) => ({
groups: getDirectoryGroups(state),
})
const mapDispatchToProps = (dispatch: ThunkDispatch<AppState, void, AnyAction>) => ({
fetchGroups: () => {

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

@ -1,21 +1,9 @@
import { connect } from 'react-redux'
import { ThunkDispatch } from 'redux-thunk'
import { fetchGroups, FetchGroupsAction } from '../../../actions/groups'
import { AppState } from '../../../types'
import Register from './register'
const mapStateToProps = () => {
}
const mapDispatchToProps = (dispatch: ThunkDispatch<AppState, void, FetchGroupsAction>) => ({
fetchGroups: () => {
dispatch(fetchGroups())
}
})
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps
)(Register)

4
src/components/pages/register/register.tsx

@ -8,7 +8,9 @@ interface Props {
const Register: FC<Props> = ({ group }) => {
return (
<div></div>
<div>
<h1 className="title">Create a new Account</h1>
</div>
)
}

2
src/components/spinner/index.tsx

@ -1,5 +1,7 @@
import React, { FC } from 'react'
import './spinner.scss'
const Spinner: FC = () => (
<div className="sk-cube-grid">
<div className="sk-cube sk-cube1"></div>

8
src/components/spinner/spinner.scss

@ -1,13 +1,13 @@
.sk-cube-grid {
width: 40px;
height: 40px;
margin: 100px auto;
width: 30px;
height: 30px;
margin: 10px auto;
}
.sk-cube-grid .sk-cube {
width: 33%;
height: 33%;
background-color: #333;
background-color: #000;
float: left;
-webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;

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

@ -1,11 +1,11 @@
import { connect } from 'react-redux'
import { getAuthenticated } from '../../selectors'
import { IAppState } from '../../types'
import { AppState } from '../../types'
import UserInfo from './user-info'
const mapStateToProps = (state: IAppState) => ({
const mapStateToProps = (state: AppState) => ({
authenticated: getAuthenticated(state),
})

20
src/routes/index.ts

@ -1,20 +0,0 @@
import { FC } from 'react'
import { Dispatch } from 'redux'
import Home from '../components/pages/home'
export interface Route {
id: string
path: string
component: FC
fetchInitialData?: (dispatch: Dispatch, ...args: any[]) => void
}
const routes: Route[] = [
{
id: 'home',
path: '/',
component: Home,
}
]
export default routes

25
src/selectors/index.ts

@ -1,13 +1,26 @@
import { denormalize } from 'normalizr'
import { createSelector } from 'reselect'
import values from 'lodash/values'
import { AppState, APIRequest } from '../types'
const REQUEST_LOADING_MIN = 500
import { groupSchema } from '../store/schemas'
import { AppState, Entity } from '../types'
export const getEntityStore = (state: AppState) => state.entities
export const getMenuCollapsed = (state: AppState) => state.menu.collapsed
export const getAuthenticated = (state: AppState) => state.authentication.authenticated
export const getNotifications = (state: AppState) => state.notifications
export const getRequests = (state: AppState) => state.requests
export const getFetching = createSelector(getRequests, requests => {
return values(requests).reduce((fetching, request) => fetching || request.fetching, false)
})
export const getDirectoryGroupIds = (state: AppState) => state.directory.groups
export const getFetching = (state: AppState) => {
const isFetching = (request: APIRequest) => request.fetching && (Date.now() - request.started > REQUEST_LOADING_MIN)
return values(state.requests).reduce((fetching, request) => fetching || isFetching(request) , false)
}
export const getDirectoryGroups = createSelector(
[getEntityStore, getDirectoryGroupIds],
(store, groups) => {
return denormalize(groups, [groupSchema], store) as Entity[]
}
)

4
src/store/index.ts

@ -2,7 +2,9 @@ import { createStore, combineReducers, applyMiddleware } from 'redux'
import { AppState } from '../types'
import authentication from '../reducers/authentication'
import directory from '../reducers/directory'
import entities from '../reducers/entities'
import forms from '../reducers/forms'
import menu from '../reducers/menu'
import notifications from '../reducers/notifications'
import requests from '../reducers/requests'
@ -13,7 +15,9 @@ import thunk from 'redux-thunk'
const store = createStore(
combineReducers<AppState>({
authentication,
directory,
entities,
forms,
menu,
notifications,
requests,

10
src/store/schemas.ts

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

41
src/types/store.ts

@ -10,10 +10,10 @@ export interface FormNotification {
}
export interface APIRequest {
readonly id: string
readonly fetching: boolean
readonly started: number
readonly succeeded: boolean
id: string
fetching: boolean
started: number
succeeded: boolean
}
export interface APIRequestCollection {
@ -21,26 +21,26 @@ export interface APIRequestCollection {
}
export interface Notification {
readonly id: string
readonly type: NotificationType
readonly content: string
readonly auto: boolean
readonly expiration: number
id: string
type: NotificationType
content: string
auto: boolean
expiration: number
}
export interface AuthenticationState {
readonly authenticated: boolean
readonly userId?: string
authenticated: boolean
userId?: string
}
export interface MenuState {
readonly collapsed: boolean
collapsed: boolean
}
export interface FormField {
readonly name: string
readonly value?: string
readonly notification?: FormNotification
name: string
value?: string
notification?: FormNotification
}
export interface Form {
@ -48,21 +48,22 @@ export interface Form {
}
export interface FormsState {
readonly form: Form
readonly notification?: FormNotification
form: Form
notification?: FormNotification
}
export interface DirectoryState {
readonly groups: string[]
readonly continuation?: string
groups: string[]
continuation?: string
}
export type RequestsState = APIRequestCollection
export type NotificationsState = readonly Notification[]
export type NotificationsState = Notification[]
export type EntitiesState = EntityStore
export interface AppState {
authentication: AuthenticationState
directory: DirectoryState
entities: EntitiesState
forms: FormsState
menu: MenuState

4
tsconfig.json

@ -1,11 +1,13 @@
{
"compilerOptions": {
"outDir": "./dist/",
"baseUrl": ".",
"sourceMap": true,
"strict": true,
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"jsx": "react"
"jsx": "react",
"resolveJsonModule": true
}
}

16
webpack.config.ts

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

Loading…
Cancel
Save