4f363fa9e7
- 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>
128 lines
3.9 KiB
TypeScript
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;
|
|
}
|