dograh/ui/src/lib/auth/providers/AuthProvider.tsx
Abhishek Kumar 4f2a629340 Initial Commit 🚀 🚀
2025-09-09 14:37:32 +05:30

140 lines
4.1 KiB
TypeScript

'use client';
import { Loader2 } from 'lucide-react';
import React, { createContext, lazy, Suspense, useContext, useEffect, useMemo, useState } from 'react';
import { createAuthService, IAuthService, StackAuthService } from '../services';
import type { AuthUser } from '../types';
interface AuthContextType {
service: IAuthService;
user: AuthUser | null; // Union type: CurrentUser | LocalUser
isAuthenticated: boolean;
loading: boolean;
getAccessToken: () => Promise<string>;
provider: string;
}
const AuthContext = createContext<AuthContextType | null>(null);
interface AuthContextProviderProps {
service: IAuthService;
children: React.ReactNode;
}
// Lazy load Stack components only when needed
const StackProviderWrapper = lazy(() =>
import('./StackProviderWrapper').then(module => ({
default: module.StackProviderWrapper
}))
);
// Generic context provider for non-Stack providers
function GenericAuthContextProvider({ service, children }: AuthContextProviderProps) {
const [user, setUser] = useState<AuthUser | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Fetch current user
const fetchUser = async () => {
setLoading(true);
try {
const currentUser = await service.getCurrentUser();
setUser(currentUser);
} catch (error) {
console.error('Failed to fetch user:', error);
setUser(null);
} finally {
setLoading(false);
}
};
fetchUser();
}, [service]);
const getAccessToken = React.useCallback(() => service.getAccessToken(), [service]);
const contextValue: AuthContextType = React.useMemo(() => ({
service,
user,
isAuthenticated: service.isAuthenticated(),
loading,
getAccessToken,
provider: service.getProviderName(),
}), [service, user, loading, getAccessToken]);
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);
}
export function AuthProvider({ children }: { children: React.ReactNode }) {
const authProvider = process.env.NEXT_PUBLIC_AUTH_PROVIDER || 'stack';
const authService = useMemo(() => createAuthService(authProvider), [authProvider]);
// For Stack provider, wrap with StackProvider and use Stack-specific context
if (authProvider === 'stack' && authService instanceof StackAuthService) {
return (
<Suspense fallback={
<div className="flex items-center justify-center min-h-screen">
<Loader2 className="w-8 h-8 animate-spin text-gray-600" />
</div>
}>
<StackProviderWrapper service={authService}>
{children}
</StackProviderWrapper>
</Suspense>
);
}
// For other providers, use generic context provider
return (
<GenericAuthContextProvider service={authService}>
{children}
</GenericAuthContextProvider>
);
}
// Export the context for Stack-specific provider
export { AuthContext };
// Stack-specific context provider that uses the useUser hook
export function StackAuthContextProvider({ service, children }: AuthContextProviderProps) {
const [loading, setLoading] = useState(true);
const stackUser: AuthUser | null = null;
useEffect(() => {
// For Stack provider, we'll get the user from the StackProviderWrapper
// This is a placeholder that will be overridden by the actual implementation
if (service instanceof StackAuthService) {
setLoading(false);
}
}, [service]);
const getAccessToken = React.useCallback(() => service.getAccessToken(), [service]);
const contextValue: AuthContextType = React.useMemo(() => ({
service,
user: stackUser,
isAuthenticated: service.isAuthenticated(),
loading,
getAccessToken,
provider: service.getProviderName(),
}), [service, loading, getAccessToken]);
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);
}
export function useAuthContext() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuthContext must be used within AuthProvider');
}
return context;
}