diff --git a/apps/desktop-e2e/tsconfig.json b/apps/desktop-e2e/tsconfig.json index 240053e44..627f12aeb 100644 --- a/apps/desktop-e2e/tsconfig.json +++ b/apps/desktop-e2e/tsconfig.json @@ -17,8 +17,7 @@ "preserveSymlinks": true }, "ts-node": { - "esm": true, - "files": true + "esm": true }, "include": ["src", ".eslintrc.cjs", "cucumber.cjs"] } diff --git a/apps/desktop/src/Router.tsx b/apps/desktop/src/Router.tsx index c5d0458c4..2c19fa1c7 100644 --- a/apps/desktop/src/Router.tsx +++ b/apps/desktop/src/Router.tsx @@ -4,6 +4,7 @@ import { useDataPolling } from "@umami/data-polling"; import { WalletClient, useCurrentAccount, + useGetUserAlerts, useImplicitAccounts, useResetBeaconConnections, } from "@umami/state"; @@ -42,12 +43,13 @@ const LoggedInRouterWithPolling = () => { useDataPolling(); const modalDisclosure = useDynamicModal(); const currentUser = useCurrentAccount(); + const getUserAlerts = useGetUserAlerts(); useEffect(() => { if (currentUser?.type === "social") { - const isInformed = localStorage.getItem("user:isSocialLoginWarningShown"); + const isInformed = getUserAlerts("isSocialLoginWarningShown"); - if (!isInformed || !JSON.parse(isInformed)) { + if (!isInformed) { void modalDisclosure.openWith(, { closeOnEsc: false }); } } diff --git a/apps/desktop/src/components/SocialLoginWarningModal/SocialLoginWarningModal.test.tsx b/apps/desktop/src/components/SocialLoginWarningModal/SocialLoginWarningModal.test.tsx index 69478cde4..c05e65f93 100644 --- a/apps/desktop/src/components/SocialLoginWarningModal/SocialLoginWarningModal.test.tsx +++ b/apps/desktop/src/components/SocialLoginWarningModal/SocialLoginWarningModal.test.tsx @@ -1,3 +1,5 @@ +import { type UmamiStore, makeStore } from "@umami/state"; + import { SocialLoginWarningModal } from "./SocialLoginWarningModal"; import { act, @@ -8,13 +10,15 @@ import { waitFor, } from "../../mocks/testUtils"; +let store: UmamiStore; + beforeEach(() => { - localStorage.clear(); + store = makeStore(); }); describe("", () => { it("renders the modal with correct title and content", async () => { - render(); + render(, { store }); await waitFor(() => { expect(screen.getByText("Important notice about your social account wallet")).toBeVisible(); @@ -28,7 +32,7 @@ describe("", () => { }); it("disables 'Continue' button when checkbox is not checked", () => { - render(); + render(, { store }); const button = screen.getByRole("button", { name: "Continue" }); expect(button).toBeDisabled(); @@ -36,7 +40,7 @@ describe("", () => { it("enables 'Continue' button when checkbox is checked", async () => { const user = userEvent.setup(); - render(); + render(, { store }); const checkbox = screen.getByRole("checkbox", { name: "I understand and accept the risks.", @@ -50,7 +54,7 @@ describe("", () => { it("sets localStorage and closes modal when 'Continue' is clicked", async () => { const { onClose } = dynamicModalContextMock; const user = userEvent.setup(); - render(); + render(, { store }); const checkbox = screen.getByRole("checkbox", { name: "I understand and accept the risks.", @@ -61,7 +65,7 @@ describe("", () => { await act(() => user.click(continueButton)); await waitFor(() => { - expect(localStorage.getItem("user:isSocialLoginWarningShown")).toBe("true"); + expect(store.getState().accounts.alerts.isSocialLoginWarningShown).toBe(true); }); expect(onClose).toHaveBeenCalled(); @@ -69,7 +73,7 @@ describe("", () => { it("toggles checkbox state correctly", async () => { const user = userEvent.setup(); - render(); + render(, { store }); const checkbox = screen.getByRole("checkbox", { name: "I understand and accept the risks.", diff --git a/apps/desktop/src/components/SocialLoginWarningModal/SocialLoginWarningModal.tsx b/apps/desktop/src/components/SocialLoginWarningModal/SocialLoginWarningModal.tsx index f30c4c58a..97271ed29 100644 --- a/apps/desktop/src/components/SocialLoginWarningModal/SocialLoginWarningModal.tsx +++ b/apps/desktop/src/components/SocialLoginWarningModal/SocialLoginWarningModal.tsx @@ -11,6 +11,7 @@ import { Text, } from "@chakra-ui/react"; import { useDynamicModalContext } from "@umami/components"; +import { accountsActions, useAppDispatch } from "@umami/state"; import { useState } from "react"; import { WarningIcon } from "../../assets/icons"; @@ -19,9 +20,10 @@ import colors from "../../style/colors"; export const SocialLoginWarningModal = () => { const { onClose } = useDynamicModalContext(); const [isAgreed, setIsAgreed] = useState(false); + const dispatch = useAppDispatch(); const handleInform = () => { - localStorage.setItem("user:isSocialLoginWarningShown", "true"); + dispatch(accountsActions.setAlerts({ key: "isSocialLoginWarningShown", value: true })); onClose(); }; diff --git a/apps/web/src/Layout.tsx b/apps/web/src/Layout.tsx index 6e6027c41..5a79cd78e 100644 --- a/apps/web/src/Layout.tsx +++ b/apps/web/src/Layout.tsx @@ -1,7 +1,7 @@ import { Grid, GridItem } from "@chakra-ui/react"; import { useDynamicModalContext } from "@umami/components"; import { useDataPolling } from "@umami/data-polling"; -import { useCurrentAccount } from "@umami/state"; +import { accountsActions, useAppDispatch, useCurrentAccount, useGetUserAlerts } from "@umami/state"; import { useEffect } from "react"; import { Footer } from "./components/Footer"; @@ -16,28 +16,30 @@ export const Layout = () => { useDataPolling(); const { openWith } = useDynamicModalContext(); const currentUser = useCurrentAccount(); + const getUserAlerts = useGetUserAlerts(); + const dispatch = useAppDispatch(); useEffect(() => { const CLOSING_DELAY = 300; const warnings = [ { - key: "user:isSocialLoginWarningShown", + key: "isSocialLoginWarningShown", component: , options: { closeOnEsc: false }, isEnabled: () => currentUser?.type === "social", }, { - key: "user:isExtensionsWarningShown", + key: "isExtensionsWarningShown", component: , options: { closeOnEsc: false, size: "xl" }, isEnabled: () => true, }, - ]; + ] as const; const warningsToShow = warnings.filter(warning => { - const isInformed = localStorage.getItem(warning.key); - return (!isInformed || !JSON.parse(isInformed)) && warning.isEnabled(); + const isInformed = getUserAlerts(warning.key); + return !isInformed && warning.isEnabled(); }); const showWarnings = async () => { @@ -47,7 +49,7 @@ export const Layout = () => { void openWith(warning.component, { ...warning.options, onClose: () => { - localStorage.setItem(warning.key, "true"); + dispatch(accountsActions.setAlerts({ key: warning.key, value: true })); resolve(true); }, }) diff --git a/packages/state/src/hooks/getAccountData.ts b/packages/state/src/hooks/getAccountData.ts index 722386a0d..d684028f6 100644 --- a/packages/state/src/hooks/getAccountData.ts +++ b/packages/state/src/hooks/getAccountData.ts @@ -15,7 +15,7 @@ import { useDispatch } from "react-redux"; import { useGetAccountBalance } from "./assets"; import { useMultisigAccounts } from "./multisig"; import { useAppSelector } from "./useAppSelector"; -import { accountsActions } from "../slices"; +import { type AccountsState, accountsActions } from "../slices"; export const useSeedPhrases = () => useAppSelector(s => s.accounts.seedPhrases); @@ -215,3 +215,9 @@ export const useGetDecryptedMnemonic = () => { return decrypt(encryptedMnemonic, password); }; }; + +export const useGetUserAlerts = () => { + const alerts = useAppSelector(s => s.accounts.alerts); + + return (key: keyof AccountsState["alerts"]) => alerts[key]; +}; diff --git a/packages/state/src/slices/accounts/State.ts b/packages/state/src/slices/accounts/State.ts index bd971b7ac..839db7085 100644 --- a/packages/state/src/slices/accounts/State.ts +++ b/packages/state/src/slices/accounts/State.ts @@ -9,4 +9,8 @@ export type AccountsState = { secretKeys: Record; current?: RawPkh | undefined; password?: string | undefined; + alerts: { + isSocialLoginWarningShown: boolean; + isExtensionsWarningShown: boolean; + }; }; diff --git a/packages/state/src/slices/accounts/accounts.ts b/packages/state/src/slices/accounts/accounts.ts index 91bfb7235..bb16ee00d 100644 --- a/packages/state/src/slices/accounts/accounts.ts +++ b/packages/state/src/slices/accounts/accounts.ts @@ -20,6 +20,10 @@ export const accountsInitialState: AccountsState = { seedPhrases: {}, secretKeys: {}, password: "", + alerts: { + isSocialLoginWarningShown: false, + isExtensionsWarningShown: false, + }, }; /** @@ -144,6 +148,15 @@ export const accountsSlice = createSlice({ setPassword: (state, { payload }: { payload: string }) => { state.password = payload; }, + setAlerts: ( + state, + { payload }: { payload: { key: keyof AccountsState["alerts"]; value: boolean } } + ) => { + state.alerts = { + ...state.alerts, + [payload.key]: payload.value, + }; + }, }, });