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,82 @@
'use client';
import { useMemo } from 'react';
import dynamic from 'next/dynamic';
import { Lightbulb } from 'lucide-react';
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
import { useLastAuthMethod } from '../hooks/use-last-auth-method';
interface LastAuthMethodHintProps {
className?: string;
}
// we force dynamic import to avoid hydration errors
export const LastAuthMethodHint = dynamic(
async () => ({ default: LastAuthMethodHintImpl }),
{
ssr: false,
},
);
function LastAuthMethodHintImpl({ className }: LastAuthMethodHintProps) {
const { hasLastMethod, methodType, providerName, isOAuth } =
useLastAuthMethod();
// Get the appropriate translation key based on the method - memoized
// This must be called before any conditional returns to follow Rules of Hooks
const methodKey = useMemo(() => {
switch (methodType) {
case 'password':
return 'auth:methodPassword';
case 'otp':
return 'auth:methodOtp';
case 'magic_link':
return 'auth:methodMagicLink';
case 'oauth':
return 'auth:methodOauth';
default:
return null;
}
}, [methodType]);
// Don't show anything until loaded or if no last method
if (!hasLastMethod) {
return null;
}
if (!methodKey) {
return null; // If method is not recognized, don't render anything
}
return (
<div
data-test="last-auth-method-hint"
className={`text-muted-foreground/80 flex items-center justify-center gap-2 text-xs ${className || ''}`}
>
<Lightbulb className="h-3 w-3" />
<span>
<Trans i18nKey="auth:lastUsedMethodPrefix" />{' '}
<If condition={isOAuth && Boolean(providerName)}>
<Trans
i18nKey="auth:methodOauthWithProvider"
values={{ provider: providerName }}
components={{
provider: <span className="text-muted-foreground font-medium" />,
}}
/>
</If>
<If condition={!isOAuth || !providerName}>
<span className="text-muted-foreground font-medium">
<Trans i18nKey={methodKey} />
</span>
</If>
</span>
</div>
);
}