Skip to content

Commit

Permalink
setting up zustand (initially for users only)
Browse files Browse the repository at this point in the history
  • Loading branch information
karla-vm committed Nov 13, 2023
1 parent 9c1034d commit 5931c67
Show file tree
Hide file tree
Showing 10 changed files with 414 additions and 9 deletions.
3 changes: 2 additions & 1 deletion services/ui-src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"react-scripts": "^5.0.0",
"react-uuid": "^1.0.3",
"sass": "^1.37.5",
"yup": "^0.32.11"
"yup": "^0.32.11",
"zustand": "^4.4.6"
},
"devDependencies": {
"@aws-sdk/types": "^3.38.0",
Expand Down
1 change: 1 addition & 0 deletions services/ui-src/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
export * from "./banners";
export * from "./formFields";
export * from "./other";
export * from "./states";
export * from "./reportContext";
export * from "./reports";
export * from "./users";
62 changes: 62 additions & 0 deletions services/ui-src/src/types/states.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
AdminBannerData,
EntityShape,
MCRUser,
ReportMetadataShape,
ReportShape,
} from "types";

// initial user state
export interface McrUserState {
// INITIAL STATE
user?: MCRUser;
showLocalLogins: boolean | undefined;
// ACTIONS
setUser: (newUser?: MCRUser) => void;
setShowLocalLogins: (showLocalLogins: boolean) => void;
}

// initial admin banner state
export interface AdminBannerState {
// INITIAL STATE
bannerData: AdminBannerData | undefined;
bannerActive: boolean;
bannerLoading: boolean;
bannerErrorMessage: string;
bannerDeleting: boolean;
// ACTIONS
setBannerData: (newBannerData: AdminBannerData | undefined) => void;
clearAdminBanner: () => void;
setBannerActive: (bannerStatus: boolean) => void;
setBannerLoading: (bannerLoading: boolean) => void;
setBannerErrorMessage: (bannerErrorMessage: string) => void;
setBannerDeleting: (bannerDeleting: boolean) => void;
}

// initial report state
export interface McrReportState {
// INITIAL STATE
report: ReportShape | undefined;
reportsByState: ReportMetadataShape[] | undefined;
submittedReportsByState: ReportMetadataShape[] | undefined;
lastSavedTime: string | undefined;
// ACTIONS
setReport: (newReport: ReportShape | undefined) => void;
setReportsByState: (
newReportsByState: ReportMetadataShape[] | undefined
) => void;
clearReportsByState: () => void;
setSubmittedReportsByState: (
newSubmittedReportsByState: ReportMetadataShape[] | undefined
) => void;
setLastSavedTime: (lastSavedTime: string | undefined) => void;
}

