/** * Global authentication context for Parcel2Go account linking. * * Provides: * - isLinked: whether user is connected to P2G * - isLoading: auth status check in progress * - startLogin: redirect to P2G OAuth * - logout: disconnect P2G account * - error: last error message */ import { createContext, useContext, useCallback, useEffect, useState, useMemo, } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; import { getApiErrorMessage } from '../utils'; import { captureException, captureMessage } from '../sentry'; interface AuthContextValue { /** True if user is linked to Parcel2Go. */ isLinked: boolean; /** True if auth status check or login is in progress. */ isLoading: boolean; /** Last error message, if any. */ error: string | null; /** Redirect to P2G OAuth login. */ startLogin: () => void; /** Disconnect from P2G account. */ logout: () => Promise; /** Clear error state. */ clearError: () => void; } const AuthContext = createContext(null); interface AuthProviderProps { children: React.ReactNode; } export function AuthProvider({ children }: AuthProviderProps) { // Compute initial linked state from server-rendered data const initialLinked = useMemo(() => { const raw = typeof window !== 'undefined' ? window.parcel2goShipping?.authLinked : undefined; return raw === true || raw === '1' || raw === 1 || raw === 'true'; }, []); const [isLinked, setIsLinked] = useState(initialLinked); const [isStatusChecking, setIsStatusChecking] = useState(true); const [isLoginInProgress, setIsLoginInProgress] = useState(false); const [isLoggingOut, setIsLoggingOut] = useState(false); const [error, setError] = useState(null); // Check auth status on mount useEffect(() => { let isMounted = true; ( apiFetch({ path: '/parcel2go-shipping/v1/auth/status', }) as Promise<{ linked: boolean }> ) .then((res) => { if (isMounted) { setIsLinked(Boolean(res?.linked)); } }) .catch((err: unknown) => { captureMessage('Auth status check failed', 'warning', { err }); // Keep server-rendered value on failure }) .finally(() => { if (isMounted) { setIsStatusChecking(false); } }); return () => { isMounted = false; }; }, []); const startLogin = useCallback(() => { setIsLoginInProgress(true); setError(null); ( apiFetch({ path: '/parcel2go-shipping/v1/auth/authorise', method: 'POST', data: {}, }) as Promise<{ authoriseUrl: string }> ) .then(({ authoriseUrl }) => { window.location.href = authoriseUrl; }) .catch((e: unknown) => { captureException(e instanceof Error ? e : new Error(String(e))); setError( getApiErrorMessage( e, 'Failed to start Parcel2Go login. Please try again.' ) ); setIsLoginInProgress(false); }); }, []); const logout = useCallback(async () => { setIsLoggingOut(true); setError(null); try { await apiFetch({ path: '/parcel2go-shipping/v1/auth/logout', method: 'POST', data: {}, }); setIsLinked(false); } catch (e: unknown) { captureException(e instanceof Error ? e : new Error(String(e))); setError( getApiErrorMessage(e, 'Failed to log out. Please try again.') ); } finally { setIsLoggingOut(false); } }, []); const clearError = useCallback(() => { setError(null); }, []); const isLoading = isStatusChecking || isLoginInProgress || isLoggingOut; const value = useMemo( () => ({ isLinked, isLoading, error, startLogin, logout, clearError, }), [isLinked, isLoading, error, startLogin, logout, clearError] ); return ( {children} ); } /** * Access auth context. Must be used within AuthProvider. */ export function useParcel2goAuth(): AuthContextValue { const context = useContext(AuthContext); if (!context) { throw new Error('useParcel2goAuth must be used within an AuthProvider'); } return context; }