diff --git a/package-lock.json b/package-lock.json index 009d588..10a8ed1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -393,6 +393,15 @@ } } }, + "@types/webpack-bundle-analyzer": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.13.3.tgz", + "integrity": "sha512-p8EXyKfq311FFFfRuAR9tOHFFTQ9DqGrjRQYXbjjEMfl9pKGaTtRy1zFJtPMyZHfRoqh5rsYPVSVknkl004M7A==", + "dev": true, + "requires": { + "@types/webpack": "*" + } + }, "@types/webpack-dev-server": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.1.7.tgz", @@ -641,6 +650,12 @@ "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", "dev": true }, + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "dev": true + }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", @@ -1084,6 +1099,18 @@ "tweetnacl": "^0.14.3" } }, + "bfj": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", + "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "check-types": "^8.0.3", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -1397,6 +1424,12 @@ "supports-color": "^5.3.0" } }, + "check-types": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", + "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", + "dev": true + }, "chokidar": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", @@ -2188,6 +2221,12 @@ "domelementtype": "1" } }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -2216,6 +2255,12 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, + "ejs": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.1.tgz", + "integrity": "sha512-kS/gEPzZs3Y1rRsbGX4UOSjtP/CeJP0CxSNZHYxGfVM/VgLcv0ZqM7C45YyTj2DI2g7+P9Dd24C+IMIg6D0nYQ==", + "dev": true + }, "elliptic": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", @@ -2622,6 +2667,12 @@ "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", "dev": true }, + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "dev": true + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3668,6 +3719,16 @@ "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" }, + "gzip-size": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "pify": "^4.0.1" + } + }, "handle-thing": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", @@ -3853,6 +3914,12 @@ "parse-passwd": "^1.0.0" } }, + "hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "dev": true + }, "hosted-git-info": { "version": "2.8.4", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", @@ -5029,6 +5096,11 @@ } } }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -5500,6 +5572,12 @@ "wrappy": "1" } }, + "opener": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", + "dev": true + }, "opn": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", @@ -7855,6 +7933,12 @@ "glob": "^7.1.2" } }, + "tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", + "dev": true + }, "ts-loader": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.1.2.tgz", @@ -8347,6 +8431,27 @@ } } }, + "webpack-bundle-analyzer": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.5.1.tgz", + "integrity": "sha512-CDdaT3TTu4F9X3tcDq6PNJOiNGgREOM0WdN2vVAoUUn+M6NLB5kJ543HImCWbrDwOpbpGARSwU8r+u0Pl367kA==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-walk": "^6.1.1", + "bfj": "^6.1.1", + "chalk": "^2.4.1", + "commander": "^2.18.0", + "ejs": "^2.6.1", + "express": "^4.16.3", + "filesize": "^3.6.1", + "gzip-size": "^5.0.0", + "lodash": "^4.17.15", + "mkdirp": "^0.5.1", + "opener": "^1.5.1", + "ws": "^6.0.0" + } + }, "webpack-cli": { "version": "3.3.9", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.9.tgz", diff --git a/package.json b/package.json index f922b3a..4d982de 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@types/redux-logger": "^3.0.7", "@types/uuid": "^3.4.5", "@types/webpack": "^4.39.1", + "@types/webpack-bundle-analyzer": "^2.13.3", "@types/webpack-dev-server": "^3.1.7", "@types/zxcvbn": "^4.4.0", "bulma": "^0.7.5", @@ -42,6 +43,7 @@ "ts-node": "^8.4.1", "typescript": "^3.6.3", "webpack": "^4.40.2", + "webpack-bundle-analyzer": "^3.5.1", "webpack-cli": "^3.3.9", "webpack-dev-server": "^3.8.1" }, @@ -53,6 +55,7 @@ "classnames": "^2.2.6", "history": "^4.10.1", "lodash": "^4.17.15", + "moment": "^2.24.0", "normalizr": "^3.4.1", "react": "^16.9.0", "react-avatar-editor": "^11.0.7", diff --git a/src/actions/authentication.ts b/src/actions/authentication.ts index 38f98fc..120887c 100644 --- a/src/actions/authentication.ts +++ b/src/actions/authentication.ts @@ -6,8 +6,13 @@ import { setEntities } from 'src/actions/entities' import { startRequest, finishRequest } from 'src/actions/requests' import { userSchema } from 'src/store/schemas' -import { REQUEST_KEYS } from 'src/constants' -import { AppThunkAction, Entity } from 'src/types' +import { + LOCAL_STORAGE_ACCESS_TOKEN_KEY, + LOCAL_STORAGE_REFRESH_TOKEN_KEY, + LOCAL_STORAGE_ACCESS_TOKEN_AT_KEY, +} from 'src/constants' + +import { AppThunkAction, Entity, RequestKey } from 'src/types' export interface SetCheckedAction extends Action { type: 'AUTHENTICATION_SET_CHECKED' @@ -23,7 +28,11 @@ export interface SetUserAction extends Action { payload: string } -export type AuthenticationActions = SetCheckedAction | SetAuthenticatedAction | SetUserAction +export interface UnauthenticateAction extends Action { + type: 'AUTHENTICATION_UNAUTHENTICATE' +} + +export type AuthenticationActions = SetCheckedAction | SetAuthenticatedAction | SetUserAction | UnauthenticateAction export const setChecked = (): SetCheckedAction => ({ type: 'AUTHENTICATION_SET_CHECKED', @@ -39,8 +48,12 @@ export const setUser = (userId: string): SetUserAction => ({ payload: userId, }) +export const unauthenticate = (): UnauthenticateAction => ({ + type: 'AUTHENTICATION_UNAUTHENTICATE', +}) + export const fetchSelf = (): AppThunkAction => async dispatch => { - dispatch(startRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY)) + dispatch(startRequest(RequestKey.FetchGroupAvailability)) try { const self = await apiFetch({ @@ -53,10 +66,44 @@ export const fetchSelf = (): AppThunkAction => async dispatch => { dispatch(setUser(self.id)) dispatch(setAuthenticated(true)) - dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY, true)) + dispatch(finishRequest(RequestKey.FetchGroupAvailability, true)) } catch (err) { dispatch(setAuthenticated(false)) - dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY, false)) + dispatch(finishRequest(RequestKey.FetchGroupAvailability, false)) + throw err + } +} + +interface AuthenticateResponse { + id: string + access: string + refresh: string + expires: number +} + +export const authenticate = (name: string, password: string): AppThunkAction => async dispatch => { + dispatch(startRequest(RequestKey.Authenticate)) + + try { + const response = await apiFetch({ + path: '/api/authenticate', + method: 'post', + body: { + id: name, + password, + }, + }) + + localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY, response.access) + localStorage.setItem(LOCAL_STORAGE_REFRESH_TOKEN_KEY, response.refresh) + if (response.expires) localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN_AT_KEY, response.expires.toString()) + + dispatch(finishRequest(RequestKey.Authenticate, true)) + await dispatch(fetchSelf()) + + return response.id + } catch (err) { + dispatch(finishRequest(RequestKey.Authenticate, false)) throw err } } diff --git a/src/actions/directory.ts b/src/actions/directory.ts index a8450bf..d868323 100644 --- a/src/actions/directory.ts +++ b/src/actions/directory.ts @@ -6,9 +6,8 @@ import { setEntity, setEntities } from 'src/actions/entities' import { startRequest, finishRequest } from 'src/actions/requests' import { groupSchema } from 'src/store/schemas' -import { REQUEST_KEYS } from 'src/constants' import { objectToQuerystring } from 'src/utils' -import { AppThunkAction, Entity } from 'src/types' +import { AppThunkAction, Entity, RequestKey } from 'src/types' export interface SetGroupsAction extends Action { type: 'DIRECTORY_SET_GROUPS' @@ -44,7 +43,7 @@ export const setContinuation = (continuation: string): SetContinuationAction => export const fetchGroup = (id: string): AppThunkAction => { return async dispatch => { - dispatch(startRequest(REQUEST_KEYS.FETCH_GROUP)) + dispatch(startRequest(RequestKey.FetchGroup)) try { const group = await apiFetch({ @@ -52,9 +51,9 @@ export const fetchGroup = (id: string): AppThunkAction => { }) dispatch(setEntity('group', group)) - dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP, true)) + dispatch(finishRequest(RequestKey.FetchGroup, true)) } catch (err) { - dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP, false)) + dispatch(finishRequest(RequestKey.FetchGroup, false)) throw err } } @@ -66,7 +65,7 @@ interface GroupsResponse { } export const fetchGroups = (sort?: string, continuation?: string): AppThunkAction => async dispatch => { - dispatch(startRequest(REQUEST_KEYS.FETCH_GROUPS)) + dispatch(startRequest(RequestKey.FetchGroups)) try { const response = await apiFetch({ @@ -82,9 +81,9 @@ export const fetchGroups = (sort?: string, continuation?: string): AppThunkActio dispatch(setContinuation(response.continuation)) } - dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUPS, true)) + dispatch(finishRequest(RequestKey.FetchGroups, true)) } catch (err) { - dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUPS, false)) + dispatch(finishRequest(RequestKey.FetchGroups, false)) throw err } } diff --git a/src/actions/registration.ts b/src/actions/registration.ts index b2d473e..b8a4669 100644 --- a/src/actions/registration.ts +++ b/src/actions/registration.ts @@ -7,10 +7,9 @@ import { startRequest, finishRequest } from 'src/actions/requests' import { LOCAL_STORAGE_ACCESS_TOKEN_KEY, LOCAL_STORAGE_REFRESH_TOKEN_KEY, - REQUEST_KEYS, } from 'src/constants' -import { AppThunkAction } from 'src/types' +import { AppThunkAction, NotificationType, RequestKey } from 'src/types' export interface SetStepAction extends Action { type: 'REGISTRATION_SET_STEP' @@ -30,7 +29,7 @@ export const setStep = (step: number): SetStepAction => ({ }) export const checkGroupAvailability = (name: string): AppThunkAction => async dispatch => { - dispatch(startRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY)) + dispatch(startRequest(RequestKey.FetchGroupAvailability)) try { const { id, available } = await apiFetch({ @@ -42,20 +41,20 @@ export const checkGroupAvailability = (name: string): AppThunkAction => async di }) if (available) { - dispatch(setFieldNotification('group-name', 'success', `${id} is available`)) + dispatch(setFieldNotification('group-name', NotificationType.Success, `${id} is available`)) } else { - dispatch(setFieldNotification('group-name', 'error', `${id} isn't available`)) + dispatch(setFieldNotification('group-name', NotificationType.Error, `${id} isn't available`)) } - dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY, true)) + dispatch(finishRequest(RequestKey.FetchGroupAvailability, true)) } catch (err) { - dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY, false)) + dispatch(finishRequest(RequestKey.FetchGroupAvailability, false)) throw err } } export const checkUserAvailability = (name: string): AppThunkAction => async dispatch => { - dispatch(startRequest(REQUEST_KEYS.FETCH_USER_AVAILABILITY)) + dispatch(startRequest(RequestKey.FetchUserAvailability)) try { const { id, available } = await apiFetch({ @@ -67,14 +66,14 @@ export const checkUserAvailability = (name: string): AppThunkAction => async dis }) if (available) { - dispatch(setFieldNotification('user-id', 'success', `${id} is available`)) + dispatch(setFieldNotification('user-id', NotificationType.Success, `${id} is available`)) } else { - dispatch(setFieldNotification('user-id', 'error', `${id} isn't available`)) + dispatch(setFieldNotification('user-id', NotificationType.Error, `${id} isn't available`)) } - dispatch(finishRequest(REQUEST_KEYS.FETCH_USER_AVAILABILITY, true)) + dispatch(finishRequest(RequestKey.FetchUserAvailability, true)) } catch (err) { - dispatch(finishRequest(REQUEST_KEYS.FETCH_USER_AVAILABILITY, false)) + dispatch(finishRequest(RequestKey.FetchUserAvailability, false)) throw err } } @@ -92,7 +91,7 @@ interface CreateGroupResponse { export const createGroup = (options: CreateGroupOptions): AppThunkAction => async dispatch => { const { name, registration, about } = options - dispatch(startRequest(REQUEST_KEYS.CREATE_GROUP)) + dispatch(startRequest(RequestKey.CreateGroup)) try { const { id } = await apiFetch({ @@ -105,11 +104,11 @@ export const createGroup = (options: CreateGroupOptions): AppThunkAction }, }) - dispatch(finishRequest(REQUEST_KEYS.CREATE_GROUP, true)) + dispatch(finishRequest(RequestKey.CreateGroup, true)) return id } catch (err) { - dispatch(finishRequest(REQUEST_KEYS.CREATE_GROUP, false)) + dispatch(finishRequest(RequestKey.CreateGroup, false)) throw err } } @@ -131,7 +130,7 @@ interface RegisterResponse { export const register = (options: RegisterOptions): AppThunkAction => async dispatch => { const { id, email, password, name, group } = options - dispatch(startRequest(REQUEST_KEYS.REGISTER)) + dispatch(startRequest(RequestKey.Register)) try { const response = await apiFetch({ @@ -146,14 +145,14 @@ export const register = (options: RegisterOptions): AppThunkAction => as }, }) - dispatch(finishRequest(REQUEST_KEYS.REGISTER, true)) + dispatch(finishRequest(RequestKey.Register, true)) localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY, response.access) localStorage.setItem(LOCAL_STORAGE_REFRESH_TOKEN_KEY, response.refresh) return response.id } catch (err) { - dispatch(finishRequest(REQUEST_KEYS.REGISTER, false)) + dispatch(finishRequest(RequestKey.Register, false)) throw err } } diff --git a/src/api/errors.ts b/src/api/errors.ts index 4a2ba59..f8495de 100644 --- a/src/api/errors.ts +++ b/src/api/errors.ts @@ -2,15 +2,15 @@ import { History } from 'history' import { setFieldNotification } from 'src/actions/forms' import { showNotification } from 'src/actions/notifications' -import { AppThunkDispatch, FormNotification } from 'src/types' +import { AppThunkDispatch, FormNotification, NotificationType } from 'src/types' export function handleApiError(err: HttpError, dispatch: AppThunkDispatch, history: History) { if (err instanceof ServerError) { - dispatch(showNotification('error', 'Server Error')) + dispatch(showNotification(NotificationType.Error, 'Server Error')) } if (err instanceof BadRequestError) { - dispatch(showNotification('error', `Error: ${err.message}`)) + dispatch(showNotification(NotificationType.Error, `Error: ${err.message}`)) for (const error of err.errors) { const { field, type, message } = error @@ -19,12 +19,12 @@ export function handleApiError(err: HttpError, dispatch: AppThunkDispatch, histo } if (err instanceof UnauthorizedError) { - dispatch(showNotification('error', 'You need to be logged in.')) + dispatch(showNotification(NotificationType.Error, 'You need to be logged in.')) history.push('/login') } if (err instanceof NotFoundError) { - dispatch(showNotification('error', 'Not found.')) + dispatch(showNotification(NotificationType.Error, 'Not found.')) } } diff --git a/src/api/fetch.ts b/src/api/fetch.ts index 3461665..a423380 100644 --- a/src/api/fetch.ts +++ b/src/api/fetch.ts @@ -11,7 +11,7 @@ import { LOCAL_STORAGE_REFRESH_TOKEN_KEY, } from '../constants' -import { FetchOptions, FormNotification } from '../types' +import { FetchOptions, FormNotification, NotificationType } from '../types' import getConfig from '../config' interface RefreshResponse { @@ -38,7 +38,7 @@ const mapErrorsToFormNotifications = (errors?: FormError[]): FormNotification[] return errors.map(e => ({ field: e.field, - type: 'error', + type: NotificationType.Error, message: e.message, })) } diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx index 0b8599a..f399d8e 100644 --- a/src/components/app/app.tsx +++ b/src/components/app/app.tsx @@ -17,7 +17,6 @@ import Login from '../pages/login' import Register from '../pages/register' import RegisterGroup from '../pages/register-group' import Self from '../pages/self' -import Test from '../pages/test' import './app.scss' @@ -65,7 +64,6 @@ const App: FC = ({ collapsed, fetching, fetchSelf, setChecked }) => { - diff --git a/src/components/create-group-step/create-group-step.tsx b/src/components/create-group-step/create-group-step.tsx index 5c98d72..148e50e 100644 --- a/src/components/create-group-step/create-group-step.tsx +++ b/src/components/create-group-step/create-group-step.tsx @@ -32,7 +32,7 @@ const CreateGroupStep: FC = ({