// initial entity state
export interface McrEntityState {
// INITIAL STATE
selectedEntity: EntityShape | undefined;
// ACTIONS
setSelectedEntity: (newSelectedEntity: EntityShape | undefined) => void;
clearSelectedEntity: () => void;
}
9 changes: 4 additions & 5 deletions services/ui-src/src/utils/auth/UserProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import {
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { useLocation } from "react-router-dom";
import { Auth } from "aws-amplify";
import config from "config";
// utils
import { initAuthManager, updateTimeout, getExpiration } from "utils";
import { initAuthManager, updateTimeout, getExpiration, useStore } from "utils";
import { PRODUCTION_HOST_DOMAIN } from "../../constants";
// types
import { MCRUser, UserContextShape, UserRoles } from "types/users";
Expand All @@ -34,15 +33,15 @@ export const UserProvider = ({ children }: Props) => {
const location = useLocation();
const isProduction = window.location.origin.includes(PRODUCTION_HOST_DOMAIN);

const [user, setUser] = useState<any>(null);
const [showLocalLogins, setShowLocalLogins] = useState(false);
// state management
const { user, showLocalLogins, setUser, setShowLocalLogins } = useStore();

// initialize the authentication manager that oversees timeouts
initAuthManager();

const logout = useCallback(async () => {
try {
setUser(null);
setUser(undefined);
await Auth.signOut();
localStorage.clear();
} catch (error) {
Expand Down
19 changes: 16 additions & 3 deletions services/ui-src/src/utils/auth/userProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { act } from "react-dom/test-utils";
// utils
import { UserContext, UserProvider } from "utils";
import { RouterWrappedComponent } from "utils/testing/setupJest";
import { UserContext, UserProvider, useStore } from "utils";
import {
mockBannerStore,
mockStateUserStore,
RouterWrappedComponent,
} from "utils/testing/setupJest";
import { UserRoles } from "types/users";

const mockAuthPayload = {
Expand All @@ -31,6 +35,9 @@ jest.mock("aws-amplify", () => ({
},
}));

jest.mock("utils/state/useStore");
const mockedUseStore = useStore as jest.MockedFunction<typeof useStore>;

// COMPONENTS

const TestComponent = () => {
Expand All @@ -48,7 +55,7 @@ const TestComponent = () => {
</button>
User Test
<p data-testid="show-local-logins">
{context.showLocalLogins
{mockedUseStore().showLocalLogins
? "showLocalLogins is true"
: "showLocalLogins is false"}
</p>
Expand Down Expand Up @@ -88,6 +95,9 @@ const breakCheckAuthState = async () => {
describe("Test UserProvider", () => {
beforeEach(async () => {
await act(async () => {
mockedUseStore
.mockReturnValue(mockBannerStore)
.mockReturnValue(mockStateUserStore);
render(testComponent);
});
});
Expand Down Expand Up @@ -137,6 +147,7 @@ describe("Test UserProvider with production path", () => {
await setWindowOrigin("mdctmcr.cms.gov");
await breakCheckAuthState();
await act(async () => {
mockedUseStore.mockReturnValue(mockStateUserStore);
await render(testComponent);
});
expect(window.location.origin).toContain("mdctmcr.cms.gov");
Expand All @@ -159,6 +170,7 @@ describe("Test UserProvider with non-production path", () => {
await setWindowOrigin("wherever");
await breakCheckAuthState();
await act(async () => {
mockedUseStore.mockReturnValue(mockStateUserStore);
await render(testComponent);
});
expect(window.location.origin).toContain("wherever");
Expand All @@ -178,6 +190,7 @@ describe("Test UserProvider error handling", () => {
});

await act(async () => {
mockedUseStore.mockReturnValue(mockStateUserStore);
render(testComponent);
});
await act(async () => {
Expand Down
2 changes: 2 additions & 0 deletions services/ui-src/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export * from "./reports/reports";
export * from "./reports/routing";
// statusing
export * from "./statusing/getRouteStatus";
// state management
export * from "./state/useStore";
// tracking
export * from "./tracking/tealium";
// validation
Expand Down
129 changes: 129 additions & 0 deletions services/ui-src/src/utils/state/useStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";
// types
import {
McrUserState,
MCRUser,
AdminBannerData,
AdminBannerState,
ReportShape,
McrReportState,
ReportMetadataShape,
EntityShape,
McrEntityState,
} from "types";

// USER STORE
const userStore = (set: Function) => ({
// initial state
user: undefined,
// show local logins
showLocalLogins: undefined,
// actions
setUser: (newUser?: MCRUser) =>
set(() => ({ user: newUser }), false, { type: "setUser" }),
// toggle show local logins (dev only)
setShowLocalLogins: () =>
set(() => ({ showLocalLogins: true }), false, { type: "showLocalLogins" }),
});

// BANNER STORE
const bannerStore = (set: Function) => ({
// initial state
bannerData: undefined,
bannerActive: false,
bannerLoading: false,
bannerErrorMessage: "",
bannerDeleting: false,
// actions
setBannerData: (newBanner: AdminBannerData | undefined) =>
set(() => ({ bannerData: newBanner }), false, { type: "setBannerData" }),
clearAdminBanner: () =>
set(() => ({ bannerData: undefined }), false, { type: "clearAdminBanner" }),
setBannerActive: (bannerStatus: boolean) =>
set(() => ({ bannerActive: bannerStatus }), false, {
type: "setBannerActive",
}),
setBannerLoading: (loading: boolean) =>
set(() => ({ bannerLoading: loading }), false, {
type: "setBannerLoading",
}),
setBannerErrorMessage: (errorMessage: string) =>
set(() => ({ bannerErrorMessage: errorMessage }), false, {
type: "setBannerErrorMessage",
}),
setBannerDeleting: (deleting: boolean) =>
set(() => ({ bannerDeleting: deleting }), false, {
type: "setBannerDeleting",
}),
});

// REPORT STORE
const reportStore = (set: Function) => ({
// initial state
report: undefined,
reportsByState: undefined,
submittedReportsByState: undefined,
lastSavedTime: undefined,
// actions
setReport: (newReport: ReportShape | undefined) =>
set(() => ({ report: newReport }), false, { type: "setReport" }),
setReportsByState: (newReportsByState: ReportMetadataShape[] | undefined) =>
set(() => ({ reportsByState: newReportsByState }), false, {
type: "setReportsByState",
}),
clearReportsByState: () =>
set(() => ({ reportsByState: undefined }), false, {
type: "clearReportsByState",
}),
setSubmittedReportsByState: (
newSubmittedReportsByState: ReportMetadataShape[] | undefined
) =>
set(
() => ({ submittedReportsByState: newSubmittedReportsByState }),
false,
{ type: "setSubmittedReportsByState" }
),
setLastSavedTime: (savedTime: string | undefined) =>
set(() => ({ lastSavedTime: savedTime }), false, {
type: "setLastSavedTime",
}),
});

// ENTITY STORE
const entityStore = (set: Function) => ({
// initial state
selectedEntity: undefined,
// actions
setSelectedEntity: (newSelectedEntity: EntityShape | undefined) =>
set(
() => ({
selectedEntity: newSelectedEntity,
}),
false,
{
type: "setSelectedEntity",
}
),
clearSelectedEntity: () =>
set(() => ({ selectedEntity: undefined }), false, {
type: "clearSelectedEntity",
}),
});

export const useStore = create(
// persist and devtools are being used for debugging state
persist(
devtools<McrUserState & AdminBannerState & McrReportState & McrEntityState>(
(set) => ({
...userStore(set),
...bannerStore(set),
...reportStore(set),
...entityStore(set),
})
),
{
name: "mcr-store",
}
)
);
Loading

0 comments on commit 5931c67

Please sign in to comment.