Initial commit

This commit is contained in:
Luka Radenovic 2021-09-27 12:17:33 +02:00
commit fa30c04815
117 changed files with 33513 additions and 0 deletions

View file

@ -0,0 +1,42 @@
import _ from 'lodash';
import axios from 'axios';
import { api } from './config';
import { ApiConfig } from './types';
/**
* Function which creates an API call for given api config
*/
export const createApiCall = (apiConfig: ApiConfig) => async () => {
const hostname = apiConfig.hostname || api.hostname;
const { path } = apiConfig;
const method = _.get(apiConfig, 'method', 'GET');
const contentType =
_.includes(['POST', 'PUT', 'PATCH'], method) && !apiConfig.formData
? {
'Content-Type': 'application/json',
}
: {};
const passedHeaders = _.get(apiConfig, 'headers', {});
const headers = {
...contentType,
...passedHeaders,
};
const axiosConfig = {
method,
headers,
url: `${hostname}${path}`,
timeout: 30000,
data: apiConfig.formData || apiConfig.body,
};
return axios(axiosConfig);
};
/**
* Performs an API call for given api config and returns a promise
*/
export const performApiCall = (apiConfig: ApiConfig) => {
const apiCall = createApiCall(apiConfig);
return apiCall();
};

View file

@ -0,0 +1,3 @@
export const api = {
hostname: process.env.REACT_APP_API_URL,
};

View file

@ -0,0 +1,5 @@
export * from './redux';
export { api } from './config';
export { createApiCall, performApiCall } from './apiCall';

View file

@ -0,0 +1,186 @@
import _ from 'lodash';
import urlcat from 'urlcat';
import { createApiCall } from '../apiCall';
import { StartAction, SuccessAction, FailureAction } from './types';
import { ApiConfig } from '../types';
/**
* Function which creates a thunk action for given api config and associated action types
* @param {string|Record<string, unknown>} apiConfig if string it is interprated as path in api endpoint
* @param {string[]} actionTypes action types for start, success and failure
*/
export const createApiAction = (apiConfig: ApiConfig, actionTypes: string[]) => async (dispatch: any) => {
const apiCall = createApiCall(apiConfig);
const startAction = (): StartAction => ({
type: actionTypes[0],
payload: null,
});
const successAction = (payload: any): SuccessAction => ({
type: actionTypes[1],
payload,
});
const failureAction = (error: string): FailureAction => ({
type: actionTypes[2],
payload: { error },
});
dispatch(startAction());
try {
const response = await apiCall();
let res;
const additionalData = apiConfig.additionalData || {};
if (!_.isEmpty(additionalData)) {
res = await dispatch(successAction({ ...response.data, ...additionalData }));
} else {
res = await dispatch(successAction(response.data));
}
return { ok: true, res };
} catch (e) {
const error = _.get(e, 'response.data', {
errorMessage: e.message || 'Undefined error, please try again.',
});
await dispatch(failureAction(error));
return {
ok: false,
errorMessage: error.message,
status: e?.response?.status,
};
}
};
/**
* Creates opinionated CRUD actions
*/
export function createCrudApiActions<T>(
basePath: string,
startActionType: string,
failureActionType: string,
fetchActionType: string,
addActionType: string,
updateActionType: string,
deleteActionType: string,
transformItemForApi: (data: T) => any,
pathTemplates: { load?: string; add?: string; change?: string } = {
load: '/',
add: '/',
change: '/:id',
},
): any {
const { load, add, change } = pathTemplates;
const fetchItems = (
pageNumber?: number,
pageSize?: number,
params: Record<string, unknown> = {},
template: string = load!,
) =>
createApiAction(
{
path: urlcat(basePath, template, { pageNumber, pageSize, ...params }),
method: 'GET',
},
[startActionType, fetchActionType, failureActionType],
);
const addItem = (item: T, params: Record<string, unknown> = {}) =>
createApiAction(
{
path: urlcat(basePath, add!, { ...params }),
method: 'POST',
body: transformItemForApi(item),
},
[startActionType, addActionType, failureActionType],
);
const updateItem = (item: T, params: Record<string, unknown> = {}) =>
createApiAction(
{
// @ts-ignore
path: urlcat(basePath, change!, { id: item.id, ...params }),
method: 'PUT',
body: transformItemForApi(item),
},
[startActionType, updateActionType, failureActionType],
);
const deleteItem = (itemId: number, params: Record<string, unknown> = {}) =>
createApiAction(
{
path: urlcat(basePath, change!, { id: itemId, ...params }),
method: 'DELETE',
},
[startActionType, deleteActionType, failureActionType],
);
return [fetchItems, addItem, updateItem, deleteItem];
}
/**
* Creates opinionated CRUD actions
*/
export function createCrudApiActionsWithoutPaging<T extends { id?: number | string | null }>(
basePath: string,
startActionType: string,
failureActionType: string,
fetchActionType: string,
addActionType: string,
updateActionType: string,
deleteActionType: string,
transformItemForApi: (data: T) => any,
pathTemplates: { load?: string; add?: string; change?: string } = {
load: '/',
add: '/',
change: '/:id',
},
): any {
const { load, add, change } = pathTemplates;
const fetchItems = (params: Record<string, unknown> = {}, template: string = load!) =>
createApiAction(
{
path: urlcat(basePath, template, params),
method: 'GET',
},
[startActionType, fetchActionType, failureActionType],
);
const addItem = (item: T, params: Record<string, unknown> = {}) =>
createApiAction(
{
path: urlcat(basePath, add!, { ...params }),
method: 'POST',
body: transformItemForApi(item),
},
[startActionType, addActionType, failureActionType],
);
const updateItem = (item: T, params: Record<string, unknown> = {}) =>
createApiAction(
{
path: urlcat(basePath, change!, { id: item.id, ...params }),
method: 'PUT',
body: transformItemForApi(item),
},
[startActionType, updateActionType, failureActionType],
);
const deleteItem = (itemId: number, params: Record<string, unknown> = {}) =>
createApiAction(
{
path: urlcat(basePath, change!, { id: itemId, ...params }),
method: 'DELETE',
},
[startActionType, deleteActionType, failureActionType],
);
return [fetchItems, addItem, updateItem, deleteItem];
}

