Files
qlockify-frontend-deployment/src/context/AuthFlowContext.tsx

197 lines
4.7 KiB
TypeScript

import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react"
type FlowName = "login" | "signup" | "forgotPassword"
export type CooldownKey =
| "loginOtpSend"
| "signupOtpSend"
| "forgotPasswordOtpSend"
| "loginPassword"
| "loginOtpVerify"
interface FlowBranchState {
mobile: string
code: string
}
interface CooldownState {
loginOtpSend: number
signupOtpSend: number
forgotPasswordOtpSend: number
loginPassword: number
loginOtpVerify: number
}
interface AuthFlowState {
login: FlowBranchState
signup: FlowBranchState
forgotPassword: FlowBranchState
cooldowns: CooldownState
}
interface AuthFlowContextValue {
state: AuthFlowState
setMobile: (flow: FlowName, mobile: string) => void
setCode: (flow: FlowName, code: string) => void
setCooldown: (key: CooldownKey, seconds: number) => void
clearCooldown: (key: CooldownKey) => void
resetFlow: (flow: FlowName) => void
}
const STORAGE_KEY = "auth_flow_state:v1"
const defaultState: AuthFlowState = {
login: {
mobile: "",
code: "",
},
signup: {
mobile: "",
code: "",
},
forgotPassword: {
mobile: "",
code: "",
},
cooldowns: {
loginOtpSend: 0,
signupOtpSend: 0,
forgotPasswordOtpSend: 0,
loginPassword: 0,
loginOtpVerify: 0,
},
}
const AuthFlowContext = createContext<AuthFlowContextValue | null>(null)
const parseStoredState = (): AuthFlowState => {
if (typeof window === "undefined") {
return defaultState
}
const raw = window.sessionStorage.getItem(STORAGE_KEY)
if (!raw) {
return defaultState
}
try {
const parsed = JSON.parse(raw) as Partial<AuthFlowState>
return {
login: {
mobile: parsed.login?.mobile ?? "",
code: parsed.login?.code ?? "",
},
signup: {
mobile: parsed.signup?.mobile ?? "",
code: parsed.signup?.code ?? "",
},
forgotPassword: {
mobile: parsed.forgotPassword?.mobile ?? "",
code: parsed.forgotPassword?.code ?? "",
},
cooldowns: {
loginOtpSend: parsed.cooldowns?.loginOtpSend ?? 0,
signupOtpSend: parsed.cooldowns?.signupOtpSend ?? 0,
forgotPasswordOtpSend: parsed.cooldowns?.forgotPasswordOtpSend ?? 0,
loginPassword: parsed.cooldowns?.loginPassword ?? 0,
loginOtpVerify: parsed.cooldowns?.loginOtpVerify ?? 0,
},
}
} catch {
return defaultState
}
}
export function AuthFlowProvider({ children }: { children: ReactNode }) {
const [state, setState] = useState<AuthFlowState>(parseStoredState)
useEffect(() => {
window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state))
}, [state])
useEffect(() => {
if (!Object.values(state.cooldowns).some((value) => value > 0)) {
return
}
const timer = window.setInterval(() => {
setState((current) => ({
...current,
cooldowns: {
loginOtpSend: Math.max(0, current.cooldowns.loginOtpSend - 1),
signupOtpSend: Math.max(0, current.cooldowns.signupOtpSend - 1),
forgotPasswordOtpSend: Math.max(0, current.cooldowns.forgotPasswordOtpSend - 1),
loginPassword: Math.max(0, current.cooldowns.loginPassword - 1),
loginOtpVerify: Math.max(0, current.cooldowns.loginOtpVerify - 1),
},
}))
}, 1000)
return () => window.clearInterval(timer)
}, [state.cooldowns])
const value = useMemo<AuthFlowContextValue>(
() => ({
state,
setMobile: (flow, mobile) => {
setState((current) => ({
...current,
[flow]: {
...current[flow],
mobile,
},
}))
},
setCode: (flow, code) => {
setState((current) => ({
...current,
[flow]: {
...current[flow],
code,
},
}))
},
setCooldown: (key, seconds) => {
setState((current) => ({
...current,
cooldowns: {
...current.cooldowns,
[key]: Math.max(current.cooldowns[key], seconds),
},
}))
},
clearCooldown: (key) => {
setState((current) => ({
...current,
cooldowns: {
...current.cooldowns,
[key]: 0,
},
}))
},
resetFlow: (flow) => {
setState((current) => ({
...current,
[flow]: {
mobile: "",
code: "",
},
}))
},
}),
[state],
)
return <AuthFlowContext.Provider value={value}>{children}</AuthFlowContext.Provider>
}
export function useAuthFlow() {
const context = useContext(AuthFlowContext)
if (!context) {
throw new Error("useAuthFlow must be used within an AuthFlowProvider")
}
return context
}