Browse Source
- AuthContext:会话级 onboardingSeen/isLoggedIn/token,completeOnboarding/login/logout - api.loginByPhone + LoginResult(mock,校验验证码>=4 位) - useLogin hook 封装 loading/error,统一从 hooks 导出master
5 changed files with 137 additions and 0 deletions
@ -0,0 +1,73 @@ |
|||
import { createContext, useContext, useState, type ReactNode } from 'react'; |
|||
|
|||
/* ===================== 鉴权(引导/登录)上下文 ===================== |
|||
* 管理「引导页是否看过」「是否已登录」「登录令牌」。 |
|||
* 与 RoleContext 配合形成完整漏斗: |
|||
* 首次启动 → 引导页 → 登录页 → 选择身份(identity-select) → 对应端。 |
|||
* |
|||
* 持久化:当前为会话级(内存)实现,App 重启后回到初始态, |
|||
* 需重新引导/登录。若已安装 @react-native-async-storage/async-storage, |
|||
* 可将底部 memoryAuth 替换为真实读写,即可跨重启记住引导/登录态。 |
|||
*/ |
|||
|
|||
export interface AuthContextValue { |
|||
/** 引导页是否已看完(首次启动展示引导页) */ |
|||
onboardingSeen: boolean; |
|||
/** 是否已登录 */ |
|||
isLoggedIn: boolean; |
|||
/** 登录令牌(mock 态为占位串) */ |
|||
token: string | null; |
|||
/** 标记引导页已看完 */ |
|||
completeOnboarding: () => void; |
|||
/** 登录成功,写入 token */ |
|||
login: (token: string) => void; |
|||
/** 退出登录,清空令牌 */ |
|||
logout: () => void; |
|||
} |
|||
|
|||
const AuthContext = createContext<AuthContextValue | undefined>(undefined); |
|||
|
|||
interface MemoryAuth { |
|||
onboardingSeen: boolean; |
|||
isLoggedIn: boolean; |
|||
token: string | null; |
|||
} |
|||
|
|||
/** 会话级内存状态(重启即重置) */ |
|||
let memoryAuth: MemoryAuth = { onboardingSeen: false, isLoggedIn: false, token: null }; |
|||
|
|||
export function AuthProvider({ children }: { children: ReactNode }) { |
|||
const [state, setState] = useState<MemoryAuth>(memoryAuth); |
|||
|
|||
const completeOnboarding = () => { |
|||
const next = { ...state, onboardingSeen: true }; |
|||
memoryAuth = next; |
|||
setState(next); |
|||
}; |
|||
|
|||
const login = (token: string) => { |
|||
const next = { ...state, isLoggedIn: true, token }; |
|||
memoryAuth = next; |
|||
setState(next); |
|||
}; |
|||
|
|||
const logout = () => { |
|||
const next = { ...state, isLoggedIn: false, token: null }; |
|||
memoryAuth = next; |
|||
setState(next); |
|||
}; |
|||
|
|||
return ( |
|||
<AuthContext.Provider value={{ ...state, completeOnboarding, login, logout }}> |
|||
{children} |
|||
</AuthContext.Provider> |
|||
); |
|||
} |
|||
|
|||
export function useAuth(): AuthContextValue { |
|||
const ctx = useContext(AuthContext); |
|||
if (!ctx) { |
|||
throw new Error('useAuth 必须在 AuthProvider 内部使用'); |
|||
} |
|||
return ctx; |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
import { useCallback, useState } from 'react'; |
|||
import { loginByPhone, type LoginResult } from '../api'; |
|||
|
|||
export interface UseLoginReturn { |
|||
/** 手机号 + 验证码登录,成功返回 LoginResult */ |
|||
login: (phone: string, code: string) => Promise<LoginResult>; |
|||
/** 登录进行中 */ |
|||
loading: boolean; |
|||
/** 登录失败的错误(用于页面提示) */ |
|||
error: Error | null; |
|||
} |
|||
|
|||
/** |
|||
* 登录(一次性变更,非取数)。 |
|||
* 包装 api.loginByPhone,向上暴露 loading / error 供页面控制按钮与提示。 |
|||
*/ |
|||
export function useLogin(): UseLoginReturn { |
|||
const [loading, setLoading] = useState(false); |
|||
const [error, setError] = useState<Error | null>(null); |
|||
|
|||
const login = useCallback(async (phone: string, code: string) => { |
|||
setLoading(true); |
|||
setError(null); |
|||
try { |
|||
const result = await loginByPhone(phone, code); |
|||
return result; |
|||
} catch (e) { |
|||
const err = e instanceof Error ? e : new Error('登录失败,请稍后重试'); |
|||
setError(err); |
|||
throw err; |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}, []); |
|||
|
|||
return { login, loading, error }; |
|||
} |
|||
Loading…
Reference in new issue