Files
dukang/apps/h5-shop/src/contexts/StoreSessionContext.tsx
T
jacy 4f363fa9e7 feat(v3.4.13): partner column, version release cascade, client UX fixes
- HQ stores: partner column uses companyName/name/phone via partnerOptionLabel

- Dev plan: version RELEASED auto-marks tasks RELEASED and tickets PUBLISHED with releasedVersionNo

- mini-user/h5-shop/h5-partner v3.4.13 UX fixes (store UI, iOS scan OAuth, partner login guard)

- Docs: v3.4.13 dev doc + status audit updates

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 01:02:07 +08:00

128 lines
3.9 KiB
TypeScript

import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from 'react';
import { stripOAuthParamsFromLocation } from '@dukang/weixin-sdk';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import {
clearAuth,
ensureSession,
saveAuth,
type ShopSessionPayload,
type StoreSessionStore,
} from '../lib/api';
import {
fetchClientConfig,
handleShopWechatCallback,
handleShopWechatCallbackOnce,
handleShopWechatLoginResult,
isShopWechatUnboundError,
SHOP_WX_NEED_PHONE_LOGIN_MSG,
stashShopWechatLoginHint,
} from '../lib/wechat-auth';
import { isWechatEnv } from '../lib/weixin';
type StoreSessionContextValue = {
ready: boolean;
authenticated: boolean;
needsSelectStore: boolean;
store: StoreSessionStore | null;
applySession: (session: ShopSessionPayload) => void;
resetSession: () => void;
};
const StoreSessionContext = createContext<StoreSessionContextValue | null>(null);
function deriveSelectStore(session: ShopSessionPayload, nextStore: StoreSessionStore | null) {
const storeId = nextStore?.storeId || session.selectedStoreId || '';
const stores = session.stores ?? nextStore?.stores ?? [];
return stores.length > 1 && !storeId;
}
export function StoreSessionProvider({ children }: { children: ReactNode }) {
const [ready, setReady] = useState(false);
const [authenticated, setAuthenticated] = useState(false);
const [needsSelectStore, setNeedsSelectStore] = useState(false);
const [store, setStore] = useState<StoreSessionStore | null>(null);
const applySession = useCallback((session: ShopSessionPayload) => {
saveAuth(session);
setAuthenticated(true);
const nextStore = session.store
? {
...session.store,
stores: session.stores ?? session.store.stores,
isPrimary: session.account?.isPrimary ?? session.store.isPrimary,
}
: null;
setStore(nextStore);
setNeedsSelectStore(deriveSelectStore(session, nextStore));
}, []);
const resetSession = useCallback(() => {
clearAuth();
setAuthenticated(false);
setNeedsSelectStore(false);
setStore(null);
}, []);
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 result = await handleShopWechatCallbackOnce();
if (result && !cancelled) {
const session = handleShopWechatLoginResult(result);
if (session) applySession(session);
}
stripOAuthParamsFromLocation();
}
} catch (e) {
const raw = e instanceof Error ? e.message : '';
if (isShopWechatUnboundError(raw)) {
stashShopWechatLoginHint(SHOP_WX_NEED_PHONE_LOGIN_MSG);
}
stripOAuthParamsFromLocation();
}
}
const result = await ensureSession();
if (cancelled) return;
setAuthenticated(result.authenticated);
setStore(result.store);
setNeedsSelectStore(result.needsSelectStore);
} catch {
if (!cancelled) resetSession();
} finally {
if (!cancelled) setReady(true);
}
})();
return () => {
cancelled = true;
};
}, [applySession, resetSession]);
const value = useMemo(
() => ({ ready, authenticated, needsSelectStore, store, applySession, resetSession }),
[ready, authenticated, needsSelectStore, store, applySession, resetSession],
);
return <StoreSessionContext.Provider value={value}>{children}</StoreSessionContext.Provider>;
}
export function useStoreSession() {
const ctx = useContext(StoreSessionContext);
if (!ctx) throw new Error('useStoreSession must be used within StoreSessionProvider');
return ctx;
}