View file

@ -0,0 +1,14 @@
export {
createApiReducer,
createCrudApiReducer,
createCrudApiReducerWithoutPaging,
chainReducers,
INITIAL_API_STATUS,
INITIAL_API_STATE,
} from './reducers';
export { createApiAction, createCrudApiActions, createCrudApiActionsWithoutPaging } from './actions';
export { isEmpty, isLoading, isValid, isError, getError, getErrorMessage } from './traits';
export * from './types';

View file

@ -0,0 +1,176 @@
import { StartAction, SuccessAction, FailureAction, ApiStatus, ApiState } from './types';
export const INITIAL_API_STATUS: ApiStatus = {
isEmpty: true,
isLoading: false,
isValid: false,
isError: false,
dateTime: Date.now(),
error: null,
errorMessage: null,
};
export const INITIAL_API_STATE: ApiState = {
_status: INITIAL_API_STATUS,
items: [],
};
type AllActions = StartAction | SuccessAction | FailureAction;
/**
* Use this function to create any reducer which handles api communication
* @param {Array} actionTypes Array of action types associated with fetchThunk e.g. USER_REQUEST,
* USER_SUCCESS and USER_ERROR
* @param {function} dataAdapter function which will be called to transform response body
* to format you want to use in state
* @param {function} errorAdapter function which will be called to transform error
* to format you want to use in state
* @returns {function} reducer handles api requests, saves response body as data and status
*/
export const createApiReducer =
(
actionTypes: string[],
dataAdapter: (data: any, state?: any) => any = (data: any) => data,
errorAdapter = (data: any) => data,
) =>
(state = INITIAL_API_STATE, action: AllActions) => {
const [REQUEST, SUCCESS, ERROR] = actionTypes;
switch (action.type) {
case REQUEST:
return {
...state,
_status: {
...state._status,
isLoading: true,
dateTime: Date.now(),
},
};
case SUCCESS:
return {
_status: {
isEmpty: false,
isLoading: false,
isValid: true,
isError: false,
dateTime: Date.now(),
error: null,
errorMessage: null,
},
...dataAdapter(action.payload, state),
};
case ERROR:
return {
...state,
_status: {
isEmpty: false,
isLoading: false,
isValid: false,
isError: true,
error: action.payload,
errorMessage: errorAdapter(action.payload),
},
};
default:
return state;
}
};
/**
* This is higher order reducer
* Use this if you want your state is combination of results of multiple reducers
* under the same key, e.g. you can get the same data from multiple endpoints
*/
export const chainReducers =
(initialState: any, ...args: any[]) =>
(state = initialState, action: any) =>
args.reduce((newState, reducer) => reducer(newState, action), state);
/**
* Creates an opinionated CRUD reducer
*/
export function createCrudApiReducer<T>(
startActionType: string,
failureActionType: string,
fetchActionType: string,
addActionType: string,
updateActionType: string,
deleteActionType: string,
transformItem: (data: any) => T,
keyField = 'id',
) {
const fetchReducer = createApiReducer(
[startActionType, fetchActionType, failureActionType],
(data) => ({ _meta: data.meta, items: (data.data || data).map(transformItem) }),
(data) => data,
);
const addReducer = createApiReducer(
[startActionType, addActionType, failureActionType],
(data, state) => ({ items: [...state.items, transformItem(data)] }),
(data) => data,
);
const updateReducer = createApiReducer(
[startActionType, updateActionType, failureActionType],
(data, state) => ({
items: state.items.map((i: any) => (i[keyField] === data[keyField] ? transformItem(data) : i)),
}),
(data) => data,
);
const deleteReducer = createApiReducer(
[startActionType, deleteActionType, failureActionType],
(data, state) => ({
items: state.items.filter((i: any) => i[keyField] !== data[keyField]),
}),
(data) => data,
);
return chainReducers(INITIAL_API_STATE, fetchReducer, addReducer, updateReducer, deleteReducer);
}
/**
* Creates an opinionated CRUD reducer without paging
*/
export function createCrudApiReducerWithoutPaging<T>(
startActionType: string,
failureActionType: string,
fetchActionType: string,
addActionType: string,
updateActionType: string,
deleteActionType: string,
transformItem: (data: any) => T,
keyField = 'id',
) {
const fetchReducer = createApiReducer(
[startActionType, fetchActionType, failureActionType],
(data) => ({ items: data.map(transformItem) }),
(data) => data,
);
const addReducer = createApiReducer(
[startActionType, addActionType, failureActionType],
(data, state) => ({ items: [...state.items, transformItem(data)] }),
(data) => data,
);
const updateReducer = createApiReducer(
[startActionType, updateActionType, failureActionType],
(data, state) => ({
items: state.items.map((i: any) => (i[keyField] === data[keyField] ? transformItem(data) : i)),
}),
(data) => data,
);
const deleteReducer = createApiReducer(
[startActionType, deleteActionType, failureActionType],
(data, state) => ({
items: state.items.filter((i: any) => i[keyField] !== data[keyField]),
}),
(data) => data,
);
return chainReducers(INITIAL_API_STATE, fetchReducer, addReducer, updateReducer, deleteReducer);
}

