Spaces:
Running
Running
| /** | |
| * Shared auth gate so axios 401 handler does not fight Clerk session state. | |
| * ApiInterceptor writes; client interceptor reads. | |
| */ | |
| let clerkLoaded = false; | |
| let clerkSignedIn = false; | |
| let hardRedirectArmed = false; | |
| let lastRedirectAt = 0; | |
| export function setClerkAuthState(opts: { isLoaded: boolean; isSignedIn: boolean }) { | |
| clerkLoaded = opts.isLoaded; | |
| clerkSignedIn = Boolean(opts.isSignedIn); | |
| // Allow a hard redirect only after Clerk finished loading and user is truly out | |
| hardRedirectArmed = clerkLoaded && !clerkSignedIn; | |
| } | |
| export function getClerkAuthState() { | |
| return { clerkLoaded, clerkSignedIn, hardRedirectArmed }; | |
| } | |
| /** | |
| * Should a 401 response force navigation to /sign-in? | |
| * - Never while Clerk still loading | |
| * - Never while Clerk says the user IS signed in (token race / backend lag) | |
| * - Throttle hard redirects to avoid bounce loops | |
| */ | |
| export function shouldHardRedirectOn401(): boolean { | |
| if (!clerkLoaded) return false; | |
| if (clerkSignedIn) return false; | |
| if (!hardRedirectArmed) return false; | |
| const now = Date.now(); | |
| if (now - lastRedirectAt < 2500) return false; | |
| lastRedirectAt = now; | |
| return true; | |
| } | |
| export function safeSignInRedirectPath(currentPath: string, search = ''): string { | |
| const path = currentPath || '/projects'; | |
| if ( | |
| path === '/' || | |
| path.startsWith('/sign-in') || | |
| path.startsWith('/sign-up') || | |
| path.startsWith('/pricing') || | |
| path.startsWith('/cennik') || | |
| path.startsWith('/regulamin') || | |
| path.startsWith('/polityka') | |
| ) { | |
| return '/sign-in'; | |
| } | |
| const returnTo = encodeURIComponent(path + (search || '')); | |
| return `/sign-in?redirect_url=${returnTo}`; | |
| } | |
| /** Resolve post-login landing: query redirect_url → fallback /projects (not /dashboard). */ | |
| export function resolvePostAuthRedirect(search: string): string { | |
| try { | |
| const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search); | |
| const raw = params.get('redirect_url') || params.get('redirectUrl') || ''; | |
| if (raw) { | |
| const decoded = decodeURIComponent(raw); | |
| // Only same-origin relative paths | |
| if (decoded.startsWith('/') && !decoded.startsWith('//') && !decoded.includes('://')) { | |
| // Avoid bounce: /dashboard is an alias of /projects | |
| if (decoded === '/dashboard' || decoded.startsWith('/dashboard?')) { | |
| return '/projects' + (decoded.includes('?') ? decoded.slice(decoded.indexOf('?')) : ''); | |
| } | |
| return decoded; | |
| } | |
| } | |
| } catch { | |
| /* ignore */ | |
| } | |
| return '/projects'; | |
| } | |