From 2f46f9ba34d04777ba693faf08b7e2a4992c637d Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 09:42:20 -0500 Subject: [PATCH 01/11] getUser service function, placeholder htApi error interceptor --- client/src/services/htApi.ts | 58 +++++++++++++++--------- client/src/store/userSlice/user.slice.ts | 10 ++-- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/client/src/services/htApi.ts b/client/src/services/htApi.ts index f2d323d8b..7d1a9707b 100644 --- a/client/src/services/htApi.ts +++ b/client/src/services/htApi.ts @@ -1,5 +1,5 @@ /**htApi.ts - service for making requests to the Haztrak API*/ -import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios'; +import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import { HaztrakSite } from 'components/HaztrakSite'; import { addNotification, rootStore } from 'store'; import { @@ -7,6 +7,7 @@ import { RcrainfoProfile, RcrainfoProfileSite, } from 'store/profileSlice/profile.slice'; +import { HaztrakUser } from 'store/userSlice/user.slice'; interface HaztrakOrgResponse { id: string; @@ -25,27 +26,6 @@ interface HaztrakProfileResponse { interface RcrainfoProfileResponse extends RcrainfoProfile> {} -export const HtApi = { - /** Retrieve the user's Haztrak profile from the Haztrak API*/ - getUserProfile: async () => { - const response: AxiosResponse = await htApi.get('/profile'); - return response.data; - }, - /** Retrieve the user's RCRAInfo profile from the Haztrak API*/ - getUserRcrainfoProfile: async (username: string) => { - const response: AxiosResponse = await htApi.get( - `/rcra/profile/${username}` - ); - return response.data; - }, - /** Launch task to pull user's site/module permissions (RCRAInfo profile) from RCRAInfo*/ - syncRcrainfoProfile: async () => { - const response: AxiosResponse<{ taskId: string }> = await htApi.get(`rcra/profile/sync`); - console.log(response); - return response.data; - }, -}; - /** An Axios instance with an interceptor to automatically apply authentication headers*/ export const htApi = axios.create({ baseURL: `${import.meta.env.VITE_HT_API_URL}/api`, @@ -69,3 +49,37 @@ htApi.interceptors.request.use( return Promise.reject(error); } ); + +/**interceptor to add errors to redux state*/ +htApi.interceptors.response.use( + (response: AxiosResponse) => response, + (error) => { + return Promise.reject(error); + } +); + +export const HtApi = { + /** Retrieve the user's Haztrak profile from the Haztrak API*/ + getUserProfile: async () => { + const response: AxiosResponse = await htApi.get('/profile'); + return response.data; + }, + /** Retrieve the user's RCRAInfo profile from the Haztrak API*/ + getUserRcrainfoProfile: async (username: string) => { + const response: AxiosResponse = await htApi.get( + `/rcra/profile/${username}` + ); + return response.data; + }, + /** Launch task to pull user's site/module permissions (RCRAInfo profile) from RCRAInfo*/ + syncRcrainfoProfile: async () => { + const response: AxiosResponse<{ taskId: string }> = await htApi.get(`rcra/profile/sync`); + return response.data; + }, + + /** Retrieve Haztrak user server*/ + getUser: async () => { + const response: AxiosResponse = await htApi.get('/user'); + return response.data; + }, +}; diff --git a/client/src/store/userSlice/user.slice.ts b/client/src/store/userSlice/user.slice.ts index 249c005de..25ef4380f 100644 --- a/client/src/store/userSlice/user.slice.ts +++ b/client/src/store/userSlice/user.slice.ts @@ -1,6 +1,7 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import axios from 'axios'; import { htApi } from 'services'; +import { HtApi } from 'services/htApi'; import { RootState } from 'store/rootStore'; export interface HaztrakUser { @@ -46,11 +47,10 @@ export const login = createAsyncThunk( ); export const getHaztrakUser = createAsyncThunk('user/getHaztrakUser', async (arg, thunkAPI) => { - const response = await htApi.get('/user'); - if (response.status >= 200 && response.status < 300) { - return response.data as HaztrakUser; - } else { - return thunkAPI.rejectWithValue(response.data); + try { + return await HtApi.getUser(); + } catch (err) { + return thunkAPI.rejectWithValue(err); } }); From 041d12969fdec23102c075d04d9b311f189a4190 Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 10:00:26 -0500 Subject: [PATCH 02/11] return Promise> from our API layer --- .../components/RcraProfile/RcraProfile.tsx | 2 +- client/src/services/htApi.ts | 32 ++++++++----------- .../src/store/profileSlice/profile.slice.ts | 4 +-- client/src/store/userSlice/user.slice.ts | 3 +- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/client/src/components/RcraProfile/RcraProfile.tsx b/client/src/components/RcraProfile/RcraProfile.tsx index 4783ebe65..96837278a 100644 --- a/client/src/components/RcraProfile/RcraProfile.tsx +++ b/client/src/components/RcraProfile/RcraProfile.tsx @@ -187,7 +187,7 @@ export function RcraProfile({ profile }: ProfileViewProps) { variant="primary" onClick={() => { HtApi.syncRcrainfoProfile() - .then((data) => { + .then(({ data }) => { dispatch( addNotification({ uniqueId: data.taskId, diff --git a/client/src/services/htApi.ts b/client/src/services/htApi.ts index 7d1a9707b..abd94a1f7 100644 --- a/client/src/services/htApi.ts +++ b/client/src/services/htApi.ts @@ -1,7 +1,7 @@ /**htApi.ts - service for making requests to the Haztrak API*/ -import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; +import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import { HaztrakSite } from 'components/HaztrakSite'; -import { addNotification, rootStore } from 'store'; +import { rootStore } from 'store'; import { HaztrakModulePermissions, RcrainfoProfile, @@ -59,27 +59,21 @@ htApi.interceptors.response.use( ); export const HtApi = { - /** Retrieve the user's Haztrak profile from the Haztrak API*/ - getUserProfile: async () => { - const response: AxiosResponse = await htApi.get('/profile'); - return response.data; + /** Fetch the user's Haztrak profile from the Haztrak API*/ + getUserProfile: async (): Promise> => { + return await htApi.get('/profile'); }, - /** Retrieve the user's RCRAInfo profile from the Haztrak API*/ - getUserRcrainfoProfile: async (username: string) => { - const response: AxiosResponse = await htApi.get( - `/rcra/profile/${username}` - ); - return response.data; + /** Fetch the user's RCRAInfo profile from the Haztrak API*/ + getRcrainfoProfile: async (username: string): Promise> => { + return await htApi.get(`/rcra/profile/${username}`); }, /** Launch task to pull user's site/module permissions (RCRAInfo profile) from RCRAInfo*/ - syncRcrainfoProfile: async () => { - const response: AxiosResponse<{ taskId: string }> = await htApi.get(`rcra/profile/sync`); - return response.data; + syncRcrainfoProfile: async (): Promise> => { + return await htApi.get(`rcra/profile/sync`); }, - /** Retrieve Haztrak user server*/ - getUser: async () => { - const response: AxiosResponse = await htApi.get('/user'); - return response.data; + /** Fetch Haztrak user from server*/ + getUser: async (): Promise> => { + return await htApi.get('/user'); }, }; diff --git a/client/src/store/profileSlice/profile.slice.ts b/client/src/store/profileSlice/profile.slice.ts index 662e91968..8ff637916 100644 --- a/client/src/store/profileSlice/profile.slice.ts +++ b/client/src/store/profileSlice/profile.slice.ts @@ -74,7 +74,7 @@ const initialState: ProfileState = { /**Retrieves a user's profile from the server.*/ export const getHaztrakProfile = createAsyncThunk('profile/getHaztrakProfile', async () => { - const data = await HtApi.getUserProfile(); + const { data } = await HtApi.getUserProfile(); const sites = data.sites.reduce((obj, site) => { return { ...obj, @@ -100,7 +100,7 @@ export const getRcraProfile = createAsyncThunk( if (!username) { throw new Error('User is not logged in'); } - const data = await HtApi.getUserRcrainfoProfile(username); + const { data } = await HtApi.getRcrainfoProfile(username); const { rcraSites, ...rest } = data; return { rcrainfoProfile: { diff --git a/client/src/store/userSlice/user.slice.ts b/client/src/store/userSlice/user.slice.ts index 25ef4380f..1167a0ea5 100644 --- a/client/src/store/userSlice/user.slice.ts +++ b/client/src/store/userSlice/user.slice.ts @@ -48,7 +48,8 @@ export const login = createAsyncThunk( export const getHaztrakUser = createAsyncThunk('user/getHaztrakUser', async (arg, thunkAPI) => { try { - return await HtApi.getUser(); + const { data } = await HtApi.getUser(); + return data; } catch (err) { return thunkAPI.rejectWithValue(err); } From 4682487dda608feae4c4ce463cd2cb3958347ca4 Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 10:20:21 -0500 Subject: [PATCH 03/11] updateRcrainfoProfile api layer function --- client/src/components/RcraProfile/RcraProfile.tsx | 3 +-- client/src/services/htApi.ts | 14 ++++++++++++++ client/src/store/profileSlice/profile.slice.ts | 2 +- client/src/store/userSlice/user.slice.ts | 4 +--- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/client/src/components/RcraProfile/RcraProfile.tsx b/client/src/components/RcraProfile/RcraProfile.tsx index 96837278a..b9866c319 100644 --- a/client/src/components/RcraProfile/RcraProfile.tsx +++ b/client/src/components/RcraProfile/RcraProfile.tsx @@ -48,8 +48,7 @@ export function RcraProfile({ profile }: ProfileViewProps) { const onSubmit = (data: RcraProfileForm) => { setProfileLoading(!profileLoading); setEditable(!editable); - htApi - .put(`/rcra/profile/${profile.user}`, data) + HtApi.updateRcrainfoProfile({ username: profile.user, data: data }) .then((r) => { dispatch(updateProfile(r.data)); }) diff --git a/client/src/services/htApi.ts b/client/src/services/htApi.ts index abd94a1f7..2d0a01190 100644 --- a/client/src/services/htApi.ts +++ b/client/src/services/htApi.ts @@ -8,6 +8,7 @@ import { RcrainfoProfileSite, } from 'store/profileSlice/profile.slice'; import { HaztrakUser } from 'store/userSlice/user.slice'; +import { an } from 'vitest/dist/reporters-5f784f42'; interface HaztrakOrgResponse { id: string; @@ -63,10 +64,23 @@ export const HtApi = { getUserProfile: async (): Promise> => { return await htApi.get('/profile'); }, + /** Fetch the user's RCRAInfo profile from the Haztrak API*/ getRcrainfoProfile: async (username: string): Promise> => { return await htApi.get(`/rcra/profile/${username}`); }, + + /** Update user's RCRAInfo Profile information such username, api ID & key*/ + updateRcrainfoProfile: async ({ + username, + data, + }: { + username: string; + data: any; + }): Promise> => { + return await htApi.put(`/rcra/profile/${username}`, data); + }, + /** Launch task to pull user's site/module permissions (RCRAInfo profile) from RCRAInfo*/ syncRcrainfoProfile: async (): Promise> => { return await htApi.get(`rcra/profile/sync`); diff --git a/client/src/store/profileSlice/profile.slice.ts b/client/src/store/profileSlice/profile.slice.ts index 8ff637916..748e56761 100644 --- a/client/src/store/profileSlice/profile.slice.ts +++ b/client/src/store/profileSlice/profile.slice.ts @@ -43,7 +43,7 @@ export interface RcrainfoProfileState extends RcrainfoProfile> {} export interface RcrainfoProfile { - user?: string; + user: string; rcraAPIID?: string; rcraUsername?: string; rcraAPIKey?: string; diff --git a/client/src/store/userSlice/user.slice.ts b/client/src/store/userSlice/user.slice.ts index 1167a0ea5..6627ed950 100644 --- a/client/src/store/userSlice/user.slice.ts +++ b/client/src/store/userSlice/user.slice.ts @@ -1,6 +1,5 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import axios from 'axios'; -import { htApi } from 'services'; import { HtApi } from 'services/htApi'; import { RootState } from 'store/rootStore'; @@ -60,11 +59,10 @@ export const getHaztrakUser = createAsyncThunk('user/getHaztrakUser', async (arg * * @description on logout, we want to strip all information * from browser storage and redux store's state - * ToDo send logout request to server * @param {Object} user UserState * @return {Object} UserState */ -function logout(user: UserState) { +function logout(user: UserState): object { localStorage.removeItem('user'); localStorage.removeItem('token'); return { ...initialState, user: undefined, token: undefined } as UserState; From bab2735dd780b320f3fb77672194831546a92f5b Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 12:00:32 -0500 Subject: [PATCH 04/11] move and rename HtApi api layer object to smaller/discrete api layer objects like UserApi to be more descriptive --- .../components/RcraProfile/RcraProfile.tsx | 7 +-- client/src/services/UserApi.ts | 59 +++++++++++++++++++ client/src/services/htApi.ts | 58 ------------------ client/src/services/index.ts | 3 +- .../src/store/profileSlice/profile.slice.ts | 6 +- client/src/store/userSlice/index.ts | 2 + client/src/store/userSlice/user.slice.ts | 4 +- 7 files changed, 71 insertions(+), 68 deletions(-) create mode 100644 client/src/services/UserApi.ts diff --git a/client/src/components/RcraProfile/RcraProfile.tsx b/client/src/components/RcraProfile/RcraProfile.tsx index b9866c319..b3e4896f9 100644 --- a/client/src/components/RcraProfile/RcraProfile.tsx +++ b/client/src/components/RcraProfile/RcraProfile.tsx @@ -5,8 +5,7 @@ import React, { useState } from 'react'; import { Button, Col, Container, Form, Row, Table } from 'react-bootstrap'; import { useForm } from 'react-hook-form'; import { Link } from 'react-router-dom'; -import { htApi } from 'services'; -import { HtApi } from 'services/htApi'; +import { htApi, UserApi } from 'services'; import { addNotification, useAppDispatch, useAppSelector } from 'store'; import { getRcraProfile, updateProfile } from 'store/profileSlice'; import { RcrainfoProfileState, selectHaztrakSites } from 'store/profileSlice/profile.slice'; @@ -48,7 +47,7 @@ export function RcraProfile({ profile }: ProfileViewProps) { const onSubmit = (data: RcraProfileForm) => { setProfileLoading(!profileLoading); setEditable(!editable); - HtApi.updateRcrainfoProfile({ username: profile.user, data: data }) + UserApi.updateRcrainfoProfile({ username: profile.user, data: data }) .then((r) => { dispatch(updateProfile(r.data)); }) @@ -185,7 +184,7 @@ export function RcraProfile({ profile }: ProfileViewProps) { className="mx-2" variant="primary" onClick={() => { - HtApi.syncRcrainfoProfile() + UserApi.syncRcrainfoProfile() .then(({ data }) => { dispatch( addNotification({ diff --git a/client/src/services/UserApi.ts b/client/src/services/UserApi.ts new file mode 100644 index 000000000..86fa94889 --- /dev/null +++ b/client/src/services/UserApi.ts @@ -0,0 +1,59 @@ +import { AxiosResponse } from 'axios'; +import { HaztrakSite } from 'components/HaztrakSite'; +import { htApi } from 'services/htApi'; +import { + HaztrakModulePermissions, + RcrainfoProfile, + RcrainfoProfileSite, +} from 'store/profileSlice/profile.slice'; +import { HaztrakUser } from 'store/userSlice'; + +interface HaztrakOrgResponse { + id: string; + name: string; + rcrainfoIntegrated: boolean; +} + +interface HaztrakProfileResponse { + user: string; + sites: Array<{ + site: HaztrakSite; + eManifest: HaztrakModulePermissions; + }>; + org?: HaztrakOrgResponse; +} + +interface RcrainfoProfileResponse extends RcrainfoProfile> {} + +export const UserApi = { + /** Fetch the user's Haztrak profile from the Haztrak API*/ + getUserProfile: async (): Promise> => { + return await htApi.get('/profile'); + }, + + /** Fetch the user's RCRAInfo profile from the Haztrak API*/ + getRcrainfoProfile: async (username: string): Promise> => { + return await htApi.get(`/rcra/profile/${username}`); + }, + + /** Update user's RCRAInfo Profile information such username, api ID & key*/ + updateRcrainfoProfile: async ({ + username, + data, + }: { + username: string; + data: any; + }): Promise> => { + return await htApi.put(`/rcra/profile/${username}`, data); + }, + + /** Launch task to pull user's site/module permissions (RCRAInfo profile) from RCRAInfo*/ + syncRcrainfoProfile: async (): Promise> => { + return await htApi.get(`rcra/profile/sync`); + }, + + /** Fetch Haztrak user from server*/ + getUser: async (): Promise> => { + return await htApi.get('/user'); + }, +}; diff --git a/client/src/services/htApi.ts b/client/src/services/htApi.ts index 2d0a01190..1db82bd1c 100644 --- a/client/src/services/htApi.ts +++ b/client/src/services/htApi.ts @@ -1,31 +1,6 @@ /**htApi.ts - service for making requests to the Haztrak API*/ import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios'; -import { HaztrakSite } from 'components/HaztrakSite'; import { rootStore } from 'store'; -import { - HaztrakModulePermissions, - RcrainfoProfile, - RcrainfoProfileSite, -} from 'store/profileSlice/profile.slice'; -import { HaztrakUser } from 'store/userSlice/user.slice'; -import { an } from 'vitest/dist/reporters-5f784f42'; - -interface HaztrakOrgResponse { - id: string; - name: string; - rcrainfoIntegrated: boolean; -} - -interface HaztrakProfileResponse { - user: string; - sites: Array<{ - site: HaztrakSite; - eManifest: HaztrakModulePermissions; - }>; - org?: HaztrakOrgResponse; -} - -interface RcrainfoProfileResponse extends RcrainfoProfile> {} /** An Axios instance with an interceptor to automatically apply authentication headers*/ export const htApi = axios.create({ @@ -58,36 +33,3 @@ htApi.interceptors.response.use( return Promise.reject(error); } ); - -export const HtApi = { - /** Fetch the user's Haztrak profile from the Haztrak API*/ - getUserProfile: async (): Promise> => { - return await htApi.get('/profile'); - }, - - /** Fetch the user's RCRAInfo profile from the Haztrak API*/ - getRcrainfoProfile: async (username: string): Promise> => { - return await htApi.get(`/rcra/profile/${username}`); - }, - - /** Update user's RCRAInfo Profile information such username, api ID & key*/ - updateRcrainfoProfile: async ({ - username, - data, - }: { - username: string; - data: any; - }): Promise> => { - return await htApi.put(`/rcra/profile/${username}`, data); - }, - - /** Launch task to pull user's site/module permissions (RCRAInfo profile) from RCRAInfo*/ - syncRcrainfoProfile: async (): Promise> => { - return await htApi.get(`rcra/profile/sync`); - }, - - /** Fetch Haztrak user from server*/ - getUser: async (): Promise> => { - return await htApi.get('/user'); - }, -}; diff --git a/client/src/services/index.ts b/client/src/services/index.ts index ae2f446e6..9f7f7fb86 100644 --- a/client/src/services/index.ts +++ b/client/src/services/index.ts @@ -1,3 +1,4 @@ import { htApi } from 'services/htApi'; +import { UserApi } from 'services/UserApi'; -export { htApi }; +export { htApi, UserApi }; diff --git a/client/src/store/profileSlice/profile.slice.ts b/client/src/store/profileSlice/profile.slice.ts index 748e56761..a74324d03 100644 --- a/client/src/store/profileSlice/profile.slice.ts +++ b/client/src/store/profileSlice/profile.slice.ts @@ -4,7 +4,7 @@ */ import { createAsyncThunk, createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { HaztrakSite } from 'components/HaztrakSite'; -import { HtApi } from 'services/htApi'; +import { UserApi } from 'services'; import { RootState } from 'store'; /**The user's RCRAInfo account data stored in the Redux store*/ @@ -74,7 +74,7 @@ const initialState: ProfileState = { /**Retrieves a user's profile from the server.*/ export const getHaztrakProfile = createAsyncThunk('profile/getHaztrakProfile', async () => { - const { data } = await HtApi.getUserProfile(); + const { data } = await UserApi.getUserProfile(); const sites = data.sites.reduce((obj, site) => { return { ...obj, @@ -100,7 +100,7 @@ export const getRcraProfile = createAsyncThunk( if (!username) { throw new Error('User is not logged in'); } - const { data } = await HtApi.getRcrainfoProfile(username); + const { data } = await UserApi.getRcrainfoProfile(username); const { rcraSites, ...rest } = data; return { rcrainfoProfile: { diff --git a/client/src/store/userSlice/index.ts b/client/src/store/userSlice/index.ts index b7d291a23..b60125f90 100644 --- a/client/src/store/userSlice/index.ts +++ b/client/src/store/userSlice/index.ts @@ -1,4 +1,6 @@ import userReducer, { login, selectUserName, selectUser, selectUserState } from './user.slice'; +import { HaztrakUser } from './user.slice'; export default userReducer; export { login, selectUserName, selectUser, selectUserState }; +export type { HaztrakUser }; diff --git a/client/src/store/userSlice/user.slice.ts b/client/src/store/userSlice/user.slice.ts index 6627ed950..c02152e65 100644 --- a/client/src/store/userSlice/user.slice.ts +++ b/client/src/store/userSlice/user.slice.ts @@ -1,6 +1,6 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import axios from 'axios'; -import { HtApi } from 'services/htApi'; +import { UserApi } from 'services'; import { RootState } from 'store/rootStore'; export interface HaztrakUser { @@ -47,7 +47,7 @@ export const login = createAsyncThunk( export const getHaztrakUser = createAsyncThunk('user/getHaztrakUser', async (arg, thunkAPI) => { try { - const { data } = await HtApi.getUser(); + const { data } = await UserApi.getUser(); return data; } catch (err) { return thunkAPI.rejectWithValue(err); From d4c57209dc6a9ea393b215f8d3b522b49bc21dcc Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 12:35:01 -0500 Subject: [PATCH 05/11] updateHaztrakUser api layer method --- .../components/UserProfile/UserProfile.tsx | 14 ++++++++------ client/src/services/UserApi.ts | 15 ++++++++++----- client/src/store/userSlice/user.slice.ts | 19 +++++++++++++++---- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/client/src/components/UserProfile/UserProfile.tsx b/client/src/components/UserProfile/UserProfile.tsx index c8509c745..93aa4f6fd 100644 --- a/client/src/components/UserProfile/UserProfile.tsx +++ b/client/src/components/UserProfile/UserProfile.tsx @@ -7,7 +7,7 @@ import React, { createRef, useState } from 'react'; import { Button, Col, Container, Form, Row, Table } from 'react-bootstrap'; import { useForm } from 'react-hook-form'; import { Link } from 'react-router-dom'; -import { htApi } from 'services'; +import { htApi, UserApi } from 'services'; import { useAppDispatch } from 'store'; import { ProfileState } from 'store/profileSlice/profile.slice'; import { HaztrakUser, updateUserProfile } from 'store/userSlice/user.slice'; @@ -24,6 +24,8 @@ const haztrakUserForm = z.object({ email: z.string().email('Not a valid email address').optional(), }); +type HaztrakUserForm = z.infer; + export function UserProfile({ user, profile }: UserProfileProps) { const [editable, setEditable] = useState(false); const fileRef = createRef(); @@ -36,12 +38,12 @@ export function UserProfile({ user, profile }: UserProfileProps) { formState: { errors }, } = useForm({ values: user, resolver: zodResolver(haztrakUserForm) }); - const onSubmit = (data: any) => { + const onSubmit = (data: HaztrakUserForm) => { setEditable(!editable); - htApi - .put('/user', data) - .then((r) => { - dispatch(updateUserProfile(r.data)); + UserApi.updateUser(data) + .then((response) => { + // @ts-ignore + dispatch(updateUserProfile(response.data)); }) .catch((r) => console.error(r)); }; diff --git a/client/src/services/UserApi.ts b/client/src/services/UserApi.ts index 86fa94889..a153e6471 100644 --- a/client/src/services/UserApi.ts +++ b/client/src/services/UserApi.ts @@ -31,6 +31,16 @@ export const UserApi = { return await htApi.get('/profile'); }, + /** Fetch Haztrak user from server*/ + getUser: async (): Promise> => { + return await htApi.get('/user'); + }, + + /** Fetch Haztrak user from server*/ + updateUser: async (data: Partial): Promise> => { + return await htApi.put('/user', data); + }, + /** Fetch the user's RCRAInfo profile from the Haztrak API*/ getRcrainfoProfile: async (username: string): Promise> => { return await htApi.get(`/rcra/profile/${username}`); @@ -51,9 +61,4 @@ export const UserApi = { syncRcrainfoProfile: async (): Promise> => { return await htApi.get(`rcra/profile/sync`); }, - - /** Fetch Haztrak user from server*/ - getUser: async (): Promise> => { - return await htApi.get('/user'); - }, }; diff --git a/client/src/store/userSlice/user.slice.ts b/client/src/store/userSlice/user.slice.ts index c02152e65..bb92acab5 100644 --- a/client/src/store/userSlice/user.slice.ts +++ b/client/src/store/userSlice/user.slice.ts @@ -1,4 +1,4 @@ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import { Action, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import axios from 'axios'; import { UserApi } from 'services'; import { RootState } from 'store/rootStore'; @@ -45,6 +45,7 @@ export const login = createAsyncThunk( } ); +/** Fetch a Haztrak User's information and store in global state */ export const getHaztrakUser = createAsyncThunk('user/getHaztrakUser', async (arg, thunkAPI) => { try { const { data } = await UserApi.getUser(); @@ -54,6 +55,19 @@ export const getHaztrakUser = createAsyncThunk('user/getHaztrakUser', async (arg } }); +/** Submit a request to update a Haztrak User's information */ +export const updateHaztrakUser = createAsyncThunk( + 'user/updateHaztrakUser', + async (data: Partial, thunkAPI) => { + try { + const response = await UserApi.updateUser(data); + return response.data; + } catch (err) { + return thunkAPI.rejectWithValue(err); + } + } +); + /** * User logout Redux reducer Function * @@ -95,9 +109,6 @@ const userSlice = createSlice({ }) .addCase(login.fulfilled, (state, action) => { const authResponse = action.payload; - // Todo: currently, we store username and jwt token in local storage so - // the user stays logged in between page refreshes. This is a known vulnerability to be - // fixed in the future. For now, it's a development convenience. localStorage.setItem('user', JSON.stringify(authResponse.user?.username)); localStorage.setItem('token', JSON.stringify(authResponse.token)); return { From 4203153d7b89904b76033a49a9326d9313cd81f9 Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 12:58:06 -0500 Subject: [PATCH 06/11] add loading indicators to UserProfile and RcraProfile components --- client/src/components/RcraProfile/RcraProfile.tsx | 12 ++++++++++-- client/src/components/UserProfile/UserProfile.tsx | 12 ++++++++++-- client/src/features/profile/Profile.tsx | 3 ++- client/src/store/profileSlice/profile.slice.ts | 2 +- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/client/src/components/RcraProfile/RcraProfile.tsx b/client/src/components/RcraProfile/RcraProfile.tsx index b3e4896f9..a4aa473d9 100644 --- a/client/src/components/RcraProfile/RcraProfile.tsx +++ b/client/src/components/RcraProfile/RcraProfile.tsx @@ -1,6 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { RcraApiUserBtn } from 'components/buttons'; -import { HtForm } from 'components/Ht'; +import { HtForm, HtSpinner } from 'components/Ht'; import React, { useState } from 'react'; import { Button, Col, Container, Form, Row, Table } from 'react-bootstrap'; import { useForm } from 'react-hook-form'; @@ -27,7 +27,7 @@ export function RcraProfile({ profile }: ProfileViewProps) { const [editable, setEditable] = useState(false); const [profileLoading, setProfileLoading] = useState(false); const userSites = useAppSelector(selectHaztrakSites); - const { rcraSites, loading, ...formValues } = profile; + const { rcraSites, isLoading, ...formValues } = profile; const dispatch = useAppDispatch(); const { @@ -56,6 +56,14 @@ export function RcraProfile({ profile }: ProfileViewProps) { .catch((r) => console.error(r)); }; + if (profile.isLoading) { + return ( + + + + ); + } + return ( <> diff --git a/client/src/components/UserProfile/UserProfile.tsx b/client/src/components/UserProfile/UserProfile.tsx index 93aa4f6fd..26e44d881 100644 --- a/client/src/components/UserProfile/UserProfile.tsx +++ b/client/src/components/UserProfile/UserProfile.tsx @@ -1,7 +1,7 @@ import { faUser } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { zodResolver } from '@hookform/resolvers/zod'; -import { HtForm } from 'components/Ht'; +import { HtForm, HtSpinner } from 'components/Ht'; import { SitePermissions } from 'components/UserProfile/SitePermissions'; import React, { createRef, useState } from 'react'; import { Button, Col, Container, Form, Row, Table } from 'react-bootstrap'; @@ -15,7 +15,7 @@ import { z } from 'zod'; interface UserProfileProps { user: HaztrakUser; - profile?: ProfileState; + profile: ProfileState; } const haztrakUserForm = z.object({ @@ -48,6 +48,14 @@ export function UserProfile({ user, profile }: UserProfileProps) { .catch((r) => console.error(r)); }; + if (user?.isLoading || profile?.loading) { + return ( + + + + ); + } + return ( diff --git a/client/src/features/profile/Profile.tsx b/client/src/features/profile/Profile.tsx index 23a9ea94f..f056a46ed 100644 --- a/client/src/features/profile/Profile.tsx +++ b/client/src/features/profile/Profile.tsx @@ -1,6 +1,7 @@ import { HtCard } from 'components/Ht'; import { RcraProfile } from 'components/RcraProfile'; import { UserProfile } from 'components/UserProfile'; +import { ProfileState } from 'store/profileSlice/profile.slice'; import { HaztrakUser, selectUser } from 'store/userSlice/user.slice'; import { useTitle } from 'hooks'; import React, { ReactElement, useEffect } from 'react'; @@ -14,7 +15,7 @@ import { getRcraProfile, selectRcraProfile } from 'store/profileSlice'; */ export function Profile(): ReactElement { const dispatch = useAppDispatch(); - const profile = useAppSelector(selectRcraProfile); + const profile: ProfileState | undefined = useAppSelector(selectRcraProfile); const user: HaztrakUser | undefined = useAppSelector(selectUser); useTitle('Profile'); diff --git a/client/src/store/profileSlice/profile.slice.ts b/client/src/store/profileSlice/profile.slice.ts index a74324d03..d45fffb88 100644 --- a/client/src/store/profileSlice/profile.slice.ts +++ b/client/src/store/profileSlice/profile.slice.ts @@ -50,7 +50,7 @@ export interface RcrainfoProfile { apiUser?: boolean; rcraSites?: T; phoneNumber?: string; - loading?: boolean; + isLoading?: boolean; error?: string; } From 4a9eaf0e827b46ec7eb5687abdff05a123de49d8 Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 14:48:28 -0500 Subject: [PATCH 07/11] manifestApi createQuickSignature and createManifest api layer endpoints --- .../src/components/Manifest/ManifestForm.tsx | 9 +++------ .../Manifest/QuickerSign/QuickerSignForm.tsx | 12 ++++++------ .../components/RcraProfile/RcraProfile.tsx | 2 +- .../components/UserProfile/UserProfile.tsx | 3 +-- client/src/services/manifestApi.ts | 19 +++++++++++++++++++ 5 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 client/src/services/manifestApi.ts diff --git a/client/src/components/Manifest/ManifestForm.tsx b/client/src/components/Manifest/ManifestForm.tsx index 5871c6f47..07fc2acfb 100644 --- a/client/src/components/Manifest/ManifestForm.tsx +++ b/client/src/components/Manifest/ManifestForm.tsx @@ -9,9 +9,8 @@ import React, { createContext, useState } from 'react'; import { Alert, Button, Col, Form, Row } from 'react-bootstrap'; import { FormProvider, SubmitHandler, useFieldArray, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; -import { htApi } from 'services'; +import { manifestApi } from 'services/manifestApi'; import { addNotification, useAppDispatch } from 'store'; -import { TaskStatus } from 'store/task.slice'; import { ContactForm, PhoneForm } from './Contact'; import { AddHandler, GeneratorForm, Handler } from './Handler'; import { Manifest, manifestSchema, ManifestStatus } from './manifestSchema'; @@ -95,14 +94,13 @@ export function ManifestForm({ const onSubmit: SubmitHandler = (data: Manifest) => { console.log('Manifest Submitted', data); - htApi - .post('/rcra/manifest', data) + manifestApi + .createManifest(data) .then((response) => { return response; }) .then((r) => { if ('manifestTrackingNumber' in r.data) { - console.log("congratulations! it's a manifest!"); navigate(`/manifest/${r.data.manifestTrackingNumber}/view`); } if ('taskId' in r.data) { @@ -116,7 +114,6 @@ export function ManifestForm({ } ) ); - console.log('r', r); setTaskId(r.data.taskId); toggleShowUpdatingRcra(); } diff --git a/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx b/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx index a7650f01f..0e8b78311 100644 --- a/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx +++ b/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Button, Col, Container, Form, ListGroup, Row } from 'react-bootstrap'; import { SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; -import { htApi } from 'services'; +import { manifestApi } from 'services/manifestApi'; import { addNotification, useAppDispatch, useAppSelector } from 'store'; import { selectUserName } from 'store/userSlice'; @@ -58,14 +58,14 @@ export function QuickerSignForm({ mtn, mtnHandler, handleClose, siteType }: Quic transporterOrder: mtnHandler.order, }; } - htApi - .post('rcra/manifest/sign', signature) - .then((response: AxiosResponse) => { + manifestApi + .createQuickSignature(signature) + .then((response) => { dispatch( addNotification({ - uniqueId: response.data.task, + uniqueId: response.data.taskId, createdDate: new Date().toISOString(), - message: `e-Manifest electronic signature task started. Task ID: ${response.data.task}`, + message: `e-Manifest electronic signature task started. Task ID: ${response.data.taskId}`, status: 'Info', read: false, timeout: 5000, diff --git a/client/src/components/RcraProfile/RcraProfile.tsx b/client/src/components/RcraProfile/RcraProfile.tsx index a4aa473d9..74883ba5a 100644 --- a/client/src/components/RcraProfile/RcraProfile.tsx +++ b/client/src/components/RcraProfile/RcraProfile.tsx @@ -5,7 +5,7 @@ import React, { useState } from 'react'; import { Button, Col, Container, Form, Row, Table } from 'react-bootstrap'; import { useForm } from 'react-hook-form'; import { Link } from 'react-router-dom'; -import { htApi, UserApi } from 'services'; +import { UserApi } from 'services'; import { addNotification, useAppDispatch, useAppSelector } from 'store'; import { getRcraProfile, updateProfile } from 'store/profileSlice'; import { RcrainfoProfileState, selectHaztrakSites } from 'store/profileSlice/profile.slice'; diff --git a/client/src/components/UserProfile/UserProfile.tsx b/client/src/components/UserProfile/UserProfile.tsx index 26e44d881..04207b129 100644 --- a/client/src/components/UserProfile/UserProfile.tsx +++ b/client/src/components/UserProfile/UserProfile.tsx @@ -6,8 +6,7 @@ import { SitePermissions } from 'components/UserProfile/SitePermissions'; import React, { createRef, useState } from 'react'; import { Button, Col, Container, Form, Row, Table } from 'react-bootstrap'; import { useForm } from 'react-hook-form'; -import { Link } from 'react-router-dom'; -import { htApi, UserApi } from 'services'; +import { UserApi } from 'services'; import { useAppDispatch } from 'store'; import { ProfileState } from 'store/profileSlice/profile.slice'; import { HaztrakUser, updateUserProfile } from 'store/userSlice/user.slice'; diff --git a/client/src/services/manifestApi.ts b/client/src/services/manifestApi.ts new file mode 100644 index 000000000..4cdc4c030 --- /dev/null +++ b/client/src/services/manifestApi.ts @@ -0,0 +1,19 @@ +import { AxiosResponse } from 'axios'; +import { Manifest } from 'components/Manifest'; +import { QuickerSignature } from 'components/Manifest/QuickerSign'; +import { htApi } from 'services/htApi'; +import { TaskStatus } from 'store/task.slice'; + +export const manifestApi = { + /** Sign a manifest through the Haztrak Proxy endpoint */ + createQuickSignature: async ( + signature: QuickerSignature + ): Promise> => { + return await htApi.post('rcra/manifest/sign', signature); + }, + + /** Create a manifest either via a proxy endpoint to RCRAInfo or as draft */ + createManifest: async (data: Manifest): Promise> => { + return await htApi.post('/rcra/manifest', data); + }, +}; From c38951b8d8f5aa45819555bcb195d682b751b2d0 Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 15:05:34 -0500 Subject: [PATCH 08/11] manifestApi sync site manifest api layer abstraction --- .../SyncManifestBtn/SyncManifestBtn.tsx | 36 +++++++++---------- client/src/services/manifestApi.ts | 5 +++ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/client/src/components/buttons/SyncManifestBtn/SyncManifestBtn.tsx b/client/src/components/buttons/SyncManifestBtn/SyncManifestBtn.tsx index 27b5965bc..197fde7dd 100644 --- a/client/src/components/buttons/SyncManifestBtn/SyncManifestBtn.tsx +++ b/client/src/components/buttons/SyncManifestBtn/SyncManifestBtn.tsx @@ -2,7 +2,7 @@ import { faSync } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { RcraApiUserBtn } from 'components/buttons'; import React, { useState } from 'react'; -import { htApi } from 'services'; +import { manifestApi } from 'services/manifestApi'; import { addNotification, useAppDispatch } from 'store'; interface SyncManifestProps { @@ -18,30 +18,30 @@ interface SyncManifestProps { export function SyncManifestBtn({ siteId, disabled }: SyncManifestProps) { const [syncingMtn, setSyncingMtn] = useState(false); const dispatch = useAppDispatch(); - const active = !disabled; return ( { setSyncingMtn(!syncingMtn); - htApi - .post('rcra/manifest/sync', { siteId: `${siteId}` }) - .then((response) => { - dispatch( - addNotification({ - uniqueId: response.data.task, - createdDate: new Date().toISOString(), - message: `Sync Manifest Task Launched. Task ID: ${response.data.task}`, - status: 'Info', - read: false, - timeout: 5000, - }) - ); - }) - .catch((reason) => console.error(reason)); + if (siteId) + manifestApi + .syncManifest(siteId) + .then((response) => { + dispatch( + addNotification({ + uniqueId: response.data.taskId, + createdDate: new Date().toISOString(), + message: `Sync Manifest Task Launched. Task ID: ${response.data.taskId}`, + status: 'Info', + read: false, + timeout: 5000, + }) + ); + }) + .catch((reason) => console.error(reason)); }} > {`Sync Manifest `} diff --git a/client/src/services/manifestApi.ts b/client/src/services/manifestApi.ts index 4cdc4c030..7b963c05a 100644 --- a/client/src/services/manifestApi.ts +++ b/client/src/services/manifestApi.ts @@ -16,4 +16,9 @@ export const manifestApi = { createManifest: async (data: Manifest): Promise> => { return await htApi.post('/rcra/manifest', data); }, + + /** Sync a sites manifest data with RCRAInfo */ + syncManifest: async (siteId: string): Promise> => { + return htApi.post('rcra/manifest/sync', { siteId: siteId }); + }, }; From 219093b6956643f690ef4a2fc29644a6379d7de1 Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 15:21:39 -0500 Subject: [PATCH 09/11] collapse RTK query slices into a single slice (as recommended by docs) --- .../Manifest/Address/AddressForm.tsx | 2 +- .../Manifest/Handler/HandlerSearchForm.tsx | 6 +- .../Manifest/UpdateRcra/UpdateRcra.tsx | 2 +- .../Manifest/WasteLine/DotIdSelect.tsx | 5 +- .../HazardousWasteForm/HazardousWasteForm.tsx | 2 +- .../StateWasteCodeSelect.tsx | 2 +- .../Notification/NotificationRow.tsx | 2 +- .../src/features/newManifest/NewManifest.tsx | 3 +- client/src/store/baseQuery.ts | 52 -------- client/src/store/htApiSlice.ts | 112 ++++++++++++++++++ client/src/store/index.ts | 20 ++++ client/src/store/rootStore.ts | 15 +-- client/src/store/site.slice.ts | 35 ------ client/src/store/task.slice.ts | 35 ------ client/src/store/wasteCode.slice.ts | 27 ----- 15 files changed, 146 insertions(+), 174 deletions(-) delete mode 100644 client/src/store/baseQuery.ts create mode 100644 client/src/store/htApiSlice.ts delete mode 100644 client/src/store/site.slice.ts delete mode 100644 client/src/store/task.slice.ts delete mode 100644 client/src/store/wasteCode.slice.ts diff --git a/client/src/components/Manifest/Address/AddressForm.tsx b/client/src/components/Manifest/Address/AddressForm.tsx index e5802d6d8..9f8d75316 100644 --- a/client/src/components/Manifest/Address/AddressForm.tsx +++ b/client/src/components/Manifest/Address/AddressForm.tsx @@ -1,5 +1,5 @@ import { GeneratorAddress } from 'components/Manifest/Address/GeneratorAddress'; -import React, { useContext } from 'react'; +import React from 'react'; interface Props { addressType: 'siteAddress' | 'mailingAddress'; diff --git a/client/src/components/Manifest/Handler/HandlerSearchForm.tsx b/client/src/components/Manifest/Handler/HandlerSearchForm.tsx index ef8ba001f..73d9ece89 100644 --- a/client/src/components/Manifest/Handler/HandlerSearchForm.tsx +++ b/client/src/components/Manifest/Handler/HandlerSearchForm.tsx @@ -12,7 +12,7 @@ import { useFormContext, } from 'react-hook-form'; import Select from 'react-select'; -import { useAppDispatch } from 'store'; +import { haztrakApi, useAppDispatch } from 'store'; import { siteApi } from 'store/site.slice'; interface Props { @@ -78,7 +78,7 @@ export function HandlerSearchForm({ if (value.length >= 5) { setRcrainfoSitesLoading(true); const rcrainfoSites = await dispatch( - siteApi.endpoints?.searchRcrainfoSites.initiate({ + haztrakApi.endpoints?.searchRcrainfoSites.initiate({ siteType: handlerType, siteId: value, }) @@ -89,7 +89,7 @@ export function HandlerSearchForm({ variant: 'danger', }); const knownRcraSites = await dispatch( - siteApi.endpoints?.searchRcraSites.initiate({ + haztrakApi.endpoints?.searchRcraSites.initiate({ siteType: handlerType, siteId: value, }) diff --git a/client/src/components/Manifest/UpdateRcra/UpdateRcra.tsx b/client/src/components/Manifest/UpdateRcra/UpdateRcra.tsx index d5fa35040..7658c583b 100644 --- a/client/src/components/Manifest/UpdateRcra/UpdateRcra.tsx +++ b/client/src/components/Manifest/UpdateRcra/UpdateRcra.tsx @@ -2,7 +2,7 @@ import { HtSpinner } from 'components/Ht'; import React from 'react'; import { Toast, ToastContainer } from 'react-bootstrap'; import { Navigate } from 'react-router-dom'; -import { useGetTaskStatusQuery } from 'store/task.slice'; +import { useGetTaskStatusQuery } from 'store'; interface UpdateRcraProps { taskId: string; diff --git a/client/src/components/Manifest/WasteLine/DotIdSelect.tsx b/client/src/components/Manifest/WasteLine/DotIdSelect.tsx index 719859499..178de5ff7 100644 --- a/client/src/components/Manifest/WasteLine/DotIdSelect.tsx +++ b/client/src/components/Manifest/WasteLine/DotIdSelect.tsx @@ -2,8 +2,7 @@ import { WasteLine } from 'components/Manifest/WasteLine/wasteLineSchema'; import React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import AsyncSelect from 'react-select/async'; -import { useAppDispatch } from 'store'; -import { wasteCodeApi } from 'store/wasteCode.slice'; +import { haztrakApi, useAppDispatch } from 'store'; interface DotIdOption { label: string; @@ -22,7 +21,7 @@ export function DotIdSelect() { * @param inputValue */ const getDotIdNumbers = async (inputValue: string) => { - const response = await dispatch(wasteCodeApi.endpoints.getDotIdNumbers.initiate(inputValue)); + const response = await dispatch(haztrakApi.endpoints.getDotIdNumbers.initiate(inputValue)); if (response.data) { return response.data.map((dotIdNumber) => { return { label: dotIdNumber, value: dotIdNumber } as DotIdOption; diff --git a/client/src/components/Manifest/WasteLine/HazardousWasteForm/HazardousWasteForm.tsx b/client/src/components/Manifest/WasteLine/HazardousWasteForm/HazardousWasteForm.tsx index e8c156585..dd33a433d 100644 --- a/client/src/components/Manifest/WasteLine/HazardousWasteForm/HazardousWasteForm.tsx +++ b/client/src/components/Manifest/WasteLine/HazardousWasteForm/HazardousWasteForm.tsx @@ -6,7 +6,7 @@ import React, { useContext } from 'react'; import { Col, Row } from 'react-bootstrap'; import { Controller, useFormContext } from 'react-hook-form'; import Select, { components, GroupBase, MultiValueProps, StylesConfig } from 'react-select'; -import { useGetFedWasteCodesQuery } from 'store/wasteCode.slice'; +import { useGetFedWasteCodesQuery } from 'store'; import { ErrorMessage } from '@hookform/error-message'; interface HazardousWasteFormProps { diff --git a/client/src/components/Manifest/WasteLine/HazardousWasteForm/StateWasteCodeSelect.tsx b/client/src/components/Manifest/WasteLine/HazardousWasteForm/StateWasteCodeSelect.tsx index 959d6dcb1..3398cf48f 100644 --- a/client/src/components/Manifest/WasteLine/HazardousWasteForm/StateWasteCodeSelect.tsx +++ b/client/src/components/Manifest/WasteLine/HazardousWasteForm/StateWasteCodeSelect.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import Select, { components } from 'react-select'; -import { useGetStateWasteCodesQuery } from 'store/wasteCode.slice'; +import { useGetStateWasteCodesQuery } from 'store'; interface StateWasteCodeSelectProps { stateId?: string; diff --git a/client/src/components/Notification/NotificationRow.tsx b/client/src/components/Notification/NotificationRow.tsx index 0ee2729fb..e926fbd27 100644 --- a/client/src/components/Notification/NotificationRow.tsx +++ b/client/src/components/Notification/NotificationRow.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { Button } from 'react-bootstrap'; import { removeNotification, useAppDispatch } from 'store'; import { HtNotification } from 'store/notificationSlice/notification.slice'; -import { useGetTaskStatusQuery } from 'store/task.slice'; +import { useGetTaskStatusQuery } from 'store'; interface NotificationRowProps { notification: HtNotification; diff --git a/client/src/features/newManifest/NewManifest.tsx b/client/src/features/newManifest/NewManifest.tsx index 20d7b591e..eae7ed842 100644 --- a/client/src/features/newManifest/NewManifest.tsx +++ b/client/src/features/newManifest/NewManifest.tsx @@ -8,8 +8,7 @@ import React, { useState } from 'react'; import { Form } from 'react-bootstrap'; import { useForm } from 'react-hook-form'; import { useParams } from 'react-router-dom'; -import { useAppSelector } from 'store'; -import { siteByEpaIdSelector } from 'store/profileSlice/profile.slice'; +import { useAppSelector, siteByEpaIdSelector } from 'store'; /** * NewManifest component allows a user to create a new electronic manifest. diff --git a/client/src/store/baseQuery.ts b/client/src/store/baseQuery.ts deleted file mode 100644 index 811537bca..000000000 --- a/client/src/store/baseQuery.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { BaseQueryFn } from '@reduxjs/toolkit/dist/query/react'; -import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; -import { htApi } from 'services'; - -export interface HtApiQueryArgs { - url: string; - method: AxiosRequestConfig['method']; - data?: AxiosRequestConfig['data']; - params?: AxiosRequestConfig['params']; -} - -export interface HtApiError { - status?: number; - data?: AxiosResponse['data']; - code?: string; - statusText?: string; -} - -/** - * Used by the RTK Query createApi, so we can hook into our htApi service - * for user authentication between the client and server. - * - * For information on custom RTK baseQuery types, see: - * https://redux-toolkit.js.org/rtk-query/usage-with-typescript#typing-a-basequery - * @param baseUrl - */ -export const htApiBaseQuery = - ( - { baseUrl }: { baseUrl: string } = { baseUrl: '/' } - ): BaseQueryFn< - HtApiQueryArgs, // Args - unknown, // Result - HtApiError, // Error - {}, // DefinitionExtraOptions - {} // Meta - > => - async ({ url, method, data, params }) => { - try { - const response = await htApi({ url: baseUrl + url, method, data, params }); - return { data: response.data }; - } catch (axiosError) { - let err = axiosError as AxiosError; - return { - error: { - cose: err.code, - status: err.response?.status, - statusText: err.response?.statusText, - data: err.response?.data || err.message, - } as HtApiError, - }; - } - }; diff --git a/client/src/store/htApiSlice.ts b/client/src/store/htApiSlice.ts new file mode 100644 index 000000000..e83defccf --- /dev/null +++ b/client/src/store/htApiSlice.ts @@ -0,0 +1,112 @@ +import { BaseQueryFn, createApi } from '@reduxjs/toolkit/dist/query/react'; +import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; +import { Code } from 'components/Manifest/WasteLine/wasteLineSchema'; +import { RcraSite } from 'components/RcraSite'; +import { htApi } from 'services'; + +export interface HtApiQueryArgs { + url: string; + method: AxiosRequestConfig['method']; + data?: AxiosRequestConfig['data']; + params?: AxiosRequestConfig['params']; +} + +export interface HtApiError { + status?: number; + data?: AxiosResponse['data']; + code?: string; + statusText?: string; +} + +/** + * Used by the RTK Query createApi, so we can hook into our htApi service + * for user authentication between the client and server. + * + * For information on custom RTK baseQuery types, see: + * https://redux-toolkit.js.org/rtk-query/usage-with-typescript#typing-a-basequery + * @param baseUrl + */ +export const htApiBaseQuery = + ( + { baseUrl }: { baseUrl: string } = { baseUrl: '/' } + ): BaseQueryFn< + HtApiQueryArgs, // Args + unknown, // Result + HtApiError, // Error + {}, // DefinitionExtraOptions + {} // Meta + > => + async ({ url, method, data, params }) => { + try { + const response = await htApi({ url: baseUrl + url, method, data, params }); + return { data: response.data }; + } catch (axiosError) { + let err = axiosError as AxiosError; + return { + error: { + code: err.code, + status: err.response?.status, + statusText: err.response?.statusText, + data: err.response?.data || err.message, + } as HtApiError, + }; + } + }; + +export interface TaskStatus { + status: 'PENDING' | 'STARTED' | 'SUCCESS' | 'FAILURE' | 'NOT FOUND'; + taskId: string; + taskName: string; + createdDate?: string; + doneDate?: string; + result?: any; +} + +interface RcrainfoSiteSearch { + siteType: 'designatedFacility' | 'generator' | 'transporter' | 'broker'; + siteId: string; +} + +export const haztrakApi = createApi({ + reducerPath: 'haztrakApi', + baseQuery: htApiBaseQuery({ + baseUrl: `${import.meta.env.VITE_HT_API_URL}/api/`, + }), + endpoints: (build) => ({ + searchRcrainfoSites: build.query, RcrainfoSiteSearch>({ + query: (data: RcrainfoSiteSearch) => ({ + url: 'rcra/handler/search', + method: 'post', + data: data, + }), + }), + searchRcraSites: build.query, RcrainfoSiteSearch>({ + query: (data: RcrainfoSiteSearch) => ({ + url: 'site/search', + method: 'get', + params: { epaId: data.siteId, siteType: data.siteType }, + }), + }), + getTaskStatus: build.query({ + query: (taskId) => ({ url: taskId, method: 'get' }), + }), + getFedWasteCodes: build.query, void>({ + query: () => ({ url: 'rcra/waste/code/federal', method: 'get' }), + }), + getStateWasteCodes: build.query, string>({ + query: (state) => ({ url: `rcra/waste/code/state/${state}`, method: 'get' }), + }), + getDotIdNumbers: build.query, string>({ + query: (id) => ({ url: 'rcra/waste/dot/id', method: 'get', params: { q: id } }), + }), + }), +}); + +export const { + useSearchRcrainfoSitesQuery, + useSearchRcraSitesQuery, + useGetTaskStatusQuery, + useGetFedWasteCodesQuery, + useGetStateWasteCodesQuery, + useGetDotIdNumbersQuery, +} = haztrakApi; diff --git a/client/src/store/index.ts b/client/src/store/index.ts index 61bc05ace..3c3d9d78b 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -2,6 +2,16 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; import { addNotification, removeNotification } from './notificationSlice'; import type { AppDispatch, RootState } from './rootStore'; import { AppStore, login, rootStore, setupStore } from './rootStore'; +import { siteByEpaIdSelector } from './profileSlice'; +import { + useGetTaskStatusQuery, + useSearchRcrainfoSitesQuery, + useSearchRcraSitesQuery, + useGetDotIdNumbersQuery, + useGetFedWasteCodesQuery, + useGetStateWasteCodesQuery, + haztrakApi, +} from 'store/htApiSlice'; // TypeSafe redux hooks for using the store export const useAppDispatch = () => useDispatch(); @@ -9,3 +19,13 @@ export const useAppSelector: TypedUseSelectorHook = useSelector; export { rootStore, login, setupStore, addNotification, removeNotification }; export type { RootState, AppDispatch, AppStore }; +export { + haztrakApi, + useGetTaskStatusQuery, + useSearchRcrainfoSitesQuery, + useSearchRcraSitesQuery, + useGetStateWasteCodesQuery, + useGetDotIdNumbersQuery, + useGetFedWasteCodesQuery, + siteByEpaIdSelector, +}; diff --git a/client/src/store/rootStore.ts b/client/src/store/rootStore.ts index 4462bf17c..eb7dac53f 100644 --- a/client/src/store/rootStore.ts +++ b/client/src/store/rootStore.ts @@ -1,18 +1,14 @@ import { combineReducers, configureStore, PreloadedState } from '@reduxjs/toolkit'; +import { haztrakApi } from 'store/htApiSlice'; import notificationReducers from 'store/notificationSlice'; import profileReducers from 'store/profileSlice/index'; -import { taskApi } from 'store/task.slice'; import userReducers, { login } from 'store/userSlice'; -import { wasteCodeApi } from 'store/wasteCode.slice'; -import { siteApi } from 'store/site.slice'; const rootReducer = combineReducers({ user: userReducers, notification: notificationReducers, profile: profileReducers, - [wasteCodeApi.reducerPath]: wasteCodeApi.reducer, - [taskApi.reducerPath]: taskApi.reducer, - [siteApi.reducerPath]: siteApi.reducer, + [haztrakApi.reducerPath]: haztrakApi.reducer, }); /** @@ -22,12 +18,7 @@ const rootReducer = combineReducers({ const setupStore = (preloadedState?: PreloadedState) => { return configureStore({ reducer: rootReducer, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware().concat( - wasteCodeApi.middleware, - taskApi.middleware, - siteApi.middleware - ), + middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(haztrakApi.middleware), preloadedState, }); }; diff --git a/client/src/store/site.slice.ts b/client/src/store/site.slice.ts deleted file mode 100644 index b6c6208fa..000000000 --- a/client/src/store/site.slice.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { createApi } from '@reduxjs/toolkit/query/react'; -import { RcraSite } from 'components/RcraSite'; -import { htApiBaseQuery } from 'store/baseQuery'; - -interface RcrainfoSiteSearch { - siteType: 'designatedFacility' | 'generator' | 'transporter' | 'broker'; - siteId: string; -} - -/** - * A RTK Query Api for fetching codes. - * the createApi function takes automatically generates react hooks, - */ -export const siteApi = createApi({ - reducerPath: 'siteApi', - baseQuery: htApiBaseQuery({ - baseUrl: `${import.meta.env.VITE_HT_API_URL}/api/`, - }), - endpoints: (build) => ({ - searchRcrainfoSites: build.query, RcrainfoSiteSearch>({ - query: (data: RcrainfoSiteSearch) => ({ - url: 'rcra/handler/search', - method: 'post', - data: data, - }), - }), - searchRcraSites: build.query, RcrainfoSiteSearch>({ - query: (data: RcrainfoSiteSearch) => ({ - url: 'site/search', - method: 'get', - params: { epaId: data.siteId, siteType: data.siteType }, - }), - }), - }), -}); diff --git a/client/src/store/task.slice.ts b/client/src/store/task.slice.ts deleted file mode 100644 index 826e269a5..000000000 --- a/client/src/store/task.slice.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { createApi } from '@reduxjs/toolkit/query/react'; -import { htApiBaseQuery } from 'store/baseQuery'; - -export interface TaskStatus { - status: 'PENDING' | 'STARTED' | 'SUCCESS' | 'FAILURE' | 'NOT FOUND'; - taskId: string; - taskName: string; - createdDate?: string; - doneDate?: string; - result?: any; -} - -export interface TaskError { - data: { - error: string; - }; -} - -/** - * A RTK Query Api for fetching codes. - * the createApi function takes automatically generates react hooks, - */ -export const taskApi = createApi({ - reducerPath: 'taskApi', - baseQuery: htApiBaseQuery({ - baseUrl: `${import.meta.env.VITE_HT_API_URL}/api/task/`, - }), - endpoints: (build) => ({ - getTaskStatus: build.query({ - query: (taskId) => ({ url: `${taskId}`, method: 'get' }), - }), - }), -}); - -export const { useGetTaskStatusQuery } = taskApi; diff --git a/client/src/store/wasteCode.slice.ts b/client/src/store/wasteCode.slice.ts deleted file mode 100644 index 467ada5ea..000000000 --- a/client/src/store/wasteCode.slice.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createApi } from '@reduxjs/toolkit/query/react'; -import { Code } from 'components/Manifest/WasteLine/wasteLineSchema'; -import { htApiBaseQuery } from 'store/baseQuery'; - -/** - * A RTK Query Api for fetching codes. - * the createApi function takes automatically generates react hooks, - */ -export const wasteCodeApi = createApi({ - reducerPath: 'wasteCodeApi', - baseQuery: htApiBaseQuery({ - baseUrl: `${import.meta.env.VITE_HT_API_URL}/api/rcra/waste/`, - }), - endpoints: (build) => ({ - getFedWasteCodes: build.query, void>({ - query: () => ({ url: 'code/federal', method: 'get' }), - }), - getStateWasteCodes: build.query, string>({ - query: (state) => ({ url: `code/state/${state}`, method: 'get' }), - }), - getDotIdNumbers: build.query, string>({ - query: (id) => ({ url: 'dot/id', method: 'get', params: { q: id } }), - }), - }), -}); - -export const { useGetFedWasteCodesQuery, useGetStateWasteCodesQuery } = wasteCodeApi; From b2d9a66ef31d8ee63e61bebc686263548f652d5d Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 15:36:07 -0500 Subject: [PATCH 10/11] rename userSlice to authSlice for clarity --- client/src/App.tsx | 4 +-- client/src/components/Layout/PrivateRoute.tsx | 2 +- client/src/components/Layout/Sidebar.tsx | 2 +- client/src/components/Layout/TopNav.tsx | 2 +- .../Manifest/QuickerSign/QuickerSignForm.tsx | 2 +- .../UserProfile/UserProfile.spec.tsx | 2 +- .../components/UserProfile/UserProfile.tsx | 2 +- client/src/features/home/Home.tsx | 2 +- client/src/features/login/Login.tsx | 2 +- client/src/features/profile/Profile.tsx | 2 +- client/src/services/UserApi.ts | 2 +- client/src/services/htApi.ts | 2 +- .../user.slice.ts => authSlice/auth.slice.ts} | 31 ++++++------------- .../authSlice.spec.ts} | 4 +-- client/src/store/authSlice/index.ts | 11 +++++++ .../{htApiSlice.ts => haztrakApiSlice.ts} | 0 client/src/store/index.ts | 2 +- .../src/store/profileSlice/profile.slice.ts | 2 +- client/src/store/rootStore.ts | 6 ++-- client/src/store/userSlice/index.ts | 6 ---- 20 files changed, 40 insertions(+), 48 deletions(-) rename client/src/store/{userSlice/user.slice.ts => authSlice/auth.slice.ts} (85%) rename client/src/store/{userSlice/user.spec.ts => authSlice/authSlice.spec.ts} (92%) create mode 100644 client/src/store/authSlice/index.ts rename client/src/store/{htApiSlice.ts => haztrakApiSlice.ts} (100%) delete mode 100644 client/src/store/userSlice/index.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index 07782d03c..1aed7371a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -5,8 +5,8 @@ import { router } from 'routes'; import { useAppDispatch, useAppSelector } from 'store'; import { getRcraProfile, selectRcraProfile } from 'store/profileSlice'; import { getHaztrakProfile } from 'store/profileSlice/profile.slice'; -import { selectUserName } from 'store/userSlice'; -import { getHaztrakUser } from 'store/userSlice/user.slice'; +import { selectUserName } from 'store/authSlice'; +import { getHaztrakUser } from 'store/authSlice/auth.slice'; import './App.scss'; function App(): ReactElement { diff --git a/client/src/components/Layout/PrivateRoute.tsx b/client/src/components/Layout/PrivateRoute.tsx index 34c6a719b..f4b93d9be 100644 --- a/client/src/components/Layout/PrivateRoute.tsx +++ b/client/src/components/Layout/PrivateRoute.tsx @@ -1,7 +1,7 @@ import React, { ReactElement } from 'react'; import { Navigate } from 'react-router-dom'; import { useAppSelector } from 'store'; -import { selectUserName } from 'store/userSlice'; +import { selectUserName } from 'store/authSlice'; interface Props { children: any; diff --git a/client/src/components/Layout/Sidebar.tsx b/client/src/components/Layout/Sidebar.tsx index 4bb46be77..64b9359b1 100644 --- a/client/src/components/Layout/Sidebar.tsx +++ b/client/src/components/Layout/Sidebar.tsx @@ -30,7 +30,7 @@ export function Sidebar(): ReactElement | null { const [helpNav, setHelpNav] = useState(false); const [mtnNav, setMtnNav] = useState(false); - const authUser = useSelector((state: RootState) => state.user.user); + const authUser = useSelector((state: RootState) => state.auth.user); if (!authUser) return null; return ( diff --git a/client/src/components/Layout/TopNav.tsx b/client/src/components/Layout/TopNav.tsx index 6d0627b89..c43b72c3c 100644 --- a/client/src/components/Layout/TopNav.tsx +++ b/client/src/components/Layout/TopNav.tsx @@ -9,7 +9,7 @@ import { faArrowRightFromBracket, faBars, faGear, faUser } from '@fortawesome/fr import logo from 'assets/haztrak-logos/haztrak-logo-zip-file/svg/logo-no-background.svg'; export function TopNav() { - const authUser = useSelector((state: RootState) => state.user.user); + const authUser = useSelector((state: RootState) => state.auth.user); const dispatch = useDispatch(); const navigation = useNavigate(); const logout = () => { diff --git a/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx b/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx index 0e8b78311..b1bc4f52c 100644 --- a/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx +++ b/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx @@ -11,7 +11,7 @@ import { SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import { manifestApi } from 'services/manifestApi'; import { addNotification, useAppDispatch, useAppSelector } from 'store'; -import { selectUserName } from 'store/userSlice'; +import { selectUserName } from 'store/authSlice'; interface QuickerSignProps { mtn: Array; diff --git a/client/src/components/UserProfile/UserProfile.spec.tsx b/client/src/components/UserProfile/UserProfile.spec.tsx index cd0d3ab88..e9922860c 100644 --- a/client/src/components/UserProfile/UserProfile.spec.tsx +++ b/client/src/components/UserProfile/UserProfile.spec.tsx @@ -5,7 +5,7 @@ import { UserProfile } from 'components/UserProfile/UserProfile'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import React from 'react'; -import { HaztrakUser } from 'store/userSlice/user.slice'; +import { HaztrakUser } from 'store/authSlice/auth.slice'; import { renderWithProviders, screen } from 'test-utils'; import { API_BASE_URL } from 'test-utils/mock/handlers'; import { vi, beforeAll, afterAll, afterEach, describe, test, expect } from 'vitest'; diff --git a/client/src/components/UserProfile/UserProfile.tsx b/client/src/components/UserProfile/UserProfile.tsx index 04207b129..299dbb0a1 100644 --- a/client/src/components/UserProfile/UserProfile.tsx +++ b/client/src/components/UserProfile/UserProfile.tsx @@ -9,7 +9,7 @@ import { useForm } from 'react-hook-form'; import { UserApi } from 'services'; import { useAppDispatch } from 'store'; import { ProfileState } from 'store/profileSlice/profile.slice'; -import { HaztrakUser, updateUserProfile } from 'store/userSlice/user.slice'; +import { HaztrakUser, updateUserProfile } from 'store/authSlice/auth.slice'; import { z } from 'zod'; interface UserProfileProps { diff --git a/client/src/features/home/Home.tsx b/client/src/features/home/Home.tsx index a1143a37f..79e480b82 100644 --- a/client/src/features/home/Home.tsx +++ b/client/src/features/home/Home.tsx @@ -9,7 +9,7 @@ import { Link } from 'react-router-dom'; import { useAppDispatch, useAppSelector } from 'store'; import { launchExampleTask } from 'store/notificationSlice/notification.slice'; import { getRcraProfile } from 'store/profileSlice'; -import { selectUserName } from 'store/userSlice/user.slice'; +import { selectUserName } from 'store/authSlice/auth.slice'; /** * Home page for logged-in user diff --git a/client/src/features/login/Login.tsx b/client/src/features/login/Login.tsx index 0bb7f583c..a857bf037 100644 --- a/client/src/features/login/Login.tsx +++ b/client/src/features/login/Login.tsx @@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form'; import { login, useAppDispatch, useAppSelector } from 'store'; import { useNavigate } from 'react-router-dom'; import { useTitle } from 'hooks'; -import { selectUserState } from 'store/userSlice'; +import { selectUserState } from 'store/authSlice'; import { z } from 'zod'; import { Col, Container, Form, Row } from 'react-bootstrap'; import logo from 'assets/haztrak-logos/low-resolution/svg/haztrak-low-resolution-logo-black-on-transparent-background.svg'; diff --git a/client/src/features/profile/Profile.tsx b/client/src/features/profile/Profile.tsx index f056a46ed..620b2b1a2 100644 --- a/client/src/features/profile/Profile.tsx +++ b/client/src/features/profile/Profile.tsx @@ -2,7 +2,7 @@ import { HtCard } from 'components/Ht'; import { RcraProfile } from 'components/RcraProfile'; import { UserProfile } from 'components/UserProfile'; import { ProfileState } from 'store/profileSlice/profile.slice'; -import { HaztrakUser, selectUser } from 'store/userSlice/user.slice'; +import { HaztrakUser, selectUser } from 'store/authSlice/auth.slice'; import { useTitle } from 'hooks'; import React, { ReactElement, useEffect } from 'react'; import { Col, Container, Row } from 'react-bootstrap'; diff --git a/client/src/services/UserApi.ts b/client/src/services/UserApi.ts index a153e6471..0dc4d657e 100644 --- a/client/src/services/UserApi.ts +++ b/client/src/services/UserApi.ts @@ -6,7 +6,7 @@ import { RcrainfoProfile, RcrainfoProfileSite, } from 'store/profileSlice/profile.slice'; -import { HaztrakUser } from 'store/userSlice'; +import { HaztrakUser } from 'store/authSlice'; interface HaztrakOrgResponse { id: string; diff --git a/client/src/services/htApi.ts b/client/src/services/htApi.ts index 1db82bd1c..d68d447c1 100644 --- a/client/src/services/htApi.ts +++ b/client/src/services/htApi.ts @@ -15,7 +15,7 @@ export const htApi = axios.create({ htApi.interceptors.request.use( (config: InternalAxiosRequestConfig) => { config.headers = config.headers ?? {}; - const token = rootStore.getState().user.token; + const token = rootStore.getState().auth.token; if (token) { config.headers['Authorization'] = `Token ${token}`; } diff --git a/client/src/store/userSlice/user.slice.ts b/client/src/store/authSlice/auth.slice.ts similarity index 85% rename from client/src/store/userSlice/user.slice.ts rename to client/src/store/authSlice/auth.slice.ts index bb92acab5..83930581e 100644 --- a/client/src/store/userSlice/user.slice.ts +++ b/client/src/store/authSlice/auth.slice.ts @@ -31,7 +31,7 @@ const initialState: UserState = { }; export const login = createAsyncThunk( - 'user/login', + 'auth/login', async ({ username, password }: { username: string; password: string }) => { const response = await axios.post(`${import.meta.env.VITE_HT_API_URL}/api/user/login`, { username, @@ -46,7 +46,7 @@ export const login = createAsyncThunk( ); /** Fetch a Haztrak User's information and store in global state */ -export const getHaztrakUser = createAsyncThunk('user/getHaztrakUser', async (arg, thunkAPI) => { +export const getHaztrakUser = createAsyncThunk('auth/getHaztrakUser', async (arg, thunkAPI) => { try { const { data } = await UserApi.getUser(); return data; @@ -55,19 +55,6 @@ export const getHaztrakUser = createAsyncThunk('user/getHaztrakUser', async (arg } }); -/** Submit a request to update a Haztrak User's information */ -export const updateHaztrakUser = createAsyncThunk( - 'user/updateHaztrakUser', - async (data: Partial, thunkAPI) => { - try { - const response = await UserApi.updateUser(data); - return response.data; - } catch (err) { - return thunkAPI.rejectWithValue(err); - } - } -); - /** * User logout Redux reducer Function * @@ -86,8 +73,8 @@ function logout(user: UserState): object { * update the HaztrakUser state with the new user information */ -const userSlice = createSlice({ - name: 'user', +const authSlice = createSlice({ + name: 'auth', initialState, reducers: { logout, @@ -153,17 +140,17 @@ const userSlice = createSlice({ /** * Get the current user's username from the Redux store */ -export const selectUserName = (state: RootState): string | undefined => state.user.user?.username; +export const selectUserName = (state: RootState): string | undefined => state.auth.user?.username; /** * Select the current user */ -export const selectUser = (state: RootState): HaztrakUser | undefined => state.user.user; +export const selectUser = (state: RootState): HaztrakUser | undefined => state.auth.user; /** * Select the current User State */ -export const selectUserState = (state: RootState): UserState => state.user; +export const selectUserState = (state: RootState): UserState => state.auth; -export default userSlice.reducer; -export const { updateUserProfile } = userSlice.actions; +export default authSlice.reducer; +export const { updateUserProfile } = authSlice.actions; diff --git a/client/src/store/userSlice/user.spec.ts b/client/src/store/authSlice/authSlice.spec.ts similarity index 92% rename from client/src/store/userSlice/user.spec.ts rename to client/src/store/authSlice/authSlice.spec.ts index b67ccb589..d29ee2500 100644 --- a/client/src/store/userSlice/user.spec.ts +++ b/client/src/store/authSlice/authSlice.spec.ts @@ -1,6 +1,6 @@ // test userSlice -import userReducers, { login } from 'store/userSlice'; -import { UserState } from 'store/userSlice/user.slice'; +import userReducers, { login } from 'store/authSlice'; +import { UserState } from 'store/authSlice/auth.slice'; import { describe, expect, test, vi } from 'vitest'; vi.spyOn(Storage.prototype, 'setItem'); diff --git a/client/src/store/authSlice/index.ts b/client/src/store/authSlice/index.ts new file mode 100644 index 000000000..fd3b31c2b --- /dev/null +++ b/client/src/store/authSlice/index.ts @@ -0,0 +1,11 @@ +import userReducer, { + login, + selectUserName, + selectUser, + selectUserState, +} from 'store/authSlice/auth.slice'; +import { HaztrakUser } from 'store/authSlice/auth.slice'; + +export default userReducer; +export { login, selectUserName, selectUser, selectUserState }; +export type { HaztrakUser }; diff --git a/client/src/store/htApiSlice.ts b/client/src/store/haztrakApiSlice.ts similarity index 100% rename from client/src/store/htApiSlice.ts rename to client/src/store/haztrakApiSlice.ts diff --git a/client/src/store/index.ts b/client/src/store/index.ts index 3c3d9d78b..cda0af9b7 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -11,7 +11,7 @@ import { useGetFedWasteCodesQuery, useGetStateWasteCodesQuery, haztrakApi, -} from 'store/htApiSlice'; +} from 'store/haztrakApiSlice'; // TypeSafe redux hooks for using the store export const useAppDispatch = () => useDispatch(); diff --git a/client/src/store/profileSlice/profile.slice.ts b/client/src/store/profileSlice/profile.slice.ts index d45fffb88..9c948c483 100644 --- a/client/src/store/profileSlice/profile.slice.ts +++ b/client/src/store/profileSlice/profile.slice.ts @@ -96,7 +96,7 @@ export const getRcraProfile = createAsyncThunk( 'profile/getRcrainfoProfile', async (arg, thunkAPI) => { const state = thunkAPI.getState() as RootState; - const username = state.user.user?.username; + const username = state.auth.user?.username; if (!username) { throw new Error('User is not logged in'); } diff --git a/client/src/store/rootStore.ts b/client/src/store/rootStore.ts index eb7dac53f..059a99ba8 100644 --- a/client/src/store/rootStore.ts +++ b/client/src/store/rootStore.ts @@ -1,11 +1,11 @@ import { combineReducers, configureStore, PreloadedState } from '@reduxjs/toolkit'; -import { haztrakApi } from 'store/htApiSlice'; +import { haztrakApi } from 'store/haztrakApiSlice'; import notificationReducers from 'store/notificationSlice'; import profileReducers from 'store/profileSlice/index'; -import userReducers, { login } from 'store/userSlice'; +import userReducers, { login } from 'store/authSlice'; const rootReducer = combineReducers({ - user: userReducers, + auth: userReducers, notification: notificationReducers, profile: profileReducers, [haztrakApi.reducerPath]: haztrakApi.reducer, diff --git a/client/src/store/userSlice/index.ts b/client/src/store/userSlice/index.ts deleted file mode 100644 index b60125f90..000000000 --- a/client/src/store/userSlice/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import userReducer, { login, selectUserName, selectUser, selectUserState } from './user.slice'; -import { HaztrakUser } from './user.slice'; - -export default userReducer; -export { login, selectUserName, selectUser, selectUserState }; -export type { HaztrakUser }; From ee77681da7c747f1375fc25ca2dac3fc1918f738 Mon Sep 17 00:00:00 2001 From: David Graham Date: Thu, 16 Nov 2023 15:43:31 -0500 Subject: [PATCH 11/11] reorg and control exports from Redux store --- client/src/App.tsx | 16 +++-- client/src/components/Layout/PrivateRoute.tsx | 3 +- client/src/components/Layout/Sidebar.spec.tsx | 2 +- client/src/components/Layout/TopNav.spec.tsx | 2 +- .../Manifest/Handler/HandlerSearchForm.tsx | 1 - .../Manifest/QuickerSign/QuickerSignForm.tsx | 5 +- .../Manifest/SiteSelect/SiteSelect.tsx | 3 +- .../Notification/NotificationBtn.tsx | 7 +- .../Notification/NotificationRow.tsx | 4 +- .../components/RcraProfile/RcraProfile.tsx | 12 +++- .../UserProfile/SitePermissions.tsx | 2 +- .../UserProfile/UserProfile.spec.tsx | 14 ++-- .../components/UserProfile/UserProfile.tsx | 6 +- .../buttons/RcraApiUserBtn/RcraApiUserBtn.tsx | 3 +- client/src/features/home/Home.spec.tsx | 2 +- client/src/features/home/Home.tsx | 11 +-- client/src/features/login/Login.tsx | 9 ++- .../features/notifications/Notifications.tsx | 3 +- client/src/features/profile/Profile.tsx | 13 ++-- client/src/services/UserApi.ts | 7 +- client/src/services/manifestApi.ts | 2 +- client/src/store/authSlice/auth.slice.ts | 6 +- client/src/store/authSlice/authSlice.spec.ts | 3 +- client/src/store/authSlice/index.ts | 11 --- client/src/store/index.ts | 67 +++++++++++++------ client/src/store/notificationSlice/index.ts | 11 --- .../notificationSlice/notification.spec.ts | 6 +- client/src/store/profileSlice/index.ts | 15 ----- .../store/profileSlice/profileSlice.spec.tsx | 8 +-- client/src/store/rootStore.ts | 8 +-- client/src/test-utils/fixtures/mockHandler.ts | 2 +- 31 files changed, 126 insertions(+), 138 deletions(-) delete mode 100644 client/src/store/authSlice/index.ts delete mode 100644 client/src/store/notificationSlice/index.ts delete mode 100644 client/src/store/profileSlice/index.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index 1aed7371a..9095b3f07 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,12 +1,16 @@ import { ErrorBoundary } from 'components/ErrorBoundary'; import React, { ReactElement, useEffect } from 'react'; -import { RouterProvider, useNavigate } from 'react-router-dom'; +import { RouterProvider } from 'react-router-dom'; import { router } from 'routes'; -import { useAppDispatch, useAppSelector } from 'store'; -import { getRcraProfile, selectRcraProfile } from 'store/profileSlice'; -import { getHaztrakProfile } from 'store/profileSlice/profile.slice'; -import { selectUserName } from 'store/authSlice'; -import { getHaztrakUser } from 'store/authSlice/auth.slice'; +import { + getHaztrakProfile, + getHaztrakUser, + getRcraProfile, + selectRcraProfile, + selectUserName, + useAppDispatch, + useAppSelector, +} from 'store'; import './App.scss'; function App(): ReactElement { diff --git a/client/src/components/Layout/PrivateRoute.tsx b/client/src/components/Layout/PrivateRoute.tsx index f4b93d9be..dba03cea5 100644 --- a/client/src/components/Layout/PrivateRoute.tsx +++ b/client/src/components/Layout/PrivateRoute.tsx @@ -1,7 +1,6 @@ import React, { ReactElement } from 'react'; import { Navigate } from 'react-router-dom'; -import { useAppSelector } from 'store'; -import { selectUserName } from 'store/authSlice'; +import { selectUserName, useAppSelector } from 'store'; interface Props { children: any; diff --git a/client/src/components/Layout/Sidebar.spec.tsx b/client/src/components/Layout/Sidebar.spec.tsx index fba1873da..a8d88ae43 100644 --- a/client/src/components/Layout/Sidebar.spec.tsx +++ b/client/src/components/Layout/Sidebar.spec.tsx @@ -13,7 +13,7 @@ describe('Sidebar', () => { const username = 'testuser1'; renderWithProviders(, { preloadedState: { - user: { + auth: { user: { username: username, isLoading: false }, token: 'fakeToken', loading: false, diff --git a/client/src/components/Layout/TopNav.spec.tsx b/client/src/components/Layout/TopNav.spec.tsx index 2cffc3d32..eb217299c 100644 --- a/client/src/components/Layout/TopNav.spec.tsx +++ b/client/src/components/Layout/TopNav.spec.tsx @@ -13,7 +13,7 @@ describe('TopNav', () => { const username = 'testuser1'; renderWithProviders(, { preloadedState: { - user: { + auth: { user: { username: username, isLoading: false }, token: 'fakeToken', loading: false, diff --git a/client/src/components/Manifest/Handler/HandlerSearchForm.tsx b/client/src/components/Manifest/Handler/HandlerSearchForm.tsx index 73d9ece89..916e9325e 100644 --- a/client/src/components/Manifest/Handler/HandlerSearchForm.tsx +++ b/client/src/components/Manifest/Handler/HandlerSearchForm.tsx @@ -13,7 +13,6 @@ import { } from 'react-hook-form'; import Select from 'react-select'; import { haztrakApi, useAppDispatch } from 'store'; -import { siteApi } from 'store/site.slice'; interface Props { handleClose: () => void; diff --git a/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx b/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx index b1bc4f52c..1316c460f 100644 --- a/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx +++ b/client/src/components/Manifest/QuickerSign/QuickerSignForm.tsx @@ -1,6 +1,6 @@ import { faFileSignature } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { AxiosError, AxiosResponse } from 'axios'; +import { AxiosError } from 'axios'; import { HtForm } from 'components/Ht'; import { Handler, RcraSiteType } from 'components/Manifest/manifestSchema'; import { QuickerSignature } from 'components/Manifest/QuickerSign/quickerSignSchema'; @@ -10,8 +10,7 @@ import { Button, Col, Container, Form, ListGroup, Row } from 'react-bootstrap'; import { SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import { manifestApi } from 'services/manifestApi'; -import { addNotification, useAppDispatch, useAppSelector } from 'store'; -import { selectUserName } from 'store/authSlice'; +import { addNotification, selectUserName, useAppDispatch, useAppSelector } from 'store'; interface QuickerSignProps { mtn: Array; diff --git a/client/src/components/Manifest/SiteSelect/SiteSelect.tsx b/client/src/components/Manifest/SiteSelect/SiteSelect.tsx index d8bfb956c..5e624451c 100644 --- a/client/src/components/Manifest/SiteSelect/SiteSelect.tsx +++ b/client/src/components/Manifest/SiteSelect/SiteSelect.tsx @@ -3,8 +3,7 @@ import { RcraSite } from 'components/RcraSite'; import React from 'react'; import { Control, Controller } from 'react-hook-form'; import Select from 'react-select'; -import { useAppSelector } from 'store'; -import { selectHaztrakSites, selectRcrainfoSites } from 'store/profileSlice/profile.slice'; +import { selectHaztrakSites, useAppSelector } from 'store'; interface SiteSelectProps { control: Control; diff --git a/client/src/components/Notification/NotificationBtn.tsx b/client/src/components/Notification/NotificationBtn.tsx index 3fba75cea..de536a86a 100644 --- a/client/src/components/Notification/NotificationBtn.tsx +++ b/client/src/components/Notification/NotificationBtn.tsx @@ -1,10 +1,9 @@ +import { faEnvelope } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React from 'react'; import { Badge, Button } from 'react-bootstrap'; import { Link } from 'react-router-dom'; -import { useAppSelector } from 'store'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faEnvelope } from '@fortawesome/free-solid-svg-icons'; -import { selectNotifications } from 'store/notificationSlice'; +import { selectNotifications, useAppSelector } from 'store'; export function NotificationBtn() { const notifications = useAppSelector(selectNotifications); diff --git a/client/src/components/Notification/NotificationRow.tsx b/client/src/components/Notification/NotificationRow.tsx index e926fbd27..3ab1d3f76 100644 --- a/client/src/components/Notification/NotificationRow.tsx +++ b/client/src/components/Notification/NotificationRow.tsx @@ -2,9 +2,7 @@ import { faCircleNotch, faTrash } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React from 'react'; import { Button } from 'react-bootstrap'; -import { removeNotification, useAppDispatch } from 'store'; -import { HtNotification } from 'store/notificationSlice/notification.slice'; -import { useGetTaskStatusQuery } from 'store'; +import { HtNotification, removeNotification, useAppDispatch, useGetTaskStatusQuery } from 'store'; interface NotificationRowProps { notification: HtNotification; diff --git a/client/src/components/RcraProfile/RcraProfile.tsx b/client/src/components/RcraProfile/RcraProfile.tsx index 74883ba5a..da3f666d3 100644 --- a/client/src/components/RcraProfile/RcraProfile.tsx +++ b/client/src/components/RcraProfile/RcraProfile.tsx @@ -6,9 +6,15 @@ import { Button, Col, Container, Form, Row, Table } from 'react-bootstrap'; import { useForm } from 'react-hook-form'; import { Link } from 'react-router-dom'; import { UserApi } from 'services'; -import { addNotification, useAppDispatch, useAppSelector } from 'store'; -import { getRcraProfile, updateProfile } from 'store/profileSlice'; -import { RcrainfoProfileState, selectHaztrakSites } from 'store/profileSlice/profile.slice'; +import { + addNotification, + getRcraProfile, + RcrainfoProfileState, + selectHaztrakSites, + updateProfile, + useAppDispatch, + useAppSelector, +} from 'store'; import { z } from 'zod'; interface ProfileViewProps { diff --git a/client/src/components/UserProfile/SitePermissions.tsx b/client/src/components/UserProfile/SitePermissions.tsx index 2ac074227..8ce6fe5fa 100644 --- a/client/src/components/UserProfile/SitePermissions.tsx +++ b/client/src/components/UserProfile/SitePermissions.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Table } from 'react-bootstrap'; import { Link } from 'react-router-dom'; -import { HaztrakProfileSite } from 'store/profileSlice/profile.slice'; +import { HaztrakProfileSite } from 'store'; interface SitePermissionsProps { sites?: Record; diff --git a/client/src/components/UserProfile/UserProfile.spec.tsx b/client/src/components/UserProfile/UserProfile.spec.tsx index e9922860c..fad71dbd8 100644 --- a/client/src/components/UserProfile/UserProfile.spec.tsx +++ b/client/src/components/UserProfile/UserProfile.spec.tsx @@ -5,10 +5,10 @@ import { UserProfile } from 'components/UserProfile/UserProfile'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import React from 'react'; -import { HaztrakUser } from 'store/authSlice/auth.slice'; +import { HaztrakUser, ProfileState } from 'store'; import { renderWithProviders, screen } from 'test-utils'; import { API_BASE_URL } from 'test-utils/mock/handlers'; -import { vi, beforeAll, afterAll, afterEach, describe, test, expect } from 'vitest'; +import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from 'vitest'; const DEFAULT_USER: HaztrakUser = { username: 'test', @@ -41,7 +41,10 @@ describe('UserProfile', () => { username: 'test', firstName: 'David', }; - renderWithProviders(, {}); + const profile: ProfileState = { + user: 'test', + }; + renderWithProviders(, {}); expect(screen.getByRole('textbox', { name: 'First Name' })).toHaveValue(user.firstName); expect(screen.getByText(user.username)).toBeInTheDocument(); }); @@ -51,7 +54,10 @@ describe('UserProfile', () => { const user: HaztrakUser = { ...DEFAULT_USER, }; - renderWithProviders(, {}); + const profile: ProfileState = { + user: 'test', + }; + renderWithProviders(, {}); const editButton = screen.getByRole('button', { name: 'Edit' }); const emailTextBox = screen.getByRole('textbox', { name: 'Email' }); // Act diff --git a/client/src/components/UserProfile/UserProfile.tsx b/client/src/components/UserProfile/UserProfile.tsx index 299dbb0a1..573adc31b 100644 --- a/client/src/components/UserProfile/UserProfile.tsx +++ b/client/src/components/UserProfile/UserProfile.tsx @@ -4,12 +4,10 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { HtForm, HtSpinner } from 'components/Ht'; import { SitePermissions } from 'components/UserProfile/SitePermissions'; import React, { createRef, useState } from 'react'; -import { Button, Col, Container, Form, Row, Table } from 'react-bootstrap'; +import { Button, Col, Container, Form, Row } from 'react-bootstrap'; import { useForm } from 'react-hook-form'; import { UserApi } from 'services'; -import { useAppDispatch } from 'store'; -import { ProfileState } from 'store/profileSlice/profile.slice'; -import { HaztrakUser, updateUserProfile } from 'store/authSlice/auth.slice'; +import { HaztrakUser, ProfileState, updateUserProfile, useAppDispatch } from 'store'; import { z } from 'zod'; interface UserProfileProps { diff --git a/client/src/components/buttons/RcraApiUserBtn/RcraApiUserBtn.tsx b/client/src/components/buttons/RcraApiUserBtn/RcraApiUserBtn.tsx index 17f7d4b30..05b866242 100644 --- a/client/src/components/buttons/RcraApiUserBtn/RcraApiUserBtn.tsx +++ b/client/src/components/buttons/RcraApiUserBtn/RcraApiUserBtn.tsx @@ -1,6 +1,5 @@ import { Button, ButtonProps } from 'react-bootstrap'; -import { useAppSelector } from 'store'; -import { selectHaztrakProfile } from 'store/profileSlice/profile.slice'; +import { selectHaztrakProfile, useAppSelector } from 'store'; interface HtApiUserBtnProps extends ButtonProps {} diff --git a/client/src/features/home/Home.spec.tsx b/client/src/features/home/Home.spec.tsx index 964b6c372..ac42f11f4 100644 --- a/client/src/features/home/Home.spec.tsx +++ b/client/src/features/home/Home.spec.tsx @@ -40,7 +40,7 @@ describe('Home', () => { test('renders', () => { renderWithProviders(, { preloadedState: { - user: { + auth: { user: { username: USERNAME, isLoading: false }, token: 'fake_token', loading: false, diff --git a/client/src/features/home/Home.tsx b/client/src/features/home/Home.tsx index 79e480b82..20ff08168 100644 --- a/client/src/features/home/Home.tsx +++ b/client/src/features/home/Home.tsx @@ -6,10 +6,13 @@ import { useTitle } from 'hooks'; import React, { ReactElement, useEffect } from 'react'; import { Accordion, Button, Col, Container, Row } from 'react-bootstrap'; import { Link } from 'react-router-dom'; -import { useAppDispatch, useAppSelector } from 'store'; -import { launchExampleTask } from 'store/notificationSlice/notification.slice'; -import { getRcraProfile } from 'store/profileSlice'; -import { selectUserName } from 'store/authSlice/auth.slice'; +import { + getRcraProfile, + launchExampleTask, + selectUserName, + useAppDispatch, + useAppSelector, +} from 'store'; /** * Home page for logged-in user diff --git a/client/src/features/login/Login.tsx b/client/src/features/login/Login.tsx index a857bf037..329dbe8d6 100644 --- a/client/src/features/login/Login.tsx +++ b/client/src/features/login/Login.tsx @@ -1,14 +1,13 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import logo from 'assets/haztrak-logos/low-resolution/svg/haztrak-low-resolution-logo-black-on-transparent-background.svg'; import { HtCard, HtForm } from 'components/Ht'; +import { useTitle } from 'hooks'; import React, { ReactElement, useEffect } from 'react'; +import { Col, Container, Form, Row } from 'react-bootstrap'; import { useForm } from 'react-hook-form'; -import { login, useAppDispatch, useAppSelector } from 'store'; import { useNavigate } from 'react-router-dom'; -import { useTitle } from 'hooks'; -import { selectUserState } from 'store/authSlice'; +import { login, selectUserState, useAppDispatch, useAppSelector } from 'store'; import { z } from 'zod'; -import { Col, Container, Form, Row } from 'react-bootstrap'; -import logo from 'assets/haztrak-logos/low-resolution/svg/haztrak-low-resolution-logo-black-on-transparent-background.svg'; const loginSchema = z.object({ username: z.string().min(1, 'Username Required').min(8), diff --git a/client/src/features/notifications/Notifications.tsx b/client/src/features/notifications/Notifications.tsx index 9c13a7a7e..9047c3aba 100644 --- a/client/src/features/notifications/Notifications.tsx +++ b/client/src/features/notifications/Notifications.tsx @@ -4,8 +4,7 @@ import { NotificationRow } from 'components/Notification'; import { useTitle } from 'hooks'; import React from 'react'; import { Col, Container, Table } from 'react-bootstrap'; -import { useAppSelector } from 'store'; -import { HtNotification, selectNotifications } from 'store/notificationSlice/notification.slice'; +import { HtNotification, selectNotifications, useAppSelector } from 'store'; /** * Table showing the user's current notifications diff --git a/client/src/features/profile/Profile.tsx b/client/src/features/profile/Profile.tsx index 620b2b1a2..2a8b37ef5 100644 --- a/client/src/features/profile/Profile.tsx +++ b/client/src/features/profile/Profile.tsx @@ -1,13 +1,18 @@ import { HtCard } from 'components/Ht'; import { RcraProfile } from 'components/RcraProfile'; import { UserProfile } from 'components/UserProfile'; -import { ProfileState } from 'store/profileSlice/profile.slice'; -import { HaztrakUser, selectUser } from 'store/authSlice/auth.slice'; import { useTitle } from 'hooks'; import React, { ReactElement, useEffect } from 'react'; import { Col, Container, Row } from 'react-bootstrap'; -import { useAppDispatch, useAppSelector } from 'store'; -import { getRcraProfile, selectRcraProfile } from 'store/profileSlice'; +import { + getRcraProfile, + HaztrakUser, + ProfileState, + selectRcraProfile, + selectUser, + useAppDispatch, + useAppSelector, +} from 'store'; /** * Display user profile diff --git a/client/src/services/UserApi.ts b/client/src/services/UserApi.ts index 0dc4d657e..7656a27fa 100644 --- a/client/src/services/UserApi.ts +++ b/client/src/services/UserApi.ts @@ -1,12 +1,7 @@ import { AxiosResponse } from 'axios'; import { HaztrakSite } from 'components/HaztrakSite'; import { htApi } from 'services/htApi'; -import { - HaztrakModulePermissions, - RcrainfoProfile, - RcrainfoProfileSite, -} from 'store/profileSlice/profile.slice'; -import { HaztrakUser } from 'store/authSlice'; +import { HaztrakModulePermissions, HaztrakUser, RcrainfoProfile, RcrainfoProfileSite } from 'store'; interface HaztrakOrgResponse { id: string; diff --git a/client/src/services/manifestApi.ts b/client/src/services/manifestApi.ts index 7b963c05a..60eddd6a6 100644 --- a/client/src/services/manifestApi.ts +++ b/client/src/services/manifestApi.ts @@ -2,7 +2,7 @@ import { AxiosResponse } from 'axios'; import { Manifest } from 'components/Manifest'; import { QuickerSignature } from 'components/Manifest/QuickerSign'; import { htApi } from 'services/htApi'; -import { TaskStatus } from 'store/task.slice'; +import { TaskStatus } from 'store'; export const manifestApi = { /** Sign a manifest through the Haztrak Proxy endpoint */ diff --git a/client/src/store/authSlice/auth.slice.ts b/client/src/store/authSlice/auth.slice.ts index 83930581e..5704186a0 100644 --- a/client/src/store/authSlice/auth.slice.ts +++ b/client/src/store/authSlice/auth.slice.ts @@ -1,4 +1,4 @@ -import { Action, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import axios from 'axios'; import { UserApi } from 'services'; import { RootState } from 'store/rootStore'; @@ -69,10 +69,6 @@ function logout(user: UserState): object { return { ...initialState, user: undefined, token: undefined } as UserState; } -/** - * update the HaztrakUser state with the new user information - */ - const authSlice = createSlice({ name: 'auth', initialState, diff --git a/client/src/store/authSlice/authSlice.spec.ts b/client/src/store/authSlice/authSlice.spec.ts index d29ee2500..bfa0c61bd 100644 --- a/client/src/store/authSlice/authSlice.spec.ts +++ b/client/src/store/authSlice/authSlice.spec.ts @@ -1,7 +1,6 @@ // test userSlice -import userReducers, { login } from 'store/authSlice'; -import { UserState } from 'store/authSlice/auth.slice'; import { describe, expect, test, vi } from 'vitest'; +import userReducers, { login, UserState } from './auth.slice'; vi.spyOn(Storage.prototype, 'setItem'); const initialState: UserState = { diff --git a/client/src/store/authSlice/index.ts b/client/src/store/authSlice/index.ts deleted file mode 100644 index fd3b31c2b..000000000 --- a/client/src/store/authSlice/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import userReducer, { - login, - selectUserName, - selectUser, - selectUserState, -} from 'store/authSlice/auth.slice'; -import { HaztrakUser } from 'store/authSlice/auth.slice'; - -export default userReducer; -export { login, selectUserName, selectUser, selectUserState }; -export type { HaztrakUser }; diff --git a/client/src/store/index.ts b/client/src/store/index.ts index cda0af9b7..4b3598c75 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -1,31 +1,60 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import { addNotification, removeNotification } from './notificationSlice'; import type { AppDispatch, RootState } from './rootStore'; -import { AppStore, login, rootStore, setupStore } from './rootStore'; -import { siteByEpaIdSelector } from './profileSlice'; -import { - useGetTaskStatusQuery, - useSearchRcrainfoSitesQuery, - useSearchRcraSitesQuery, - useGetDotIdNumbersQuery, - useGetFedWasteCodesQuery, - useGetStateWasteCodesQuery, - haztrakApi, -} from 'store/haztrakApiSlice'; +import { AppStore, rootStore, setupStore } from './rootStore'; -// TypeSafe redux hooks for using the store +// exports related to the rootStore export const useAppDispatch = () => useDispatch(); export const useAppSelector: TypedUseSelectorHook = useSelector; - -export { rootStore, login, setupStore, addNotification, removeNotification }; +export { rootStore, setupStore }; export type { RootState, AppDispatch, AppStore }; + export { haztrakApi, + useGetDotIdNumbersQuery, + useGetFedWasteCodesQuery, + useGetStateWasteCodesQuery, useGetTaskStatusQuery, useSearchRcrainfoSitesQuery, useSearchRcraSitesQuery, - useGetStateWasteCodesQuery, - useGetDotIdNumbersQuery, - useGetFedWasteCodesQuery, +} from 'store/haztrakApiSlice'; + +export { + getHaztrakUser, + login, + selectUser, + selectUserName, + selectUserState, + updateUserProfile, +} from './authSlice/auth.slice'; + +export { + addNotification, + removeNotification, + selectNotifications, + launchExampleTask, +} from './notificationSlice/notification.slice'; + +export { + getHaztrakProfile, + getRcraProfile, + selectRcrainfoSites, + selectRcraProfile, siteByEpaIdSelector, -}; + updateProfile, + selectHaztrakSites, + selectHaztrakProfile, +} from './profileSlice/profile.slice'; + +export type { HaztrakUser } from './authSlice/auth.slice'; +export type { TaskStatus } from './haztrakApiSlice'; +export type { HtNotification } from './notificationSlice/notification.slice'; +export type { + ProfileState, + RcrainfoProfileState, + HaztrakProfileSite, + HaztrakSitePermissions, + RcrainfoSitePermissions, + HaztrakModulePermissions, + RcrainfoProfile, + RcrainfoProfileSite, +} from './profileSlice/profile.slice'; diff --git a/client/src/store/notificationSlice/index.ts b/client/src/store/notificationSlice/index.ts deleted file mode 100644 index b18b15f11..000000000 --- a/client/src/store/notificationSlice/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import notificationReducer, { - addNotification, - removeNotification, - updateNotification, - selectNotifications, -} from 'store/notificationSlice/notification.slice'; -import Notification from './notification.slice'; - -export default notificationReducer; -export { addNotification, removeNotification, updateNotification, selectNotifications }; -export type { Notification }; diff --git a/client/src/store/notificationSlice/notification.spec.ts b/client/src/store/notificationSlice/notification.spec.ts index 623ce436b..2b3b40d4d 100644 --- a/client/src/store/notificationSlice/notification.spec.ts +++ b/client/src/store/notificationSlice/notification.spec.ts @@ -2,12 +2,12 @@ * Test for the notification Redux slice */ import { cleanup } from '@testing-library/react'; +import { afterEach, describe, expect, test } from 'vitest'; import NotificationReducer, { addNotification, + NotificationState, removeNotification, -} from 'store/notificationSlice/notification.slice'; -import { afterEach, describe, expect, test } from 'vitest'; -import { NotificationState } from './notification.slice'; +} from './notification.slice'; const initialState: NotificationState = { notifications: [], diff --git a/client/src/store/profileSlice/index.ts b/client/src/store/profileSlice/index.ts deleted file mode 100644 index d62eea31a..000000000 --- a/client/src/store/profileSlice/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import profileReducers, { getRcraProfile, updateProfile } from 'store/profileSlice/profile.slice'; -import { - selectRcrainfoSites, - selectRcraProfile, - siteByEpaIdSelector, -} from 'store/profileSlice/profile.slice'; - -export default profileReducers; -export { - getRcraProfile, - updateProfile, - selectRcraProfile, - selectRcrainfoSites, - siteByEpaIdSelector, -}; diff --git a/client/src/store/profileSlice/profileSlice.spec.tsx b/client/src/store/profileSlice/profileSlice.spec.tsx index df3841462..3914673b7 100644 --- a/client/src/store/profileSlice/profileSlice.spec.tsx +++ b/client/src/store/profileSlice/profileSlice.spec.tsx @@ -9,13 +9,7 @@ import { renderWithProviders } from 'test-utils'; import { createMockRcrainfoSite } from 'test-utils/fixtures'; import { createMockRcrainfoPermissions } from 'test-utils/fixtures/mockHandler'; import { describe, expect, test } from 'vitest'; -import rcraProfileReducer, { - getRcraProfile, - RcrainfoProfileSite, - ProfileState, - siteByEpaIdSelector, - selectRcrainfoSites, -} from 'store/profileSlice/profile.slice'; +import { selectRcrainfoSites, siteByEpaIdSelector } from './profile.slice'; interface TestComponentProps { siteId: string; diff --git a/client/src/store/rootStore.ts b/client/src/store/rootStore.ts index 059a99ba8..6da962f3c 100644 --- a/client/src/store/rootStore.ts +++ b/client/src/store/rootStore.ts @@ -1,8 +1,8 @@ import { combineReducers, configureStore, PreloadedState } from '@reduxjs/toolkit'; -import { haztrakApi } from 'store/haztrakApiSlice'; -import notificationReducers from 'store/notificationSlice'; -import profileReducers from 'store/profileSlice/index'; -import userReducers, { login } from 'store/authSlice'; +import userReducers, { login } from './authSlice/auth.slice'; +import { haztrakApi } from './haztrakApiSlice'; +import notificationReducers from './notificationSlice/notification.slice'; +import profileReducers from './profileSlice/profile.slice'; const rootReducer = combineReducers({ auth: userReducers, diff --git a/client/src/test-utils/fixtures/mockHandler.ts b/client/src/test-utils/fixtures/mockHandler.ts index 9105d8316..ddbe9f24c 100644 --- a/client/src/test-utils/fixtures/mockHandler.ts +++ b/client/src/test-utils/fixtures/mockHandler.ts @@ -2,7 +2,7 @@ import { HaztrakSite } from 'components/HaztrakSite'; import { Handler } from 'components/Manifest'; import { Transporter } from 'components/Manifest/Transporter'; import { RcraSite } from 'components/RcraSite'; -import { RcrainfoSitePermissions } from 'store/profileSlice/profile.slice'; +import { RcrainfoSitePermissions } from 'store'; /** * A mock handler object for tests