88 lines
2.3 KiB
TypeScript
88 lines
2.3 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import {
|
||
|
|
createContext,
|
||
|
|
useCallback,
|
||
|
|
useContext,
|
||
|
|
useEffect,
|
||
|
|
useState,
|
||
|
|
ReactNode,
|
||
|
|
} from "react";
|
||
|
|
import { tokenStore } from "./token-store";
|
||
|
|
import { UserProfile, verifyOtp as apiVerifyOtp, getProfileMe, ApiError } from "./api";
|
||
|
|
|
||
|
|
type AuthStatus = "loading" | "guest" | "not-admin" | "authenticated";
|
||
|
|
|
||
|
|
interface AuthContextValue {
|
||
|
|
status: AuthStatus;
|
||
|
|
user: UserProfile | null;
|
||
|
|
login: (phone: string, otp: string) => Promise<UserProfile>;
|
||
|
|
logout: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const AuthContext = createContext<AuthContextValue | null>(null);
|
||
|
|
|
||
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
||
|
|
const [status, setStatus] = useState<AuthStatus>("loading");
|
||
|
|
const [user, setUser] = useState<UserProfile | null>(null);
|
||
|
|
|
||
|
|
const bootstrap = useCallback(async () => {
|
||
|
|
const access = tokenStore.getAccess();
|
||
|
|
if (!access) {
|
||
|
|
setStatus("guest");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
const profile = await getProfileMe();
|
||
|
|
if (!profile.user.is_admin) {
|
||
|
|
setUser(profile.user);
|
||
|
|
setStatus("not-admin");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
setUser(profile.user);
|
||
|
|
setStatus("authenticated");
|
||
|
|
} catch {
|
||
|
|
tokenStore.clear();
|
||
|
|
setStatus("guest");
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||
|
|
bootstrap();
|
||
|
|
}, [bootstrap]);
|
||
|
|
|
||
|
|
const login = useCallback(async (phone: string, otp: string) => {
|
||
|
|
const data = await apiVerifyOtp(phone, otp);
|
||
|
|
tokenStore.set(data.tokens.access_token, data.tokens.refresh_token);
|
||
|
|
if (!data.user.is_admin) {
|
||
|
|
setUser(data.user);
|
||
|
|
setStatus("not-admin");
|
||
|
|
// توکن رو نگه میداریم نه، پاک میکنیم چون این پنل فقط برای ادمینهاست
|
||
|
|
tokenStore.clear();
|
||
|
|
throw new ApiError("این حساب دسترسی ادمین نداره", 403);
|
||
|
|
}
|
||
|
|
setUser(data.user);
|
||
|
|
setStatus("authenticated");
|
||
|
|
return data.user;
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const logout = useCallback(() => {
|
||
|
|
tokenStore.clear();
|
||
|
|
setUser(null);
|
||
|
|
setStatus("guest");
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<AuthContext.Provider value={{ status, user, login, logout }}>
|
||
|
|
{children}
|
||
|
|
</AuthContext.Provider>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useAuth() {
|
||
|
|
const ctx = useContext(AuthContext);
|
||
|
|
if (!ctx) throw new Error("useAuth باید داخل AuthProvider استفاده بشه");
|
||
|
|
return ctx;
|
||
|
|
}
|