import type { AuthProviderInfo, RecordModel } from 'pocketbase'; import { usePocketbase } from './usePocketbase'; export interface LoggedInUser extends RecordModel { id: string; email: string; emailVisibility: boolean; verified: boolean; name: string; avatar?: string; created: Date; updated: Date; } const user = ref(null); const loading = ref(false); const error = ref(null); let isInitialized = false; export const useAuth = () => { const pb = usePocketbase(); const router = useRouter(); const userCollection = 'users'; const initAuth = async () => { user.value = pb.authStore.record as LoggedInUser; pb.authStore.onChange((_token, model) => (user.value = (model as LoggedInUser) ?? null)); }; if (!isInitialized) { initAuth(); isInitialized = true; } const isAuthenticated = computed(() => { return !!user.value && pb.authStore.isValid; }); const authProviders = async (): Promise => { const authMethods = await pb.collection(userCollection).listAuthMethods(); return authMethods.oauth2.enabled ? authMethods.oauth2.providers : []; }; /** * Initiates OAuth login flow with the specified provider. * * Handles various error scenarios with user-friendly messages: * - **Unconfigured Provider**: "not configured" in error → Provider not set up in Pocketbase * - **Denied Authorization**: "denied" or "cancel" in error → User cancelled OAuth popup * - **Network Errors**: "network" or "fetch" in error → Connection issues * - **Generic Errors**: All other errors → Fallback message for unexpected failures * * All errors are logged to console with `[useAuth]` prefix for debugging. * * @param provider - The OAuth provider name (e.g., 'google', 'microsoft') * @throws Sets `error.value` with user-friendly message on failure * * @example * ```typescript * const { login, error } = useAuth() * * await login('google') * if (error.value) { * // Display error.value.message to user * console.log(error.value.message) // "Login was cancelled. Please try again." * } * ``` */ const login = async (provider: string) => { loading.value = true; error.value = null; try { const providers = await authProviders(); const providerData = providers.find((p) => p.name === provider); if (!providerData) { throw new Error(`${provider} OAuth is not configured`); } const response = await pb.collection(userCollection).authWithOAuth2({ provider }); user.value = response.record as LoggedInUser; } catch (pbError) { const err = pbError as Error; console.error('[useAuth] Login failed:', err); // Error categorization for user-friendly messages const message = err?.message?.toLowerCase() || ''; if (message.includes('not configured')) { error.value = new Error('This login provider is not available. Contact admin.'); } else if (message.includes('denied') || message.includes('cancel')) { error.value = new Error('Login was cancelled. Please try again.'); } else if (message.includes('network') || message.includes('fetch')) { error.value = new Error('Connection failed. Check your internet and try again.'); } else { error.value = new Error('Login failed. Please try again later.'); } } finally { loading.value = false; } }; const refreshAuth = async () => await pb.collection(userCollection).authRefresh(); const handleOAuthCallback = async () => { user.value = pb.authStore.record as LoggedInUser; if (isAuthenticated.value) { await router.push('/dashboard'); } else { await router.push('/'); } }; const logout = () => { user.value = null; error.value = null; pb.authStore.clear(); }; return { user, loading, error, isAuthenticated, initAuth, login, logout, refreshAuth, handleOAuthCallback, authProviders, }; };