From 373f5730c205d4094f0fc544c373d459f504bd95 Mon Sep 17 00:00:00 2001 From: Oleg Chendighelean Date: Tue, 25 Feb 2025 17:07:01 +0000 Subject: [PATCH] feat: implement session management and login flow --- apps/web/src/components/App/App.tsx | 27 +++++--- .../SetupPassword/SetupPassword.tsx | 4 +- apps/web/src/providers/ReduxStore.tsx | 2 + .../src/views/SessionLogin/SessionLogin.tsx | 67 +++++++++++++++++++ apps/web/src/views/SessionLogin/index.ts | 1 + packages/state/src/hooks/session.ts | 15 ++++- packages/state/src/reducer.ts | 4 +- packages/state/src/slices/session.ts | 15 +++++ packages/state/src/store.ts | 2 + 9 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 apps/web/src/views/SessionLogin/SessionLogin.tsx create mode 100644 apps/web/src/views/SessionLogin/index.ts create mode 100644 packages/state/src/slices/session.ts diff --git a/apps/web/src/components/App/App.tsx b/apps/web/src/components/App/App.tsx index 160c292d2a..603113a556 100644 --- a/apps/web/src/components/App/App.tsx +++ b/apps/web/src/components/App/App.tsx @@ -1,20 +1,25 @@ -import { useCurrentAccount } from "@umami/state"; +import { useHandleSession } from "@umami/state"; import { Layout } from "../../Layout"; +import { SessionLogin } from "../../views/SessionLogin/SessionLogin"; import { Welcome } from "../../views/Welcome"; import { BeaconProvider } from "../beacon"; import { WalletConnectProvider } from "../WalletConnect/WalletConnectProvider"; export const App = () => { - const currentAccount = useCurrentAccount(); + const { isSessionActive, isOnboarded } = useHandleSession(); - return currentAccount ? ( - - - - - - ) : ( - - ); + if (!isOnboarded()) { + return ; + } else if (!isSessionActive) { + return ; + } else { + return ( + + + + + + ); + } }; diff --git a/apps/web/src/components/Onboarding/SetupPassword/SetupPassword.tsx b/apps/web/src/components/Onboarding/SetupPassword/SetupPassword.tsx index 0a4059877e..c273bbe3f6 100644 --- a/apps/web/src/components/Onboarding/SetupPassword/SetupPassword.tsx +++ b/apps/web/src/components/Onboarding/SetupPassword/SetupPassword.tsx @@ -11,7 +11,7 @@ import { Text, } from "@chakra-ui/react"; import { useMultiForm } from "@umami/components"; -import { useIsPasswordSet, useSessionTimeout } from "@umami/state"; +import { useHandleSession, useIsPasswordSet } from "@umami/state"; import { defaultDerivationPathTemplate } from "@umami/tezos"; import { FormProvider } from "react-hook-form"; @@ -72,7 +72,7 @@ export const SetupPassword = ({ mode }: SetupPasswordProps) => { const color = useColor(); const { onSubmit, isLoading } = useGetSetupPasswordSubmitHandler(mode); const isPasswordSet = useIsPasswordSet(); - const { setupSessionTimeout } = useSessionTimeout(); + const { setupSessionTimeout } = useHandleSession(); const form = useMultiForm({ mode: "all", diff --git a/apps/web/src/providers/ReduxStore.tsx b/apps/web/src/providers/ReduxStore.tsx index f32546b63c..ea43a790a6 100644 --- a/apps/web/src/providers/ReduxStore.tsx +++ b/apps/web/src/providers/ReduxStore.tsx @@ -14,12 +14,14 @@ export const ReduxStore = ({ children }: PropsWithChildren) => ( const ReduxStoreContent = ({ children }: PropsWithChildren) => { const nonce = getOrCreateUserNonce(); + if (!nonce) { return <>{children}; } if (!persistor) { const { persistor } = initializePersistence(store, nonce); setupPersistor(persistor); + return ( {children} diff --git a/apps/web/src/views/SessionLogin/SessionLogin.tsx b/apps/web/src/views/SessionLogin/SessionLogin.tsx new file mode 100644 index 0000000000..36e6d18f44 --- /dev/null +++ b/apps/web/src/views/SessionLogin/SessionLogin.tsx @@ -0,0 +1,67 @@ +import { Button, Center, Flex, type FlexProps, Heading, Icon, Text } from "@chakra-ui/react"; +import { type FieldValues, FormProvider, useForm } from "react-hook-form"; + +import { LogoLightIcon, TezosLogoIcon } from "../../assets/icons"; +import { PasswordInput } from "../../components/PasswordInput"; +import { useColor } from "../../styles/useColor"; + +export const SessionLogin = () => { + const color = useColor(); + + const form = useForm({ + defaultValues: { + password: "", + }, + mode: "onBlur", + }); + + const onSubmit = (data: FieldValues) => { + console.log(data); + }; + + return ( + + + + + + + Welcome back! + + + You need to sign back in to use your wallet. + + + + + + + + + ); +}; + +const Logo = (props: FlexProps) => ( +
+ + Powered by + + +
+); diff --git a/apps/web/src/views/SessionLogin/index.ts b/apps/web/src/views/SessionLogin/index.ts new file mode 100644 index 0000000000..b4e2067041 --- /dev/null +++ b/apps/web/src/views/SessionLogin/index.ts @@ -0,0 +1 @@ +export * from "./SessionLogin"; diff --git a/packages/state/src/hooks/session.ts b/packages/state/src/hooks/session.ts index e3029071c9..f228c0027b 100644 --- a/packages/state/src/hooks/session.ts +++ b/packages/state/src/hooks/session.ts @@ -1,10 +1,19 @@ +import { useAppDispatch } from "./useAppDispatch"; +import { useAppSelector } from "./useAppSelector"; +import { setHasSession } from "../slices/session"; + const SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes from login -export const useSessionTimeout = () => { +export const useHandleSession = () => { + const isOnboarded = () => !!localStorage.getItem("user_requirements_nonce"); + const isSessionActive = useAppSelector(state => state.session.hasSession); + const dispatch = useAppDispatch(); + const setupSessionTimeout = () => { try { - const timeoutId = window.setTimeout(() => { + const timeoutId = setTimeout(() => { sessionStorage.clear(); + dispatch(setHasSession(false)); }, SESSION_TIMEOUT); // Store timeout ID in case we need to clear it @@ -14,5 +23,5 @@ export const useSessionTimeout = () => { } }; - return { setupSessionTimeout }; + return { setupSessionTimeout, isSessionActive, isOnboarded }; }; diff --git a/packages/state/src/reducer.ts b/packages/state/src/reducer.ts index ca6f68fc45..a19a22764d 100644 --- a/packages/state/src/reducer.ts +++ b/packages/state/src/reducer.ts @@ -16,6 +16,7 @@ import { errorsSlice } from "./slices/errors"; import { multisigsSlice } from "./slices/multisigs"; import { networksSlice } from "./slices/networks"; import { protocolSettingsSlice } from "./slices/protocolSettings"; +import { sessionSlice } from "./slices/session"; import { tokensSlice } from "./slices/tokens"; let TEST_STORAGE: Storage | undefined; @@ -47,6 +48,7 @@ export const makeReducer = () => { networks: networksSlice.reducer, protocolSettings: protocolSettingsSlice.reducer, tokens: tokensSlice.reducer, + session: sessionSlice.reducer, }); return (state: any, action: Action) => { @@ -68,7 +70,7 @@ export const makePersistConfigs = (storage_: Storage | undefined, password?: str key: "root", version: VERSION, storage, - blacklist: ["accounts", "assets", "announcement", "tokens", "protocolSettings"], + blacklist: ["accounts", "assets", "announcement", "tokens", "protocolSettings", "session"], migrate: createAsyncMigrate(mainStoreMigrations, { debug: false }), transforms: [ encryptTransform( diff --git a/packages/state/src/slices/session.ts b/packages/state/src/slices/session.ts new file mode 100644 index 0000000000..05e4bc14e0 --- /dev/null +++ b/packages/state/src/slices/session.ts @@ -0,0 +1,15 @@ +import { createSlice } from "@reduxjs/toolkit"; + +export const sessionSlice = createSlice({ + name: "session", + initialState: { + hasSession: false, + }, + reducers: { + setHasSession: (state, action) => { + state.hasSession = action.payload; + }, + }, +}); + +export const { setHasSession } = sessionSlice.actions; diff --git a/packages/state/src/store.ts b/packages/state/src/store.ts index a8da7f9149..949e894e97 100644 --- a/packages/state/src/store.ts +++ b/packages/state/src/store.ts @@ -3,6 +3,7 @@ import { type Storage, persistReducer, persistStore } from "redux-persist"; import { makePersistConfigs, makeReducer } from "./reducer"; import { accountsSlice } from "./slices/accounts/accounts"; +import { setHasSession } from "./slices/session"; // Create initial store without persistence export const makeStore = () => { @@ -55,6 +56,7 @@ export const initializePersistence = ( // Update store's reducer store.replaceReducer(finalReducer); + store.dispatch(setHasSession(true)); const persistor = persistStore(store); return { persistor };