|
|
@@ -6,6 +6,7 @@ import {
|
|
|
useContext,
|
|
|
useEffect,
|
|
|
useMemo,
|
|
|
+ useRef,
|
|
|
useState,
|
|
|
type ReactNode,
|
|
|
} from "react";
|
|
|
@@ -24,6 +25,7 @@ import { loginWithPassword } from "@/lib/auth-api";
|
|
|
import { isRegisterPasswordValid } from "@/lib/password-rules";
|
|
|
import { registerWithEmail } from "@/lib/register-api";
|
|
|
import { fetchCustomUserInfo } from "@/lib/user-info-api";
|
|
|
+import { tryFetchCustomUserLabel } from "@/lib/user-label-api";
|
|
|
|
|
|
type AuthContextValue = {
|
|
|
user: UserSession | null;
|
|
|
@@ -43,7 +45,10 @@ type AuthContextValue = {
|
|
|
logout: () => void;
|
|
|
updateProfile: (
|
|
|
patch: Partial<
|
|
|
- Pick<UserSession, "name" | "phone" | "identity" | "scholarship" | "levelLabel">
|
|
|
+ Pick<
|
|
|
+ UserSession,
|
|
|
+ "name" | "phone" | "identity" | "scholarship" | "levelLabel" | "referralCode"
|
|
|
+ >
|
|
|
>,
|
|
|
) => void;
|
|
|
addMockOrder: (order: Omit<MockOrder, "id" | "createdAt">) => void;
|
|
|
@@ -73,6 +78,7 @@ function newUserSession(
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
const [user, setUser] = useState<UserSession | null>(null);
|
|
|
const [isReady, setIsReady] = useState(false);
|
|
|
+ const skipLabelFetchRef = useRef(false);
|
|
|
useEffect(() => {
|
|
|
/* eslint-disable react-hooks/set-state-in-effect -- hydrate once from localStorage after mount */
|
|
|
setUser(loadUser());
|
|
|
@@ -80,6 +86,31 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
/* eslint-enable react-hooks/set-state-in-effect */
|
|
|
}, []);
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ if (!isReady || !user?.email) return;
|
|
|
+ if (skipLabelFetchRef.current) {
|
|
|
+ skipLabelFetchRef.current = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let cancelled = false;
|
|
|
+ void tryFetchCustomUserLabel().then((label) => {
|
|
|
+ if (cancelled || !label) return;
|
|
|
+ setUser((current) => {
|
|
|
+ if (!current) return current;
|
|
|
+ const next: UserSession = {
|
|
|
+ ...current,
|
|
|
+ levelLabel: label.levelLabel,
|
|
|
+ referralCode: label.referralCode,
|
|
|
+ };
|
|
|
+ saveUser(next);
|
|
|
+ return next;
|
|
|
+ });
|
|
|
+ });
|
|
|
+ return () => {
|
|
|
+ cancelled = true;
|
|
|
+ };
|
|
|
+ }, [isReady, user?.email]);
|
|
|
+
|
|
|
const persist = useCallback((next: UserSession | null) => {
|
|
|
setUser(next);
|
|
|
if (next) saveUser(next);
|
|
|
@@ -99,14 +130,18 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
if (token) setApiAuthToken(token);
|
|
|
const base = newUserSession(email.trim(), displayName);
|
|
|
try {
|
|
|
- const info = await fetchCustomUserInfo();
|
|
|
+ const [info, label] = await Promise.all([
|
|
|
+ fetchCustomUserInfo(),
|
|
|
+ tryFetchCustomUserLabel(),
|
|
|
+ ]);
|
|
|
persist({
|
|
|
...base,
|
|
|
email: info.email || base.email,
|
|
|
name: info.name || base.name,
|
|
|
phone: info.phone || base.phone,
|
|
|
identity: info.identity || base.identity,
|
|
|
- levelLabel: info.levelLabel,
|
|
|
+ levelLabel: label?.levelLabel,
|
|
|
+ referralCode: label?.referralCode,
|
|
|
});
|
|
|
} catch {
|
|
|
persist(base);
|
|
|
@@ -152,6 +187,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
});
|
|
|
if (token) setApiAuthToken(token);
|
|
|
const base = newUserSession(input.email.trim(), input.name.trim() || "学员");
|
|
|
+ skipLabelFetchRef.current = true;
|
|
|
try {
|
|
|
const info = await fetchCustomUserInfo();
|
|
|
persist({
|
|
|
@@ -160,7 +196,6 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
name: info.name || base.name,
|
|
|
phone: info.phone || base.phone,
|
|
|
identity: info.identity || base.identity,
|
|
|
- levelLabel: info.levelLabel,
|
|
|
});
|
|
|
} catch {
|
|
|
persist(base);
|
|
|
@@ -182,7 +217,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
const updateProfile = useCallback(
|
|
|
(
|
|
|
patch: Partial<
|
|
|
- Pick<UserSession, "name" | "phone" | "identity" | "scholarship" | "levelLabel">
|
|
|
+ Pick<
|
|
|
+ UserSession,
|
|
|
+ "name" | "phone" | "identity" | "scholarship" | "levelLabel" | "referralCode"
|
|
|
+ >
|
|
|
>,
|
|
|
) => {
|
|
|
if (!user) return;
|