import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode, } from 'react'; import { stripOAuthParamsFromLocation, toAppPath } from '@dukang/weixin-sdk'; import { isWxAuthorizeEnabled } from '@dukang/shared-types'; import type { PartnerMe, PartnerStaffRole } from '@dukang/shared-types'; import { clearAuth, ensureSession, request, saveAuth, type PartnerSessionPayload, type PartnerSessionProfile, } from '../lib/api'; import { fetchClientConfig, processPartnerWechatOAuthCallback } from '../lib/wechat-auth'; import { isWechatEnv } from '../lib/weixin'; import { toastError } from '../lib/toast'; export type PartnerAccount = PartnerMe & { staffRole?: PartnerStaffRole; }; type PartnerSessionValue = { ready: boolean; authenticated: boolean; account: PartnerAccount | null; /** @deprecated 使用 authenticated */ loggedIn: boolean; /** @deprecated 使用 ready */ loading: boolean; applySession: (session: PartnerSessionPayload) => void; refresh: () => Promise; logout: () => void; }; const PartnerSessionContext = createContext(null); function accountFromProfile(profile: PartnerSessionProfile): PartnerAccount { return { id: profile.id, name: profile.name, phone: profile.phone, companyName: profile.companyName ?? '', isPrimary: profile.isPrimary ?? true, staffRole: profile.staffRole, permissions: profile.permissions, primaryAccountId: profile.primaryAccountId, primaryPhone: profile.primaryPhone, primaryName: profile.primaryName, hasWechat: profile.hasWechat, wxNickname: profile.wxNickname, wxAvatarUrl: profile.wxAvatarUrl, managedWarehouseId: profile.managedWarehouseId, hasWarehouseAccess: profile.hasWarehouseAccess, }; } export function PartnerSessionProvider({ children }: { children: ReactNode }) { const [ready, setReady] = useState(false); const [authenticated, setAuthenticated] = useState(false); const [account, setAccount] = useState(null); const applySession = useCallback((session: PartnerSessionPayload) => { saveAuth(session); setAuthenticated(true); if (session.partner) { setAccount(accountFromProfile(session.partner)); } }, []); const refresh = useCallback(async (): Promise => { try { const result = await ensureSession(); setAuthenticated(result.authenticated); if (!result.authenticated || !result.partner) { setAccount(null); return null; } const me = await request('PARTNER_H5', '/partner/me', { silent: true }); setAccount(me); return me; } catch { setAccount(null); setAuthenticated(false); return null; } }, []); const logout = useCallback(() => { clearAuth(); setAccount(null); setAuthenticated(false); window.location.href = toAppPath('/login'); }, []); useEffect(() => { let cancelled = false; (async () => { try { const params = new URLSearchParams(window.location.search); if (isWechatEnv() && params.get('code')) { try { const config = await fetchClientConfig(); if (isWxAuthorizeEnabled(config)) { const session = await processPartnerWechatOAuthCallback(); if (session && !cancelled) { applySession(session); const me = await request('PARTNER_H5', '/partner/me', { silent: true }).catch(() => null); if (me && !cancelled) setAccount(me); } stripOAuthParamsFromLocation(); } } catch (e) { // 微信 OAuth 回跳后后端可能因账号暂停(DISABLED)等拒绝登录, // 必须把错误显式提示出来,否则用户无任何反馈(与短信路径一致)。 toastError(e instanceof Error ? e.message : '微信登录失败'); stripOAuthParamsFromLocation(); } } const result = await ensureSession(); if (cancelled) return; setAuthenticated(result.authenticated); if (result.authenticated) { const me = await request('PARTNER_H5', '/partner/me', { silent: true }).catch(() => null); if (!cancelled) setAccount(me); } else { setAccount(null); } } catch { if (!cancelled) { clearAuth({ keepProfile: true }); setAuthenticated(false); setAccount(null); } } finally { if (!cancelled) setReady(true); } })(); return () => { cancelled = true; }; }, [applySession]); const value = useMemo( () => ({ ready, authenticated, account, loggedIn: authenticated, loading: !ready, applySession, refresh, logout, }), [ready, authenticated, account, applySession, refresh, logout], ); return ( {children} ); } export function usePartnerSession(): PartnerSessionValue { const ctx = useContext(PartnerSessionContext); if (!ctx) throw new Error('usePartnerSession 必须在 PartnerSessionProvider 内使用'); return ctx; } export type { PartnerSessionProfile };