feat: dashboard Second Brain, essai 7 jours et vérification e-mail
Rendre le dashboard actionnable (inbox, peek, carte mentale), aligner la facturation sur l’essai 7 jours, et bloquer le login e-mail tant que l’adresse n’est pas confirmée. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { useActionState } from 'react';
|
||||
import { useActionState, useRef, useState, Suspense } from 'react';
|
||||
import { useFormStatus } from 'react-dom';
|
||||
import { authenticate } from '@/app/actions/auth';
|
||||
import Link from 'next/link';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { Mail, Lock, ArrowRight, Sparkles } from 'lucide-react';
|
||||
import { useLanguage } from '@/lib/i18n';
|
||||
import { GoogleSignInButton } from '@/components/google-sign-in-button';
|
||||
import { resendSignupVerification } from '@/app/actions/auth-verify';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
function AuthDivider({ label }: { label: string }) {
|
||||
return (
|
||||
@@ -41,7 +44,7 @@ function LoginButton() {
|
||||
);
|
||||
}
|
||||
|
||||
export function LoginForm({
|
||||
function LoginFormInner({
|
||||
allowRegister = true,
|
||||
googleAuthEnabled = false,
|
||||
authError,
|
||||
@@ -51,7 +54,11 @@ export function LoginForm({
|
||||
authError?: string;
|
||||
}) {
|
||||
const [errorMessage, dispatch] = useActionState(authenticate, undefined);
|
||||
const { t } = useLanguage();
|
||||
const { t, language } = useLanguage();
|
||||
const searchParams = useSearchParams();
|
||||
const emailRef = useRef<HTMLInputElement>(null);
|
||||
const [resending, setResending] = useState(false);
|
||||
const verified = searchParams.get('verified') === '1';
|
||||
const oauthError =
|
||||
authError === 'SessionRequired'
|
||||
? t('auth.sessionExpired')
|
||||
@@ -59,6 +66,21 @@ export function LoginForm({
|
||||
? t('auth.oauthAccountNotLinked')
|
||||
: authError ?? null;
|
||||
|
||||
const showUnverified = errorMessage === 'EMAIL_NOT_VERIFIED';
|
||||
|
||||
const handleResend = async () => {
|
||||
const email = emailRef.current?.value?.trim() ?? '';
|
||||
if (!email) {
|
||||
toast.error(t('auth.verifyMissingEmail'));
|
||||
return;
|
||||
}
|
||||
setResending(true);
|
||||
const result = await resendSignupVerification(email, language);
|
||||
setResending(false);
|
||||
if (result.success) toast.success(t('auth.verifyResent'));
|
||||
else toast.error(t('auth.verifyResendFailed'));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-[var(--background)]/50 border border-[var(--border)] p-8 md:p-10 rounded-[48px] shadow-2xl">
|
||||
<div className="space-y-8">
|
||||
@@ -71,6 +93,12 @@ export function LoginForm({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{verified && (
|
||||
<p className="text-sm text-emerald-600 dark:text-emerald-400 text-center px-2" role="status">
|
||||
{t('auth.emailVerifiedBanner')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{oauthError && (
|
||||
<p className="text-sm text-red-500 text-center px-2" role="alert">
|
||||
{oauthError}
|
||||
@@ -94,6 +122,7 @@ export function LoginForm({
|
||||
<Mail size={16} />
|
||||
</div>
|
||||
<input
|
||||
ref={emailRef}
|
||||
className="w-full bg-slate-50 dark:bg-white/5 border border-[var(--border)] rounded-2xl py-4 pl-12 pr-4 text-sm outline-none focus:border-[var(--color-brand-accent)] focus:ring-4 ring-[var(--color-brand-accent)]/5 transition-all"
|
||||
id="email"
|
||||
type="email"
|
||||
@@ -136,13 +165,26 @@ export function LoginForm({
|
||||
|
||||
<LoginButton />
|
||||
|
||||
<div
|
||||
className="flex h-8 items-end space-x-1"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>
|
||||
{errorMessage && (
|
||||
<p className="text-sm text-red-500">{errorMessage}</p>
|
||||
<div className="space-y-2" aria-live="polite" aria-atomic="true">
|
||||
{showUnverified && (
|
||||
<div className="rounded-2xl border border-amber-500/30 bg-amber-500/10 px-4 py-3 space-y-2">
|
||||
<p className="text-sm text-amber-800 dark:text-amber-200">{t('auth.emailNotVerified')}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleResend}
|
||||
disabled={resending}
|
||||
className="text-[11px] font-bold uppercase tracking-widest text-[var(--color-brand-accent)] hover:underline disabled:opacity-50"
|
||||
>
|
||||
{resending ? t('auth.sending') : t('auth.resendVerification')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{errorMessage && !showUnverified && (
|
||||
<p className="text-sm text-red-500">
|
||||
{errorMessage === 'Invalid credentials.'
|
||||
? t('auth.invalidCredentials')
|
||||
: errorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
@@ -164,3 +206,15 @@ export function LoginForm({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function LoginForm(props: {
|
||||
allowRegister?: boolean;
|
||||
googleAuthEnabled?: boolean;
|
||||
authError?: string;
|
||||
}) {
|
||||
return (
|
||||
<Suspense fallback={<div className="p-10 text-center text-sm text-muted-foreground">…</div>}>
|
||||
<LoginFormInner {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user