View file

@ -0,0 +1,29 @@
import _ from 'lodash';
export const isEmpty = (data: any): boolean => {
return _.get(data, '_status.isEmpty', true);
};
export const isLoading = (data: any): boolean => {
return _.get(data, '_status.isLoading', false);
};
export const isValid = (data: any): boolean => {
return _.get(data, '_status.isValid', false);
};
export const isError = (data: any): boolean => {
return _.get(data, '_status.isError', false);
};
export const getError = (data: any): any => {
return _.get(data, '_status.error', null);
};
export const getErrorMessage = (data: any): string | null => {
return _.get(data, '_status.errorMessage', null);
};
export const getTotalPages = (data: any): number | undefined => {
return _.get(data, '_meta.totalPages', undefined);
};

View file

@ -0,0 +1,29 @@
export interface StartAction {
type: string;
payload: any;
}
export interface SuccessAction {
type: string;
payload: any;
}
export interface FailureAction {
type: string;
payload: { error: string };
}
export interface ApiStatus {
isEmpty: boolean;
isLoading: boolean;
isValid: boolean;
isError: boolean;
dateTime: number;
error: any;
errorMessage: string | null;
}
export interface ApiState<T = any> {
_status: ApiStatus;
items: T[];
}

29
src/services/api/types.ts Normal file
View file

@ -0,0 +1,29 @@
import { Method } from 'axios';
export interface ApiConfig {
hostname?: string;
path: string;
method?: Method;
headers?: any;
formData?: any;
body?: any;
additionalData?: any;
}
export interface SimpleSnackBarConfig {
serviceName: any;
}
export interface SnackBarConfigWithMessages {
getMessage: {
success(response: any): string;
error(error: any): string;
};
}
export type SnackBarConfig = SnackBarConfigWithMessages | SimpleSnackBarConfig;
export enum SortDirection {
Ascending = 'asc',
Descending = 'desc',
}

