kintex/mobile/context/AuthContext.tsx

106 lines
3.0 KiB
TypeScript

/*
* 인증 컨텍스트 — JWT를 secure-store에 영속, 앱 부팅 시 복원.
* 활성 워크스페이스(행사) 선택 상태 보관. 비밀번호는 저장하지 않는다.
*/
import React, {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { setAccessToken } from '../lib/api';
import { secureDelete, secureGet, secureSet } from '../lib/secureStore';
import type { AuthUser, LoginResponse, WorkspaceDto } from '../lib/types';
const TOKEN_KEY = 'kintex.accessToken';
const USER_KEY = 'kintex.user';
interface AuthState {
ready: boolean;
token: string | null;
user: AuthUser | null;
workspaces: WorkspaceDto[];
activeEventId: string | null;
}
interface AuthContextValue extends AuthState {
activeWorkspace: WorkspaceDto | null;
signIn: (res: LoginResponse) => Promise<void>;
signOut: () => Promise<void>;
selectWorkspace: (eventId: string) => void;
}
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [state, setState] = useState<AuthState>({
ready: false,
token: null,
user: null,
workspaces: [],
activeEventId: null,
});
// 부팅 시 토큰 복원
useEffect(() => {
let cancelled = false;
(async () => {
const token = await secureGet(TOKEN_KEY);
const userRaw = await secureGet(USER_KEY);
if (cancelled) return;
const user = userRaw ? (JSON.parse(userRaw) as AuthUser) : null;
if (token) setAccessToken(token);
setState((s) => ({ ...s, ready: true, token, user }));
})();
return () => {
cancelled = true;
};
}, []);
const signIn = useCallback(async (res: LoginResponse) => {
setAccessToken(res.accessToken);
await secureSet(TOKEN_KEY, res.accessToken);
await secureSet(USER_KEY, JSON.stringify(res.user));
setState((s) => ({
...s,
token: res.accessToken,
user: res.user,
workspaces: res.workspaces ?? [],
activeEventId: res.workspaces?.[0]?.eventId ?? null,
}));
}, []);
const signOut = useCallback(async () => {
setAccessToken(null);
await secureDelete(TOKEN_KEY);
await secureDelete(USER_KEY);
setState((s) => ({
...s,
token: null,
user: null,
workspaces: [],
activeEventId: null,
}));
}, []);
const selectWorkspace = useCallback((eventId: string) => {
setState((s) => ({ ...s, activeEventId: eventId }));
}, []);
const value = useMemo<AuthContextValue>(() => {
const activeWorkspace =
state.workspaces.find((w) => w.eventId === state.activeEventId) ?? null;
return { ...state, activeWorkspace, signIn, signOut, selectWorkspace };
}, [state, signIn, signOut, selectWorkspace]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth(): AuthContextValue {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}