From 0f94d83b273581342e5e7bf0eae1633aee14d78d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 22 Mar 2025 15:43:54 -0700 Subject: [PATCH] improvement(auth): updated email template for auth, general login improvements with pw validation and better error handling --- sim/app/(auth)/login/login-form.tsx | 132 +++++++++++----- sim/app/(auth)/signup/signup-form.tsx | 179 ++++++++++++++++++---- sim/app/(auth)/verify/use-verification.ts | 16 +- sim/app/(auth)/verify/verify-content.tsx | 40 ++++- sim/app/emails/otp-verification-email.tsx | 11 +- sim/app/emails/reset-password-email.tsx | 8 +- 6 files changed, 303 insertions(+), 83 deletions(-) diff --git a/sim/app/(auth)/login/login-form.tsx b/sim/app/(auth)/login/login-form.tsx index 7d0c5d5b98..09aef6e473 100644 --- a/sim/app/(auth)/login/login-form.tsx +++ b/sim/app/(auth)/login/login-form.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' +import { Eye, EyeOff } from 'lucide-react' import { Button } from '@/components/ui/button' import { Card, @@ -35,8 +36,10 @@ export default function LoginPage({ }) { const router = useRouter() const [isLoading, setIsLoading] = useState(false) - const [mounted, setMounted] = useState(false) + const [, setMounted] = useState(false) const { addNotification } = useNotificationStore() + const [showPassword, setShowPassword] = useState(false) + const [password, setPassword] = useState('') // Forgot password states const [forgotPasswordOpen, setForgotPasswordOpen] = useState(false) @@ -57,53 +60,91 @@ export default function LoginPage({ const formData = new FormData(e.currentTarget) const email = formData.get('email') as string - const password = formData.get('password') as string try { - const result = await client.signIn.email({ - email, - password, - callbackURL: '/w', - }) + const result = await client.signIn.email( + { + email, + password, + callbackURL: '/w', + }, + { + onError: (ctx) => { + console.error('Login error:', ctx.error) + let errorMessage = 'Invalid email or password' + + // Handle all possible error cases from Better Auth + if (ctx.error.message?.includes('EMAIL_NOT_VERIFIED')) { + return + } else if ( + ctx.error.message?.includes('BAD_REQUEST') || + ctx.error.message?.includes('Email and password sign in is not enabled') + ) { + errorMessage = 'Email sign in is currently disabled.' + } else if ( + ctx.error.message?.includes('INVALID_CREDENTIALS') || + ctx.error.message?.includes('invalid password') + ) { + errorMessage = 'Invalid email or password. Please try again.' + } else if ( + ctx.error.message?.includes('USER_NOT_FOUND') || + ctx.error.message?.includes('not found') + ) { + errorMessage = 'No account found with this email. Please sign up first.' + } else if (ctx.error.message?.includes('MISSING_CREDENTIALS')) { + errorMessage = 'Please enter both email and password.' + } else if (ctx.error.message?.includes('EMAIL_PASSWORD_DISABLED')) { + errorMessage = 'Email and password login is disabled.' + } else if (ctx.error.message?.includes('FAILED_TO_CREATE_SESSION')) { + errorMessage = 'Failed to create session. Please try again later.' + } else if (ctx.error.message?.includes('too many attempts')) { + errorMessage = + 'Too many login attempts. Please try again later or reset your password.' + } else if (ctx.error.message?.includes('account locked')) { + errorMessage = + 'Your account has been locked for security. Please reset your password.' + } else if (ctx.error.message?.includes('network')) { + errorMessage = 'Network error. Please check your connection and try again.' + } else if (ctx.error.message?.includes('rate limit')) { + errorMessage = 'Too many requests. Please wait a moment before trying again.' + } + + addNotification('error', errorMessage, null) + }, + } + ) if (!result || result.error) { - throw new Error(result?.error?.message || 'Authentication failed') + setIsLoading(false) + return } } catch (err: any) { - let errorMessage = 'Invalid email or password' - - if (err.message?.includes('not verified')) { - // Redirect to verification page directly without asking for confirmation + // Handle only the special verification case that requires a redirect + if (err.message?.includes('not verified') || err.message?.includes('EMAIL_NOT_VERIFIED')) { try { - // Send a new verification OTP await client.emailOtp.sendVerificationOtp({ email, type: 'email-verification', }) - // Redirect to the verify page - router.push(`/verify?email=${encodeURIComponent(email)}`) + if (typeof window !== 'undefined') { + sessionStorage.setItem('verificationEmail', email) + } + + router.push(`/verify`) return } catch (verifyErr) { - errorMessage = 'Failed to send verification code. Please try again later.' + addNotification( + 'error', + 'Failed to send verification code. Please try again later.', + null + ) + setIsLoading(false) + return } - } else if (err.message?.includes('not found')) { - errorMessage = 'No account found with this email. Please sign up first.' - } else if (err.message?.includes('invalid password')) { - errorMessage = 'Invalid password. Please try again or use the forgot password link.' - } else if (err.message?.includes('too many attempts')) { - errorMessage = 'Too many login attempts. Please try again later or reset your password.' - } else if (err.message?.includes('account locked')) { - errorMessage = 'Your account has been locked for security. Please reset your password.' - } else if (err.message?.includes('network')) { - errorMessage = 'Network error. Please check your connection and try again.' - } else if (err.message?.includes('rate limit')) { - errorMessage = 'Too many requests. Please wait a moment before trying again.' } - addNotification('error', errorMessage, null) - // Prevent navigation on error - return + console.error('Uncaught login error:', err) } finally { setIsLoading(false) } @@ -160,7 +201,8 @@ export default function LoginPage({ return (
- {mounted && } + {/* Ensure NotificationList is always rendered */} +

Sim Studio

@@ -211,13 +253,25 @@ export default function LoginPage({ Forgot password?
- +
+ setPassword(e.target.value)} + /> + +
+ + {showValidationError && passwordErrors.length > 0 && ( +
+

Password must:

+
    + {passwordErrors.map((error, index) => ( +
  • {error}
  • + ))} +
+
+ )} + {countdown > 0 ? ( + + Resend in {countdown}s + + ) : ( + + )}

)} diff --git a/sim/app/emails/otp-verification-email.tsx b/sim/app/emails/otp-verification-email.tsx index a9aff68e4f..fe0208988f 100644 --- a/sim/app/emails/otp-verification-email.tsx +++ b/sim/app/emails/otp-verification-email.tsx @@ -12,8 +12,9 @@ import { Section, Text, } from '@react-email/components' +import { XIcon } from 'lucide-react' +import { DiscordIcon } from '@/components/icons' import { baseStyles } from './base-styles' -import { DiscordIcon, XIcon } from './email-icons' interface OTPVerificationEmailProps { otp: string @@ -47,13 +48,17 @@ export const OTPVerificationEmail = ({ {getSubjectByType(type)} -
+
Sim Studio
diff --git a/sim/app/emails/reset-password-email.tsx b/sim/app/emails/reset-password-email.tsx index c8ed66d287..5468616ae3 100644 --- a/sim/app/emails/reset-password-email.tsx +++ b/sim/app/emails/reset-password-email.tsx @@ -39,13 +39,17 @@ export const ResetPasswordEmail = ({ Reset your Sim Studio password -
+
Sim Studio