42
src/services/auth/api.ts Normal file
View file

@ -0,0 +1,42 @@
import { performApiCall } from 'src/services/api';
export const sendPasswordResetLink = async (email: string): Promise<boolean> => {
try {
await performApiCall({
path: `/auth/forgot-password`,
method: 'POST',
body: { email },
});
return true;
} catch {
return false;
}
};
export const resetPassword = async (email: string, code: string, password: string): Promise<boolean> => {
try {
await performApiCall({
path: `/auth/reset-password`,
method: 'POST',
body: { email, code, password },
});
return true;
} catch {
return false;
}
};
export const verifyEmail = async (email: string, activationCode: string): Promise<boolean> => {
try {
await performApiCall({
path: `/auth/activation`,
method: 'POST',
body: { email, activationCode },
});
return true;
} catch {
return false;
}
};

View file

@ -0,0 +1 @@
export { useAuth } from './use-auth';

View file

@ -0,0 +1,38 @@
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getAuth, signIn, signOut, register as apiRegister, refreshUser as apiRefreshUser } from '../redux';
export function useAuth() {
const dispatch = useDispatch();
const auth = useSelector(getAuth);
const logIn = useCallback(
(email: string, password: string) => {
return dispatch(signIn(email, password));
},
[dispatch],
);
const register = useCallback(
(email: string, password: string, firstName: string, lastName: string) => {
return dispatch(apiRegister(email, password, firstName, lastName));
},
[dispatch],
);
const logOut = useCallback(() => {
return dispatch(signOut());
}, [dispatch]);
const refreshUser = useCallback(() => {
return dispatch(apiRefreshUser());
}, [dispatch]);
return {
auth,
logIn,
logOut,
register,
refreshUser,
};
}

View file

@ -0,0 +1,3 @@
export { useAuth } from './hooks';
export { getAuth, reducer, signIn, signOut, register, AuthActionTypes, getIsAuthLoading } from './redux';
export * from './types';

View file

@ -0,0 +1,56 @@
import { createApiAction } from 'src/services/api';
import { SuccessAction } from 'src/services/api/redux/types';
export enum AuthActionTypes {
SIGN_IN_START = 'auth/sign_in_start',
SIGN_IN_SUCCESS = 'auth/sign_in_success',
SIGN_IN_FAILURE = 'auth/sign_in_failure',
SIGN_OUT = 'auth/SIGN_OUT',
REGISTRATION_START = 'auth/registration_start',
REGISTRATION_FAILURE = 'auth/registration_failure',
}
const signOutAction = (): SuccessAction => ({
type: AuthActionTypes.SIGN_OUT,
payload: null,
});
export const signIn = (email: string, password: string) =>
createApiAction(
{
path: '/login',
method: 'POST',
body: { username: email, password },
},
[AuthActionTypes.SIGN_IN_START, AuthActionTypes.SIGN_IN_SUCCESS, AuthActionTypes.SIGN_IN_FAILURE],
);
export const refreshUser = () =>
createApiAction(
{
path: '/dashboard',
method: 'GET',
},
[AuthActionTypes.SIGN_IN_START, AuthActionTypes.SIGN_IN_SUCCESS, AuthActionTypes.SIGN_IN_FAILURE],
);
export function signOut() {
return (dispatch: any) => {
dispatch(signOutAction());
};
}
export const register = (email: string, password: string, firstName: string, lastName: string) =>
createApiAction(
{
path: '/auth/register',
method: 'POST',
body: {
email,
firstName,
lastName,
password,
},
},
[AuthActionTypes.REGISTRATION_START, AuthActionTypes.SIGN_IN_SUCCESS, AuthActionTypes.REGISTRATION_FAILURE],
);

View file

@ -0,0 +1,4 @@
export { signIn, signOut, register, refreshUser, AuthActionTypes } from './actions';
export { default as reducer } from './reducers';
export { getAuth, getIsAuthLoading } from './selectors';
export * from './types';

View file

@ -0,0 +1,23 @@
import { createApiReducer, chainReducers, INITIAL_API_STATUS, INITIAL_API_STATE } from 'src/services/api';
import { AuthState } from './types';
import { AuthActionTypes } from './actions';
const initialState: AuthState = {
token: null,
_status: INITIAL_API_STATUS,
};
const auth = createApiReducer(
[AuthActionTypes.SIGN_IN_START, AuthActionTypes.SIGN_IN_SUCCESS, AuthActionTypes.SIGN_IN_FAILURE],
(data) => ({ token: data.access_token }),
(data) => data.error.message,
);
const signOut = createApiReducer(
['', AuthActionTypes.SIGN_OUT, ''],
() => INITIAL_API_STATE,
() => INITIAL_API_STATE,
);
export default chainReducers(initialState, auth, signOut);

