perfect-postcode/frontend/src/hooks/useAuth.ts
2026-02-22 23:14:42 +00:00

170 lines
4.6 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import pb from '../lib/pocketbase';
import { trackEvent } from '../lib/analytics';
export interface AuthUser {
id: string;
email: string;
verified: boolean;
isAdmin: boolean;
subscription: string;
newsletter: boolean;
}
function recordToUser(record: { id: string; [key: string]: unknown }): AuthUser {
if (typeof record.email !== 'string') {
throw new Error('PocketBase record missing email field');
}
return {
id: record.id,
email: record.email,
verified: typeof record.verified === 'boolean' ? record.verified : false,
isAdmin: typeof record.is_admin === 'boolean' ? record.is_admin : false,
subscription: typeof record.subscription === 'string' ? record.subscription : 'free',
newsletter: typeof record.newsletter === 'boolean' ? record.newsletter : false,
};
}
export function useAuth() {
const [user, setUser] = useState<AuthUser | null>(() => {
if (pb.authStore.isValid && pb.authStore.record) {
return recordToUser(pb.authStore.record);
}
return null;
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Sync with authStore changes (cross-tab, external updates)
useEffect(() => {
const unsubscribe = pb.authStore.onChange(() => {
if (pb.authStore.isValid && pb.authStore.record) {
setUser(recordToUser(pb.authStore.record));
} else {
setUser(null);
}
});
return unsubscribe;
}, []);
const login = useCallback(async (email: string, password: string) => {
setLoading(true);
setError(null);
try {
const result = await pb.collection('users').authWithPassword(email, password);
setUser(recordToUser(result.record));
trackEvent('Login', { method: 'email' });
} catch (err) {
const msg = err instanceof Error ? err.message : 'Login failed';
setError(msg);
throw err;
} finally {
setLoading(false);
}
}, []);
const register = useCallback(async (email: string, password: string) => {
setLoading(true);
setError(null);
try {
await pb.collection('users').create({
email,
password,
passwordConfirm: password,
});
// Auto-login after registration
const result = await pb.collection('users').authWithPassword(email, password);
setUser(recordToUser(result.record));
trackEvent('Register');
} catch (err) {
const msg = err instanceof Error ? err.message : 'Registration failed';
setError(msg);
throw err;
} finally {
setLoading(false);
}
}, []);
const loginWithOAuth = useCallback(async (provider: string) => {
setLoading(true);
setError(null);
try {
const result = await pb.collection('users').authWithOAuth2({ provider });
setUser(recordToUser(result.record));
trackEvent('Login', { method: provider });
} catch (err) {
const msg = err instanceof Error ? err.message : 'OAuth login failed';
setError(msg);
throw err;
} finally {
setLoading(false);
}
}, []);
const logout = useCallback(() => {
trackEvent('Logout');
pb.authStore.clear();
setUser(null);
}, []);
const requestPasswordReset = useCallback(async (email: string) => {
setLoading(true);
setError(null);
try {
await pb.collection('users').requestPasswordReset(email);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Password reset request failed';
setError(msg);
throw err;
} finally {
setLoading(false);
}
}, []);
const refreshAuth = useCallback(async () => {
setLoading(true);
setError(null);
try {
const result = await pb.collection('users').authRefresh();
setUser(recordToUser(result.record));
} catch (err) {
const msg = err instanceof Error ? err.message : 'Auth refresh failed';
setError(msg);
throw err;
} finally {
setLoading(false);
}
}, []);
const requestVerification = useCallback(async (email: string) => {
setLoading(true);
setError(null);
try {
await pb.collection('users').requestVerification(email);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Verification request failed';
setError(msg);
throw err;
} finally {
setLoading(false);
}
}, []);
const clearError = useCallback(() => {
setError(null);
}, []);
return {
user,
loading,
error,
login,
register,
loginWithOAuth,
logout,
requestPasswordReset,
requestVerification,
refreshAuth,
clearError,
};
}