2025-09-09 14:37:32 +05:30
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { StackClientApp,StackProvider, StackTheme, useUser as useStackUser } from '@stackframe/stack';
|
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
|
|
2025-09-09 19:10:18 +05:30
|
|
|
import logger from '@/lib/logger';
|
|
|
|
|
|
2025-09-09 14:37:32 +05:30
|
|
|
import { StackAuthService } from '../services';
|
|
|
|
|
import type { AuthUser } from '../types';
|
|
|
|
|
import { AuthContext } from './AuthProvider';
|
|
|
|
|
|
2025-09-09 19:10:18 +05:30
|
|
|
// Create a singleton StackClientApp instance to prevent multiple initializations
|
|
|
|
|
let stackClientAppInstance: StackClientApp<true, string> | null = null;
|
|
|
|
|
|
|
|
|
|
function getStackClientApp(): StackClientApp<true, string> {
|
|
|
|
|
if (!stackClientAppInstance) {
|
|
|
|
|
logger.debug('[StackProviderWrapper] Creating singleton StackClientApp instance');
|
|
|
|
|
stackClientAppInstance = new StackClientApp({
|
|
|
|
|
tokenStore: "nextjs-cookie",
|
|
|
|
|
urls: {
|
|
|
|
|
afterSignIn: "/after-sign-in"
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return stackClientAppInstance;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-09 14:37:32 +05:30
|
|
|
interface StackProviderWrapperProps {
|
|
|
|
|
service: StackAuthService;
|
|
|
|
|
children: React.ReactNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stack-specific context provider that uses the useUser hook
|
|
|
|
|
function StackAuthContextProvider({ service, children }: { service: StackAuthService; children: React.ReactNode }) {
|
2025-09-09 19:10:18 +05:30
|
|
|
const renderCount = React.useRef(0);
|
|
|
|
|
const lastUserId = React.useRef<string | undefined>(undefined);
|
|
|
|
|
renderCount.current++;
|
|
|
|
|
|
|
|
|
|
logger.debug(`[StackAuthContextProvider] Render #${renderCount.current} - Starting`);
|
|
|
|
|
|
2025-09-09 14:37:32 +05:30
|
|
|
const stackUser = useStackUser(); // Always call the hook
|
2025-09-09 19:10:18 +05:30
|
|
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
|
|
|
|
|
|
|
|
// Track if user actually changed
|
|
|
|
|
const userChanged = lastUserId.current !== stackUser?.id;
|
|
|
|
|
if (userChanged) {
|
|
|
|
|
lastUserId.current = stackUser?.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.debug(`[StackAuthContextProvider] Render #${renderCount.current} - stackUser:`, {
|
|
|
|
|
hasUser: !!stackUser,
|
|
|
|
|
userId: stackUser?.id,
|
|
|
|
|
isInitialized,
|
|
|
|
|
userChanged
|
|
|
|
|
});
|
2025-09-09 14:37:32 +05:30
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-09-09 19:10:18 +05:30
|
|
|
// Only log and update if user actually changed
|
|
|
|
|
if (!userChanged && isInitialized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.debug('[StackAuthContextProvider] useEffect triggered (user changed)', {
|
|
|
|
|
hasUser: !!stackUser,
|
|
|
|
|
userId: stackUser?.id,
|
|
|
|
|
isInitialized,
|
|
|
|
|
isStackAuthService: service instanceof StackAuthService
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Only update the service once when user becomes available
|
|
|
|
|
if (!isInitialized && service instanceof StackAuthService && stackUser) {
|
|
|
|
|
logger.debug('[StackAuthContextProvider] Setting user instance in service', {
|
|
|
|
|
userId: stackUser.id
|
|
|
|
|
});
|
2025-09-09 14:37:32 +05:30
|
|
|
service.setUserInstance(stackUser);
|
2025-09-09 19:10:18 +05:30
|
|
|
setIsInitialized(true);
|
2025-09-09 14:37:32 +05:30
|
|
|
}
|
2025-09-09 19:10:18 +05:30
|
|
|
}, [service, stackUser, isInitialized, userChanged]);
|
|
|
|
|
|
|
|
|
|
const getAccessToken = React.useCallback(() => {
|
|
|
|
|
logger.debug('[StackAuthContextProvider] getAccessToken called');
|
|
|
|
|
return service.getAccessToken();
|
|
|
|
|
}, [service]);
|
|
|
|
|
|
|
|
|
|
// Stabilize the context value to prevent unnecessary re-renders
|
|
|
|
|
const contextValue = React.useMemo(() => {
|
|
|
|
|
const isAuth = service.isAuthenticated();
|
|
|
|
|
// IMPORTANT: Stay in loading state until service is initialized (has user set)
|
|
|
|
|
// Even if stackUser exists, we're still loading until setUserInstance is called
|
|
|
|
|
const loadingState = !isInitialized;
|
2025-09-09 14:37:32 +05:30
|
|
|
|
2025-09-09 19:10:18 +05:30
|
|
|
const value = {
|
|
|
|
|
service,
|
|
|
|
|
user: stackUser as AuthUser, // Pass the actual Stack CurrentUser
|
|
|
|
|
isAuthenticated: isAuth,
|
|
|
|
|
loading: loadingState, // Loading until service is initialized
|
|
|
|
|
getAccessToken,
|
|
|
|
|
provider: service.getProviderName(),
|
|
|
|
|
};
|
2025-09-09 14:37:32 +05:30
|
|
|
|
2025-09-09 19:10:18 +05:30
|
|
|
logger.debug('[StackAuthContextProvider] Context value created', {
|
|
|
|
|
isAuthenticated: isAuth,
|
|
|
|
|
loading: loadingState,
|
|
|
|
|
hasUser: !!value.user,
|
|
|
|
|
userId: stackUser?.id,
|
|
|
|
|
isInitialized,
|
|
|
|
|
provider: value.provider,
|
|
|
|
|
serviceHasUser: isAuth
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
}, [service, stackUser, isInitialized, getAccessToken]);
|
2025-09-09 14:37:32 +05:30
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<AuthContext.Provider value={contextValue}>
|
|
|
|
|
{children}
|
|
|
|
|
</AuthContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function StackProviderWrapper({ service, children }: StackProviderWrapperProps) {
|
2025-09-09 19:10:18 +05:30
|
|
|
logger.debug('[StackProviderWrapper] Rendering wrapper');
|
|
|
|
|
|
|
|
|
|
// Use the singleton instance
|
|
|
|
|
const stackClientApp = getStackClientApp();
|
2025-09-09 14:37:32 +05:30
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<StackProvider app={stackClientApp}>
|
|
|
|
|
<StackTheme>
|
|
|
|
|
<StackAuthContextProvider service={service}>
|
|
|
|
|
{children}
|
|
|
|
|
</StackAuthContextProvider>
|
|
|
|
|
</StackTheme>
|
|
|
|
|
</StackProvider>
|
|
|
|
|
);
|
|
|
|
|
}
|