View file

@ -0,0 +1,9 @@
import { State } from 'src/redux';
import { isLoading } from 'src/services/api';
export const getAuth = (state: State) => state.auth;
export const getIsAuthLoading = (state: State) => isLoading(getAuth(state));
export const getToken = (state: State) => state.auth.token;

View file

@ -0,0 +1,7 @@
import { ApiStatus } from 'src/services/api/redux/types';
import { Auth } from '../types';
export interface AuthState extends Auth {
_status: ApiStatus;
}

View file

@ -0,0 +1,3 @@
export interface Auth {
token: string | null;
}

11
src/services/users/api.ts Normal file
View file

@ -0,0 +1,11 @@
import { performApiCall } from 'src/services/api';
import { User } from './types';
import { transformUser } from './transformations';
export const fetchMemberDetails = async (memberId: number): Promise<User> => {
const res = await performApiCall({
path: `/members/${memberId}`,
});
return transformUser(res.data);
};

View file

@ -0,0 +1 @@
export { useUsers } from './use-users';

View file

@ -0,0 +1,16 @@
import { useDispatch, useSelector } from 'react-redux';
import { getUsers, fetchUsers } from '../redux';
export function useUsers() {
const dispatch = useDispatch();
const users = useSelector(getUsers);
function loadUsers() {
return dispatch(fetchUsers());
}
return {
users,
loadUsers,
};
}

View file

@ -0,0 +1,7 @@
export * from './types';
export { reducer, fetchCurrentUser, getCurrentUser } from './redux';
export { useUsers } from './hooks';
export { fetchMemberDetails } from './api';

View file

@ -0,0 +1,64 @@
import { createApiAction, createCrudApiActions } from 'src/services/api';
import { transformUserForApi } from '../transformations';
import { User } from '../types';
import { CurrentUserUpdateAPI } from './types';
export enum UserActionTypes {
CHANGE_CURRENT_USER_START = 'users/fetch_current_user_start',
CHANGE_CURRENT_USER_FAILURE = 'users/fetch_current_user_failure',
FETCH_CURRENT_USER_SUCCESS = 'users/fetch_current_user_success',
UPDATE_CURRENT_USER_SUCCESS = 'users/update_current_user_success',
CHANGE_USERS_START = 'users/change_users_start',
CHANGE_USERS_FAILURE = 'users/change_users_failure',
FETCH_USERS_SUCCESS = 'users/fetch_users_success',
ADD_ACCOUNT_USER_SUCCESS = 'users/add_account_user_success',
UPDATE_ACCOUNT_USER_SUCCESS = 'users/update_account_user_success',
DELETE_ACCOUNT_USER_SUCCESS = 'users/delete_account_user_success',
CHANGE_USER_ROLES_START = 'users/change_user_roles_start',
CHANGE_USER_ROLES_FAILURE = 'users/change_user_roles_failure',
FETCH_USER_ROLES_SUCCESS = 'users/fetch_user_roles_success',
ADD_USER_ROLE_SUCCESS = 'users/add_user_role_success',
UPDATE_USER_ROLE_SUCCESS = 'users/update_user_role_success',
DELETE_USER_ROLE_SUCCESS = 'users/delete_user_role_success',
}
export const fetchCurrentUser = () =>
createApiAction(
{
path: '/users/me',
method: 'GET',
},
[
UserActionTypes.CHANGE_CURRENT_USER_START,
UserActionTypes.FETCH_CURRENT_USER_SUCCESS,
UserActionTypes.CHANGE_CURRENT_USER_FAILURE,
],
);
export const updateCurrentUser = (data: CurrentUserUpdateAPI) =>
createApiAction(
{
path: '/users/me',
method: 'PATCH',
body: data,
},
[
UserActionTypes.CHANGE_CURRENT_USER_START,
UserActionTypes.UPDATE_CURRENT_USER_SUCCESS,
UserActionTypes.CHANGE_CURRENT_USER_FAILURE,
],
);
export const [fetchUsers, addUser, updateUser, deleteUser] = createCrudApiActions<User>(
'/users',
UserActionTypes.CHANGE_USERS_START,
UserActionTypes.CHANGE_USERS_FAILURE,
UserActionTypes.FETCH_USERS_SUCCESS,
UserActionTypes.ADD_ACCOUNT_USER_SUCCESS,
UserActionTypes.UPDATE_ACCOUNT_USER_SUCCESS,
UserActionTypes.DELETE_ACCOUNT_USER_SUCCESS,
transformUserForApi,
);

