Add OTP sign-in option + Account Linking (#276)

* feat(accounts): allow linking email password
* feat(auth): add OTP sign-in
* refactor(accounts): remove 'sonner' dependency and update toast imports
* feat(supabase): enable analytics and configure database seeding
* feat(auth): update email templates and add OTP template
* feat(auth): add last sign in method hints
* feat(config): add devIndicators position to bottom-right
* feat(auth): implement comprehensive last authentication method tracking tests
This commit is contained in:
Giancarlo Buomprisco
2025-06-13 16:47:35 +07:00
committed by GitHub
parent 856e9612c4
commit 9033155fcd
87 changed files with 2580 additions and 1172 deletions

View File

@@ -0,0 +1,78 @@
'use client';
import { useCallback, useMemo, useState } from 'react';
import type { AuthMethod, LastAuthMethod } from '../utils/last-auth-method';
import {
clearLastAuthMethod,
getLastAuthMethod,
saveLastAuthMethod,
} from '../utils/last-auth-method';
export function useLastAuthMethod() {
const [lastAuthMethod, setLastAuthMethod] = useState<LastAuthMethod | null>(
getLastAuthMethod(),
);
// Save a new auth method - memoized to prevent unnecessary re-renders
const recordAuthMethod = useCallback(
(
method: AuthMethod,
options?: {
provider?: string;
email?: string;
},
) => {
const authMethod: LastAuthMethod = {
method,
provider: options?.provider,
email: options?.email,
timestamp: Date.now(),
};
saveLastAuthMethod(authMethod);
setLastAuthMethod(authMethod);
},
[],
);
// Clear the stored auth method - memoized to prevent unnecessary re-renders
const clearAuthMethod = useCallback(() => {
clearLastAuthMethod();
setLastAuthMethod(null);
}, []);
// Compute derived values using useMemo for performance
const derivedData = useMemo(() => {
if (!lastAuthMethod) {
return {
hasLastMethod: false,
methodType: null,
providerName: null,
isOAuth: false,
};
}
const isOAuth = lastAuthMethod.method === 'oauth';
const providerName =
isOAuth && lastAuthMethod.provider
? lastAuthMethod.provider.charAt(0).toUpperCase() +
lastAuthMethod.provider.slice(1)
: null;
return {
hasLastMethod: true,
methodType: lastAuthMethod.method,
providerName,
isOAuth,
};
}, [lastAuthMethod]);
return {
lastAuthMethod,
recordAuthMethod,
clearAuthMethod,
...derivedData,
};
}

View File

@@ -7,6 +7,8 @@ import { useRouter } from 'next/navigation';
import { useAppEvents } from '@kit/shared/events';
import { useSignUpWithEmailAndPassword } from '@kit/supabase/hooks/use-sign-up-with-email-password';
import { useLastAuthMethod } from './use-last-auth-method';
type SignUpCredentials = {
email: string;
password: string;
@@ -33,6 +35,7 @@ export function usePasswordSignUpFlow({
const router = useRouter();
const signUpMutation = useSignUpWithEmailAndPassword();
const appEvents = useAppEvents();
const { recordAuthMethod } = useLastAuthMethod();
const signUp = useCallback(
async (credentials: SignUpCredentials) => {
@@ -47,6 +50,9 @@ export function usePasswordSignUpFlow({
captchaToken,
});
// Record last auth method
recordAuthMethod('password', { email: credentials.email });
// emit event to track sign up
appEvents.emit({
type: 'user.signedUp',
@@ -58,6 +64,7 @@ export function usePasswordSignUpFlow({
// Update URL with success status. This is useful for password managers
// to understand that the form was submitted successfully.
const url = new URL(window.location.href);
url.searchParams.set('status', 'success');
router.replace(url.pathname + url.search);
@@ -66,6 +73,7 @@ export function usePasswordSignUpFlow({
}
} catch (error) {
console.error(error);
throw error;
} finally {
resetCaptchaToken?.();