From 8f8c3b6c9c76b5ddade0ea5bed9cbed8a3afdc83 Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Fri, 22 Mar 2019 13:26:58 +0800 Subject: [PATCH] Refactor action definitions to type less --- packages/client/src/actions/ActionTypes.ts | 28 --- packages/client/src/actions/GetAction.ts | 6 + packages/client/src/actions/IAction.ts | 6 +- packages/client/src/actions/IAsyncAction.ts | 12 ++ packages/client/src/actions/IErrorAction.ts | 4 - packages/client/src/actions/IPendingAction.ts | 6 + .../client/src/actions/IRejectedAction.ts | 6 + .../client/src/actions/IResolvedAction.ts | 6 + packages/client/src/actions/UnionType.ts | 13 -- packages/client/src/actions/index.ts | 8 +- packages/client/src/login/LoginActions.ts | 55 +++--- packages/client/src/login/LoginReducer.ts | 14 +- packages/client/src/login/withForm.tsx | 2 +- .../src/middleware/PromiseMiddleware.test.ts | 12 +- .../src/middleware/PromiseMiddleware.ts | 24 ++- packages/client/src/team/TeamActions.ts | 175 +++++------------- packages/client/src/team/TeamList.tsx | 16 +- packages/client/src/team/TeamManager.tsx | 20 +- packages/client/src/team/TeamReducer.ts | 30 ++- packages/client/src/team/TeamUserList.tsx | 19 +- 20 files changed, 186 insertions(+), 276 deletions(-) delete mode 100644 packages/client/src/actions/ActionTypes.ts create mode 100644 packages/client/src/actions/GetAction.ts create mode 100644 packages/client/src/actions/IAsyncAction.ts delete mode 100644 packages/client/src/actions/IErrorAction.ts create mode 100644 packages/client/src/actions/IPendingAction.ts create mode 100644 packages/client/src/actions/IRejectedAction.ts create mode 100644 packages/client/src/actions/IResolvedAction.ts delete mode 100644 packages/client/src/actions/UnionType.ts diff --git a/packages/client/src/actions/ActionTypes.ts b/packages/client/src/actions/ActionTypes.ts deleted file mode 100644 index 6992c8f..0000000 --- a/packages/client/src/actions/ActionTypes.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Get the type of promise -// https://www.typescriptlang.org/docs/handbook/advanced-types.html -// section: Type inference in conditional types -type Unpacked = T extends Promise ? U : T -import {IAction} from './IAction' -import {IErrorAction} from './IErrorAction' - -// Also from TypeScript handbook: -// https://www.typescriptlang.org/docs/handbook/advanced-types.html -type FunctionProperties = { - [K in keyof T]: - T[K] extends (...args: any[]) => IAction - ? { - payload: Unpacked['payload']>, - type: Unpacked['type']>, - } - : T[K] extends (...args: any[]) => IErrorAction - ? { - error: Error, - type: Unpacked['type']>, - } - : never -} - -// https://stackoverflow.com/questions/48305190/ -// Is there an automatic way to create a discriminated union for all interfaces -// in a namespace? -export type ActionTypes = FunctionProperties[keyof FunctionProperties] diff --git a/packages/client/src/actions/GetAction.ts b/packages/client/src/actions/GetAction.ts new file mode 100644 index 0000000..df704c0 --- /dev/null +++ b/packages/client/src/actions/GetAction.ts @@ -0,0 +1,6 @@ +import {IAction} from './IAction' + +export type GetAction = + MyTypes extends IAction + ? MyTypes + : never diff --git a/packages/client/src/actions/IAction.ts b/packages/client/src/actions/IAction.ts index 15d05c5..1c2ce5f 100644 --- a/packages/client/src/actions/IAction.ts +++ b/packages/client/src/actions/IAction.ts @@ -1,7 +1,3 @@ -// Maybe this won't be necessary after this is merged: -// https://github.com/Microsoft/TypeScript/pull/29478 - -export interface IAction { - payload: Promise | T, +export interface IAction { type: ActionType } diff --git a/packages/client/src/actions/IAsyncAction.ts b/packages/client/src/actions/IAsyncAction.ts new file mode 100644 index 0000000..d64b03a --- /dev/null +++ b/packages/client/src/actions/IAsyncAction.ts @@ -0,0 +1,12 @@ +import {IPendingAction} from './IPendingAction' +import {IResolvedAction} from './IResolvedAction' +import {IRejectedAction} from './IRejectedAction' + +export type IAsyncAction = + IPendingAction + | IResolvedAction + | IRejectedAction diff --git a/packages/client/src/actions/IErrorAction.ts b/packages/client/src/actions/IErrorAction.ts deleted file mode 100644 index 3a1b792..0000000 --- a/packages/client/src/actions/IErrorAction.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IErrorAction { - error: Error, - type: ActionType -} diff --git a/packages/client/src/actions/IPendingAction.ts b/packages/client/src/actions/IPendingAction.ts new file mode 100644 index 0000000..1712788 --- /dev/null +++ b/packages/client/src/actions/IPendingAction.ts @@ -0,0 +1,6 @@ +import {IAction} from './IAction' + +export interface IPendingAction extends + IAction { + payload: Promise +} diff --git a/packages/client/src/actions/IRejectedAction.ts b/packages/client/src/actions/IRejectedAction.ts new file mode 100644 index 0000000..4088cdc --- /dev/null +++ b/packages/client/src/actions/IRejectedAction.ts @@ -0,0 +1,6 @@ +import {IAction} from './IAction' + +export interface IRejectedAction extends + IAction { + error: Error +} diff --git a/packages/client/src/actions/IResolvedAction.ts b/packages/client/src/actions/IResolvedAction.ts new file mode 100644 index 0000000..afb045e --- /dev/null +++ b/packages/client/src/actions/IResolvedAction.ts @@ -0,0 +1,6 @@ +import {IAction} from './IAction' + +export interface IResolvedAction extends + IAction { + payload: T +} diff --git a/packages/client/src/actions/UnionType.ts b/packages/client/src/actions/UnionType.ts deleted file mode 100644 index 45e6fe9..0000000 --- a/packages/client/src/actions/UnionType.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Get the type of promise -// https://www.typescriptlang.org/docs/handbook/advanced-types.html -// section: Type inference in conditional types -type Unpacked = T extends Promise ? U : T - -type FunctionProperties = { - [K in keyof T]: T[K] extends (...args: any[]) => any ? { - payload: Unpacked['payload']>, - type: Unpacked['type']>, - }: never -} - -export type UnionType = FunctionProperties[keyof FunctionProperties] diff --git a/packages/client/src/actions/index.ts b/packages/client/src/actions/index.ts index ee8b3bf..e50eafd 100644 --- a/packages/client/src/actions/index.ts +++ b/packages/client/src/actions/index.ts @@ -1,4 +1,6 @@ -export * from './ActionTypes' +export * from './GetAction' export * from './IAction' -export * from './IErrorAction' -export * from './UnionType' +export * from './IAsyncAction' +export * from './IPendingAction' +export * from './IRejectedAction' +export * from './IResolvedAction' diff --git a/packages/client/src/login/LoginActions.ts b/packages/client/src/login/LoginActions.ts index b5261f2..c1db71c 100644 --- a/packages/client/src/login/LoginActions.ts +++ b/packages/client/src/login/LoginActions.ts @@ -1,4 +1,4 @@ -import {IAction, IErrorAction, ActionTypes} from '../actions' +import {GetAction, IAsyncAction, IResolvedAction} from '../actions' import {IAPIDef, ICredentials, INewUser, IUser} from '@rondo/common' import {IHTTPClient} from '../http/IHTTPClient' @@ -18,56 +18,51 @@ export enum LoginActionKeys { LOGIN_REDIRECT_SET = 'LOGIN_REDIRECT_SET', } +export type LoginActionType = + IAsyncAction + | IAsyncAction + | IAsyncAction + | IResolvedAction<{redirectTo: string}, 'LOGIN_REDIRECT_SET'> + +type Action = GetAction + export class LoginActions { constructor(protected readonly http: IHTTPClient) {} - logIn = (credentials: ICredentials) - : IAction => { + logIn = (credentials: ICredentials): Action<'LOGIN_PENDING'> => { return { payload: this.http.post('/auth/login', credentials), - type: LoginActionKeys.LOGIN, + type: 'LOGIN_PENDING', } } - logInError = (error: Error) - : IErrorAction => { - return { - error, - type: LoginActionKeys.LOGIN_REJECTED, - } - } - - logOut = (): IAction => { + logOut = (): Action<'LOGIN_LOGOUT_PENDING'> => { return { payload: this.http.get('/auth/logout'), - type: LoginActionKeys.LOGIN_LOG_OUT, + type: 'LOGIN_LOGOUT_PENDING', } } - register = (profile: INewUser): - IAction => { + register = (profile: INewUser): Action<'LOGIN_REGISTER_PENDING'> => { return { payload: this.http.post('/auth/register', profile), - type: LoginActionKeys.LOGIN_REGISTER, + type: 'LOGIN_REGISTER_PENDING', } } - registerError = (error: Error) - : IErrorAction => { - return { - error, - type: LoginActionKeys.LOGIN_REGISTER_REJECTED, - } - } - - setRedirectTo = (redirectTo: string) - : IAction<{redirectTo: string}, LoginActionKeys.LOGIN_REDIRECT_SET> => { + setRedirectTo = (redirectTo: string): Action<'LOGIN_REDIRECT_SET'> => { return { payload: {redirectTo}, type: LoginActionKeys.LOGIN_REDIRECT_SET, } } } - -// This makes it very easy to write reducer code. -export type LoginActionType = ActionTypes diff --git a/packages/client/src/login/LoginReducer.ts b/packages/client/src/login/LoginReducer.ts index db62a3c..8711402 100644 --- a/packages/client/src/login/LoginReducer.ts +++ b/packages/client/src/login/LoginReducer.ts @@ -1,5 +1,5 @@ import {IUser} from '@rondo/common' -import {LoginActionKeys, LoginActionType} from './LoginActions' +import {LoginActionType} from './LoginActions' export interface ILoginState { readonly error?: string @@ -18,17 +18,17 @@ export function Login( action: LoginActionType, ): ILoginState { switch (action.type) { - case LoginActionKeys.LOGIN: + case 'LOGIN_RESOLVED': return {...state, user: action.payload, error: ''} - case LoginActionKeys.LOGIN_LOG_OUT: + case 'LOGIN_LOGOUT_RESOLVED': return {...state, user: undefined} - case LoginActionKeys.LOGIN_REJECTED: + case 'LOGIN_REJECTED': return {...state, error: action.error.message} - case LoginActionKeys.LOGIN_REGISTER: + case 'LOGIN_REGISTER_RESOLVED': return {...state, user: action.payload, error: ''} - case LoginActionKeys.LOGIN_REGISTER_REJECTED: + case 'LOGIN_REGISTER_REJECTED': return {...state, error: action.error.message} - case LoginActionKeys.LOGIN_REDIRECT_SET: + case 'LOGIN_REDIRECT_SET': return {...state, redirectTo: action.payload.redirectTo} default: return state diff --git a/packages/client/src/login/withForm.tsx b/packages/client/src/login/withForm.tsx index badaeec..f1bfeb0 100644 --- a/packages/client/src/login/withForm.tsx +++ b/packages/client/src/login/withForm.tsx @@ -10,7 +10,7 @@ export interface IComponentProps { } export interface IFormHOCProps { - onSubmit: (props: Data) => IAction + onSubmit: (props: Data) => IAction // TODO figure out what would happen if the underlying child component // would have the same required property as the HOC, like onSuccess? onSuccess?: () => void diff --git a/packages/client/src/middleware/PromiseMiddleware.test.ts b/packages/client/src/middleware/PromiseMiddleware.test.ts index fd5ac88..51ee5dd 100644 --- a/packages/client/src/middleware/PromiseMiddleware.test.ts +++ b/packages/client/src/middleware/PromiseMiddleware.test.ts @@ -26,21 +26,21 @@ describe('PromiseMiddleware', () => { expect(store.getState().slice(1)).toEqual([action]) }) - it('dispatches pending and fulfilled action', async () => { + it('dispatches pending and resolved action', async () => { const value = 123 const type = 'TEST' const action = { payload: Promise.resolve(value), - type, + type: `${type}_PENDING`, } const result = store.dispatch(action) expect(result).toBe(action) await result.payload expect(store.getState().slice(1)).toEqual([{ - type: `${type}_PENDING`, + ...action, }, { payload: value, - type, + type: type + '_RESOLVED', }]) }) @@ -49,14 +49,14 @@ describe('PromiseMiddleware', () => { const type = 'TEST' const action = { payload: Promise.reject(error), - type, + type: `${type}_PENDING`, } const result = store.dispatch(action) expect(result).toBe(action) const err = await getError(result.payload) expect(err).toBe(error) expect(store.getState().slice(1)).toEqual([{ - type: `${type}_PENDING`, + ...action, }, { error, type: `${type}_REJECTED`, diff --git a/packages/client/src/middleware/PromiseMiddleware.ts b/packages/client/src/middleware/PromiseMiddleware.ts index 79e9bcd..1c3e80e 100644 --- a/packages/client/src/middleware/PromiseMiddleware.ts +++ b/packages/client/src/middleware/PromiseMiddleware.ts @@ -11,7 +11,7 @@ function isPromise(value: any): value is Promise { * * If `action.payload` is a `Promise`, it will be handled by this class. It * differs from other promise middlewares for redux because by default it does - * not add an extension to action dispatched after a promise is fulfilled. This + * not add an extension to action dispatched after a promise is resolved. This * makes it easier to infer types from the API endpoints so they can be used in * both Action creators and Reducers. * @@ -20,36 +20,42 @@ function isPromise(value: any): value is Promise { * const middleware = applyMiddleware(new PromiseMiddleware().handle) */ export class PromiseMiddleware { + protected regexp: RegExp + constructor( readonly pendingExtension = '_PENDING', - readonly fulfilledExtension = '', + readonly resolvedExtension = '_RESOLVED', readonly rejectedExtension = '_REJECTED', ) { assert( - this.pendingExtension !== this.fulfilledExtension && - this.fulfilledExtension !== this.rejectedExtension && + this.pendingExtension !== this.resolvedExtension && + this.resolvedExtension !== this.rejectedExtension && this.pendingExtension !== this.rejectedExtension, - 'Pending, fulfilled and rejected extensions must be unique') + 'Pending, resolved and rejected extensions must be unique') + + this.regexp = new RegExp(pendingExtension + '$') } handle: Middleware = store => next => (action: AnyAction) => { const {payload, type} = action + // Propagate this action. Only attach listeners to the promise. + next(action) if (!isPromise(payload)) { - next(action) return } - store.dispatch({type: type + this.pendingExtension}) + + const strippedType = type.replace(this.regexp, '') payload .then(result => { store.dispatch({ payload: result, - type, + type: strippedType + this.resolvedExtension, }) }) .catch(err => { store.dispatch({ error: err, - type: type + this.rejectedExtension, + type: strippedType + this.rejectedExtension, }) }) diff --git a/packages/client/src/team/TeamActions.ts b/packages/client/src/team/TeamActions.ts index 3494fca..1877d39 100644 --- a/packages/client/src/team/TeamActions.ts +++ b/packages/client/src/team/TeamActions.ts @@ -1,105 +1,73 @@ import {IAPIDef} from '@rondo/common' -import {IAction, IErrorAction, ActionTypes} from '../actions' +import {GetAction, IAsyncAction} from '../actions' import {IHTTPClient} from '../http/IHTTPClient' import {ITeam, IUser, IUserInTeam} from '@rondo/common' -export enum TeamActionKeys { - TEAMS = 'TEAMS', - TEAMS_PENDING = 'TEAMS_PENDING', - TEAMS_REJECTED = 'TEAMS_REJECTED', +export type TeamActionType = + IAsyncAction + | IAsyncAction + | IAsyncAction + | IAsyncAction<{}, + 'TEAM_REMOVE_PENDING', + 'TEAM_REMOVE_RESOLVED', + 'TEAM_REMOVE_REJECTED'> + | IAsyncAction + | IAsyncAction<{userId: number, teamId: number}, + 'TEAM_USER_REMOVE_PENDING', + 'TEAM_USER_REMOVE_RESOLVED', + 'TEAM_USER_REMOVE_REJECTED'> + | IAsyncAction<{teamId: number, usersInTeam: IUserInTeam[]}, + 'TEAM_USERS_PENDING', + 'TEAM_USERS_RESOLVED', + 'TEAM_USERS_REJECTED'> + | IAsyncAction - TEAM_CREATE = 'TEAM_CREATE', - TEAM_CREATE_PENDING = 'TEAM_CREATE_PENDING', - TEAM_CREATE_REJECTED = 'TEAM_CREATE_REJECTED', - - TEAM_UPDATE = 'TEAM_UPDATE', - TEAM_UPDATE_PENDING = 'TEAM_UPDATE_PENDING', - TEAM_UPDATE_REJECTED = 'TEAM_UPDATE_REJECTED', - - TEAM_REMOVE = 'TEAM_REMOVE', - TEAM_REMOVE_PENDING = 'TEAM_REMOVE_PENDING', - TEAM_REMOVE_REJECTED = 'TEAM_REMOVE_REJECTED', - - TEAM_USER_ADD = 'TEAM_USER_ADD', - TEAM_USER_ADD_PENDING = 'TEAM_USER_ADD_PENDING', - TEAM_USER_ADD_REJECTED = 'TEAM_USER_ADD_REJECTED', - - TEAM_USER_REMOVE = 'TEAM_USER_REMOVE', - TEAM_USER_REMOVE_PENDING = 'TEAM_USER_REMOVE_PENDING', - TEAM_USER_REMOVE_REJECTED = 'TEAM_USER_REMOVE_REJECTED', - - TEAM_USERS = 'TEAM_USERS', - TEAM_USERS_PENDING = 'TEAM_USERS_PENDING', - TEAM_USERS_REJECTED = 'TEAM_USERS_REJECTED', - - TEAM_USER_FIND = 'TEAM_USER_FIND', - TEAM_USER_FIND_PENDING = 'TEAM_USER_FIND_PENDING', - TEAM_USER_FIND_REJECTED = 'TEAM_USER_FIND_REJECTED', -} +type Action = GetAction export class TeamActions { constructor(protected readonly http: IHTTPClient) {} - fetchMyTeams = (): IAction => { + fetchMyTeams = (): Action<'TEAMS_PENDING'> => { return { payload: this.http.get('/my/teams'), - type: TeamActionKeys.TEAMS, + type: 'TEAMS_PENDING', } } - fetchMyTeamsError = (error: Error) - : IErrorAction => { - return { - error, - type: TeamActionKeys.TEAMS_REJECTED, - } - } - - createTeam = (team: {name: string}) - : IAction => { + createTeam = (team: {name: string}): Action<'TEAM_CREATE_PENDING'> => { return { payload: this.http.post('/teams', team), - type: TeamActionKeys.TEAM_CREATE, - } - } - - createTeamError = (error: Error) - : IErrorAction => { - return { - error, - type: TeamActionKeys.TEAM_CREATE_REJECTED, + type: 'TEAM_CREATE_PENDING', } } updateTeam = ({id, name}: {id: number, name: string}) - : IAction => { + : Action<'TEAM_UPDATE_PENDING'> => { return { payload: this.http.put('/teams/:id', {name}, {id}), - type: TeamActionKeys.TEAM_UPDATE, + type: 'TEAM_UPDATE_PENDING', } } - updateTeamError = (error: Error) - : IErrorAction => { - return { - error, - type: TeamActionKeys.TEAM_UPDATE_REJECTED, - } - } - - removeTeam = ({id}: {id: number}) - : IAction<{}, TeamActionKeys.TEAM_REMOVE> => { + removeTeam = ({id}: {id: number}): Action<'TEAM_REMOVE_PENDING'> => { return { payload: this.http.delete('/teams/:id', {}, {id}), - type: TeamActionKeys.TEAM_REMOVE, - } - } - - removeTeamError = (error: Error) - : IErrorAction => { - return { - error, - type: TeamActionKeys.TEAM_REMOVE_REJECTED, + type: 'TEAM_REMOVE_PENDING', } } @@ -109,21 +77,13 @@ export class TeamActions { teamId: number, roleId: number, }) - : IAction { + : Action<'TEAM_USER_ADD_PENDING'> { return { payload: this.http.post('/teams/:teamId/users/:userId', {}, { userId, teamId, }), - type: TeamActionKeys.TEAM_USER_ADD, - } - } - - addUserError = (error: Error) - : IErrorAction => { - return { - error, - type: TeamActionKeys.TEAM_USER_ADD_REJECTED, + type: 'TEAM_USER_ADD_PENDING', } } @@ -132,66 +92,33 @@ export class TeamActions { userId: number, teamId: number, }) - : IAction<{ - userId: number, - teamId: number, - }, TeamActionKeys.TEAM_USER_REMOVE> => { + : Action<'TEAM_USER_REMOVE_PENDING'> => { return { payload: this.http.delete('/teams/:teamId/users/:userId', {}, { userId, teamId, }), - type: TeamActionKeys.TEAM_USER_REMOVE, - } - } - - removeUserError = (error: Error) - : IErrorAction => { - return { - error, - type: TeamActionKeys.TEAM_USER_REMOVE_REJECTED, + type: 'TEAM_USER_REMOVE_PENDING', } } fetchUsersInTeam = ({teamId}: {teamId: number}) - : IAction<{ - teamId: number, - usersInTeam: IUserInTeam[] - }, TeamActionKeys.TEAM_USERS> => { + : Action<'TEAM_USERS_PENDING'> => { return { payload: this.http.get('/teams/:teamId/users', { teamId, }) .then(usersInTeam => ({teamId, usersInTeam})), - type: TeamActionKeys.TEAM_USERS, + type: 'TEAM_USERS_PENDING', } } - fetchUsersInTeamError = (error: Error) - : IErrorAction => { - return { - error, - type: TeamActionKeys.TEAM_USERS_REJECTED, - } - } - - findUserByEmail = (email: string) - : IAction => { + findUserByEmail = (email: string): Action<'TEAM_USER_FIND_PENDING'> => { return { payload: this.http.get('/users/emails/:email', { email, }), - type: TeamActionKeys.TEAM_USER_FIND, - } - } - - findUserByEmailError = (error: Error) - : IErrorAction => { - return { - error, - type: TeamActionKeys.TEAM_USER_FIND_REJECTED, + type: 'TEAM_USER_FIND_PENDING', } } } - -export type TeamActionType = ActionTypes diff --git a/packages/client/src/team/TeamList.tsx b/packages/client/src/team/TeamList.tsx index bb9ea21..804423a 100644 --- a/packages/client/src/team/TeamList.tsx +++ b/packages/client/src/team/TeamList.tsx @@ -1,26 +1,26 @@ import React from 'react' -import {IAction} from '../actions' import {ITeam, ReadonlyRecord} from '@rondo/common' +import {TeamActions} from './TeamActions' export interface ITeamListProps { teamsById: ReadonlyRecord, teamIds: ReadonlyArray, - onAddTeam: (params: {name: string}) => IAction - onRemoveTeam: (params: {id: number}) => IAction - onUpdateTeam: (params: {id: number, name: string}) => IAction + onAddTeam: TeamActions['createTeam'] + onRemoveTeam: TeamActions['removeTeam'] + onUpdateTeam: TeamActions['updateTeam'] editTeamId: number } export interface ITeamProps { team: ITeam editTeamId: number // TODO handle edits via react-router params - onRemoveTeam: (params: {id: number}) => IAction - onUpdateTeam: (params: {id: number, name: string}) => IAction + onRemoveTeam: TeamActions['removeTeam'] + onUpdateTeam: TeamActions['updateTeam'] } export interface IAddTeamProps { - onAddTeam: (params: {name: string}) => IAction - onUpdateTeam: (params: {id: number, name: string}) => IAction + onAddTeam: TeamActions['createTeam'] + onUpdateTeam: TeamActions['updateTeam'] team?: ITeam } diff --git a/packages/client/src/team/TeamManager.tsx b/packages/client/src/team/TeamManager.tsx index 83d9a8d..79af972 100644 --- a/packages/client/src/team/TeamManager.tsx +++ b/packages/client/src/team/TeamManager.tsx @@ -1,19 +1,19 @@ import React from 'react' -import {IAction} from '../actions' -import {ITeam, IUser, IUserInTeam, ReadonlyRecord} from '@rondo/common' +import {ITeam, IUserInTeam, ReadonlyRecord} from '@rondo/common' import {TeamList} from './TeamList' import {TeamUserList} from './TeamUserList' +import {TeamActions} from './TeamActions' export interface ITeamManagerProps { - createTeam: (params: {name: string}) => IAction - updateTeam: (params: {id: number, name: string}) => IAction - removeTeam: (params: {id: number}) => IAction + createTeam: TeamActions['createTeam'] + updateTeam: TeamActions['updateTeam'] + removeTeam: TeamActions['removeTeam'] - addUser: (params: {userId: number, teamId: number, roleId: number}) => IAction - removeUser: (params: {userId: number, teamId: number}) => IAction - fetchMyTeams: () => IAction - fetchUsersInTeam: (params: {teamId: number}) => IAction - findUserByEmail: (email: string) => IAction + addUser: TeamActions['addUser'] + removeUser: TeamActions['removeUser'] + fetchMyTeams: TeamActions['fetchMyTeams'] + fetchUsersInTeam: TeamActions['fetchUsersInTeam'] + findUserByEmail: TeamActions['findUserByEmail'] teamsById: ReadonlyRecord teamIds: ReadonlyArray diff --git a/packages/client/src/team/TeamReducer.ts b/packages/client/src/team/TeamReducer.ts index 5430c06..66379c8 100644 --- a/packages/client/src/team/TeamReducer.ts +++ b/packages/client/src/team/TeamReducer.ts @@ -1,5 +1,6 @@ import {ITeam, IUserInTeam, ReadonlyRecord, indexBy} from '@rondo/common' -import {TeamActionKeys, TeamActionType} from './TeamActions' +import {TeamActionType} from './TeamActions' +import {GetAction} from '../actions' export interface ITeamState { readonly error: string @@ -23,10 +24,7 @@ const defaultState: ITeamState = { function removeUser( state: ITeamState, - action: { - payload: {userId: number, teamId: number}, - type: TeamActionKeys.TEAM_USER_REMOVE, - }, + action: GetAction, ) { const {payload} = action @@ -54,14 +52,14 @@ function getUserKey(userInTeam: {userId: number, teamId: number}) { export function Team(state = defaultState, action: TeamActionType): ITeamState { switch (action.type) { - case TeamActionKeys.TEAMS: + case 'TEAMS_RESOLVED': return { ...state, teamIds: action.payload.map(team => team.id), teamsById: indexBy(action.payload, 'id'), } - case TeamActionKeys.TEAM_CREATE: - case TeamActionKeys.TEAM_UPDATE: + case 'TEAM_CREATE_RESOLVED': + case 'TEAM_UPDATE_RESOLVED': return { ...state, teamIds: state.teamIds.indexOf(action.payload.id) >= 0 @@ -73,7 +71,7 @@ export function Team(state = defaultState, action: TeamActionType): ITeamState { }, } return state - case TeamActionKeys.TEAM_USER_ADD: + case 'TEAM_USER_ADD_RESOLVED': return { ...state, userKeysByTeamId: { @@ -88,9 +86,9 @@ export function Team(state = defaultState, action: TeamActionType): ITeamState { [getUserKey(action.payload)]: action.payload, }, } - case TeamActionKeys.TEAM_USER_REMOVE: + case 'TEAM_USER_REMOVE_RESOLVED': return removeUser(state, action) - case TeamActionKeys.TEAM_USERS: + case 'TEAM_USERS_RESOLVED': const usersByKey = action.payload.usersInTeam .reduce((obj, userInTeam) => { obj[getUserKey(userInTeam)] = userInTeam @@ -109,11 +107,11 @@ export function Team(state = defaultState, action: TeamActionType): ITeamState { ...usersByKey, }, } - case TeamActionKeys.TEAM_CREATE_REJECTED: - case TeamActionKeys.TEAM_UPDATE_REJECTED: - case TeamActionKeys.TEAM_USER_ADD_REJECTED: - case TeamActionKeys.TEAM_USER_REMOVE_REJECTED: - case TeamActionKeys.TEAM_USERS_REJECTED: + case 'TEAM_CREATE_REJECTED': + case 'TEAM_UPDATE_REJECTED': + case 'TEAM_USER_ADD_REJECTED': + case 'TEAM_USER_REMOVE_REJECTED': + case 'TEAM_USERS_REJECTED': return { ...state, error: action.error.message, diff --git a/packages/client/src/team/TeamUserList.tsx b/packages/client/src/team/TeamUserList.tsx index 6bad0b5..e4417b0 100644 --- a/packages/client/src/team/TeamUserList.tsx +++ b/packages/client/src/team/TeamUserList.tsx @@ -1,17 +1,16 @@ import React from 'react' import {IUser, IUserInTeam, ReadonlyRecord} from '@rondo/common' -import {IAction} from '../actions' +import {TeamActions} from './TeamActions' const EMPTY_ARRAY: ReadonlyArray = [] export interface ITeamUsersProps { // fetchMyTeams: () => void, - fetchUsersInTeam: (params: {teamId: number}) => IAction - findUserByEmail: (email: string) => IAction + fetchUsersInTeam: TeamActions['fetchUsersInTeam'] + findUserByEmail: TeamActions['findUserByEmail'] - onAddUser: (params: {userId: number, teamId: number, roleId: number}) - => IAction - onRemoveUser: (params: {userId: number, teamId: number}) => IAction + onAddUser: TeamActions['addUser'] + onRemoveUser: TeamActions['removeUser'] teamId: number userKeysByTeamId: ReadonlyRecord> @@ -24,12 +23,8 @@ export interface ITeamUserProps { } export interface IAddUserProps { - onAddUser: (params: { - userId: number, - teamId: number, - roleId: number, - }) => IAction - onSearchUser: (email: string) => IAction + onAddUser: TeamActions['addUser'] + onSearchUser: TeamActions['findUserByEmail'] teamId: number }