View file

@ -0,0 +1,4 @@
export * from './actions';
export { default as reducer } from './reducers';
export { getCurrentUser, getUsers } from './selectors';
export * from './types';

View file

@ -0,0 +1,61 @@
import { combineReducers } from 'redux';
import { createApiReducer, chainReducers, INITIAL_API_STATE, createCrudApiReducer } from 'src/services/api';
import { AuthActionTypes } from 'src/services/auth/redux/actions';
import { transformUser } from '../transformations';
import { User } from '../types';
import { UserActionTypes } from './actions';
const signInReducer = createApiReducer(
[AuthActionTypes.SIGN_IN_START, AuthActionTypes.SIGN_IN_SUCCESS, AuthActionTypes.SIGN_IN_FAILURE],
transformUser,
(data) => data,
);
const fetchCurrentUserReducer = createApiReducer(
[
UserActionTypes.CHANGE_CURRENT_USER_START,
UserActionTypes.FETCH_CURRENT_USER_SUCCESS,
UserActionTypes.CHANGE_CURRENT_USER_FAILURE,
],
transformUser,
(data) => data,
);
const updateCurrentUserReducer = createApiReducer(
[
UserActionTypes.CHANGE_CURRENT_USER_START,
UserActionTypes.UPDATE_CURRENT_USER_SUCCESS,
UserActionTypes.CHANGE_CURRENT_USER_FAILURE,
],
transformUser,
(data) => data,
);
const signOutReducer = createApiReducer(
['', AuthActionTypes.SIGN_OUT, ''],
() => INITIAL_API_STATE,
() => INITIAL_API_STATE,
);
const usersReducer = createCrudApiReducer<User>(
UserActionTypes.CHANGE_USERS_START,
UserActionTypes.CHANGE_USERS_FAILURE,
UserActionTypes.FETCH_USERS_SUCCESS,
UserActionTypes.ADD_ACCOUNT_USER_SUCCESS,
UserActionTypes.UPDATE_ACCOUNT_USER_SUCCESS,
UserActionTypes.DELETE_ACCOUNT_USER_SUCCESS,
transformUser,
);
export default combineReducers({
currentUser: chainReducers(
INITIAL_API_STATE,
signInReducer,
fetchCurrentUserReducer,
updateCurrentUserReducer,
signOutReducer,
),
users: usersReducer,
});

View file

@ -0,0 +1,4 @@
import { State } from 'src/redux';
export const getCurrentUser = (state: State) => state.users.currentUser;
export const getUsers = (state: State) => state.users.users;

View file

@ -0,0 +1,22 @@
import { ApiState, ApiStatus } from 'src/services/api/redux';
import { User } from '../types';
export interface CurrentUserState extends User {
_status: ApiStatus;
}
export interface UsersState {
currentUser: CurrentUserState;
users: ApiState<User>;
}
export interface CurrentUserUpdateAPI {
phoneNumber?: string;
email?: string;
language?: string;
country?: string;
firstName?: string;
lastName?: string;
password?: string;
}

View file

@ -0,0 +1,23 @@
import _ from 'lodash';
import { FormUser, User, UserApiRequest } from './types';
export const transformUserForApi = (user: FormUser): UserApiRequest => ({
id: user.id,
email: user.email,
name: user.name,
status: user.status,
last_login: user.last_login,
});
export const transformUser = (response: any): User => {
const userResponse = _.get(response, 'user', response);
return {
id: userResponse.id,
email: userResponse.email,
name: userResponse.name,
last_login: userResponse.last_login,
status: userResponse.status,
};
};

View file

@ -0,0 +1,26 @@
export interface User {
id: number | null;
email: string;
name: string;
last_login: string;
status: string;
}
export interface FormUser extends User {
password?: string;
confirmPassword?: string;
}
export interface UserRole {
id?: number;
name: string;
isAdministrator: boolean;
}
export interface UserApiRequest {
id: number | null;
email: string;
name: string;
last_login: string;
status: string;
}