feat(auth): improve otp delivery and verification flow

This commit is contained in:
2026-05-13 09:58:59 +03:30
parent be0619f5d9
commit 64a949e44f
12 changed files with 693 additions and 197 deletions

View File

@@ -11,6 +11,8 @@ export type CooldownKey =
interface FlowBranchState {
mobile: string
code: string
otpExpiresAt: number | null
pendingOtpSend: boolean
}
interface CooldownState {
@@ -32,25 +34,34 @@ interface AuthFlowContextValue {
state: AuthFlowState
setMobile: (flow: FlowName, mobile: string) => void
setCode: (flow: FlowName, code: string) => void
markOtpSendPending: (flow: FlowName) => void
setOtpDelivery: (flow: FlowName, expiresInSeconds: number) => void
clearOtpDelivery: (flow: FlowName) => void
setCooldown: (key: CooldownKey, seconds: number) => void
clearCooldown: (key: CooldownKey) => void
resetFlow: (flow: FlowName) => void
}
const STORAGE_KEY = "auth_flow_state:v1"
const STORAGE_KEY = "auth_flow_state:v2"
const defaultState: AuthFlowState = {
login: {
mobile: "",
code: "",
otpExpiresAt: null,
pendingOtpSend: false,
},
signup: {
mobile: "",
code: "",
otpExpiresAt: null,
pendingOtpSend: false,
},
forgotPassword: {
mobile: "",
code: "",
otpExpiresAt: null,
pendingOtpSend: false,
},
cooldowns: {
loginOtpSend: 0,
@@ -80,14 +91,20 @@ const parseStoredState = (): AuthFlowState => {
login: {
mobile: parsed.login?.mobile ?? "",
code: parsed.login?.code ?? "",
otpExpiresAt: parsed.login?.otpExpiresAt ?? null,
pendingOtpSend: parsed.login?.pendingOtpSend ?? false,
},
signup: {
mobile: parsed.signup?.mobile ?? "",
code: parsed.signup?.code ?? "",
otpExpiresAt: parsed.signup?.otpExpiresAt ?? null,
pendingOtpSend: parsed.signup?.pendingOtpSend ?? false,
},
forgotPassword: {
mobile: parsed.forgotPassword?.mobile ?? "",
code: parsed.forgotPassword?.code ?? "",
otpExpiresAt: parsed.forgotPassword?.otpExpiresAt ?? null,
pendingOtpSend: parsed.forgotPassword?.pendingOtpSend ?? false,
},
cooldowns: {
loginOtpSend: parsed.cooldowns?.loginOtpSend ?? 0,
@@ -151,6 +168,36 @@ export function AuthFlowProvider({ children }: { children: ReactNode }) {
},
}))
},
markOtpSendPending: (flow) => {
setState((current) => ({
...current,
[flow]: {
...current[flow],
code: "",
pendingOtpSend: true,
},
}))
},
setOtpDelivery: (flow, expiresInSeconds) => {
setState((current) => ({
...current,
[flow]: {
...current[flow],
pendingOtpSend: false,
otpExpiresAt: Date.now() + Math.max(0, expiresInSeconds) * 1000,
},
}))
},
clearOtpDelivery: (flow) => {
setState((current) => ({
...current,
[flow]: {
...current[flow],
pendingOtpSend: false,
otpExpiresAt: null,
},
}))
},
setCooldown: (key, seconds) => {
setState((current) => ({
...current,
@@ -175,6 +222,8 @@ export function AuthFlowProvider({ children }: { children: ReactNode }) {
[flow]: {
mobile: "",
code: "",
otpExpiresAt: null,
pendingOtpSend: false,
},
}))
},