mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(errors): fix error handling for signup/signin
This commit is contained in:
@@ -10,7 +10,6 @@ import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { client } from '@/lib/auth-client'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { useNotificationStore } from '@/stores/notifications/store'
|
||||
import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons'
|
||||
|
||||
const logger = createLogger('LoginForm')
|
||||
@@ -49,9 +48,10 @@ export default function LoginPage({
|
||||
const searchParams = useSearchParams()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const { addNotification } = useNotificationStore()
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [password, setPassword] = useState('')
|
||||
const [passwordErrors, setPasswordErrors] = useState<string[]>([])
|
||||
const [showValidationError, setShowValidationError] = useState(false)
|
||||
|
||||
// Initialize state for URL parameters
|
||||
const [callbackUrl, setCallbackUrl] = useState('/w')
|
||||
@@ -121,45 +121,47 @@ export default function LoginPage({
|
||||
{
|
||||
onError: (ctx) => {
|
||||
console.error('Login error:', ctx.error)
|
||||
let errorMessage = 'Invalid email or password'
|
||||
let errorMessage: string[] = ['Invalid email or password']
|
||||
|
||||
// Handle all possible error cases from Better Auth
|
||||
if (ctx.error.message?.includes('EMAIL_NOT_VERIFIED')) {
|
||||
if (ctx.error.code?.includes('EMAIL_NOT_VERIFIED')) {
|
||||
return
|
||||
} else if (
|
||||
ctx.error.message?.includes('BAD_REQUEST') ||
|
||||
ctx.error.code?.includes('BAD_REQUEST') ||
|
||||
ctx.error.message?.includes('Email and password sign in is not enabled')
|
||||
) {
|
||||
errorMessage = 'Email sign in is currently disabled.'
|
||||
errorMessage.push('Email sign in is currently disabled.')
|
||||
} else if (
|
||||
ctx.error.message?.includes('INVALID_CREDENTIALS') ||
|
||||
ctx.error.code?.includes('INVALID_CREDENTIALS') ||
|
||||
ctx.error.message?.includes('invalid password')
|
||||
) {
|
||||
errorMessage = 'Invalid email or password. Please try again.'
|
||||
errorMessage.push('Invalid email or password. Please try again.')
|
||||
} else if (
|
||||
ctx.error.message?.includes('USER_NOT_FOUND') ||
|
||||
ctx.error.code?.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 =
|
||||
errorMessage.push('No account found with this email. Please sign up first.')
|
||||
} else if (ctx.error.code?.includes('MISSING_CREDENTIALS')) {
|
||||
errorMessage.push('Please enter both email and password.')
|
||||
} else if (ctx.error.code?.includes('EMAIL_PASSWORD_DISABLED')) {
|
||||
errorMessage.push('Email and password login is disabled.')
|
||||
} else if (ctx.error.code?.includes('FAILED_TO_CREATE_SESSION')) {
|
||||
errorMessage.push('Failed to create session. Please try again later.')
|
||||
} else if (ctx.error.code?.includes('too many attempts')) {
|
||||
errorMessage.push(
|
||||
'Too many login attempts. Please try again later or reset your password.'
|
||||
} else if (ctx.error.message?.includes('account locked')) {
|
||||
errorMessage =
|
||||
)
|
||||
} else if (ctx.error.code?.includes('account locked')) {
|
||||
errorMessage.push(
|
||||
'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.code?.includes('network')) {
|
||||
errorMessage.push('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.'
|
||||
errorMessage.push('Too many requests. Please wait a moment before trying again.')
|
||||
}
|
||||
|
||||
addNotification('error', errorMessage, null)
|
||||
setPasswordErrors(errorMessage)
|
||||
setShowValidationError(true)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -176,7 +178,7 @@ export default function LoginPage({
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Handle only the special verification case that requires a redirect
|
||||
if (err.message?.includes('not verified') || err.message?.includes('EMAIL_NOT_VERIFIED')) {
|
||||
if (err.message?.includes('not verified') || err.code?.includes('EMAIL_NOT_VERIFIED')) {
|
||||
try {
|
||||
await client.emailOtp.sendVerificationOtp({
|
||||
email,
|
||||
@@ -190,11 +192,8 @@ export default function LoginPage({
|
||||
router.push(`/verify`)
|
||||
return
|
||||
} catch (verifyErr) {
|
||||
addNotification(
|
||||
'error',
|
||||
'Failed to send verification code. Please try again later.',
|
||||
null
|
||||
)
|
||||
setPasswordErrors(['Failed to send verification code. Please try again later.'])
|
||||
setShowValidationError(true)
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
@@ -308,7 +307,13 @@ export default function LoginPage({
|
||||
autoCorrect="off"
|
||||
placeholder="Enter your password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setPassword(e.target.value)
|
||||
if (showValidationError) {
|
||||
setShowValidationError(false)
|
||||
setPasswordErrors([])
|
||||
}
|
||||
}}
|
||||
className="bg-neutral-900 border-neutral-700 text-white pr-10"
|
||||
/>
|
||||
<button
|
||||
@@ -320,6 +325,13 @@ export default function LoginPage({
|
||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
{showValidationError && passwordErrors.length > 0 && (
|
||||
<div className="text-xs text-red-400 mt-1 space-y-1">
|
||||
{passwordErrors.map((error, index) => (
|
||||
<p key={index}>{error}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
import { Suspense, useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { Command, CornerDownLeft, Eye, EyeOff } from 'lucide-react'
|
||||
import { Eye, EyeOff } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { client } from '@/lib/auth-client'
|
||||
import { useNotificationStore } from '@/stores/notifications/store'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons'
|
||||
|
||||
const PASSWORD_VALIDATIONS = {
|
||||
@@ -41,12 +41,12 @@ function SignupFormContent({
|
||||
const searchParams = useSearchParams()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [, setMounted] = useState(false)
|
||||
const { addNotification } = useNotificationStore()
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [password, setPassword] = useState('')
|
||||
const [passwordErrors, setPasswordErrors] = useState<string[]>([])
|
||||
const [showValidationError, setShowValidationError] = useState(false)
|
||||
const [email, setEmail] = useState('')
|
||||
const [emailError, setEmailError] = useState('')
|
||||
const [waitlistToken, setWaitlistToken] = useState('')
|
||||
const [redirectUrl, setRedirectUrl] = useState('')
|
||||
const [isInviteFlow, setIsInviteFlow] = useState(false)
|
||||
@@ -144,6 +144,14 @@ function SignupFormContent({
|
||||
setShowValidationError(false)
|
||||
}
|
||||
|
||||
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setEmail(e.target.value)
|
||||
// Clear any previous email errors when the user starts typing
|
||||
if (emailError) {
|
||||
setEmailError('')
|
||||
}
|
||||
}
|
||||
|
||||
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault()
|
||||
setIsLoading(true)
|
||||
@@ -163,7 +171,8 @@ function SignupFormContent({
|
||||
try {
|
||||
if (errors.length > 0) {
|
||||
// Show first error as notification
|
||||
addNotification('error', errors[0], null)
|
||||
setPasswordErrors([errors[0]])
|
||||
setShowValidationError(true)
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
@@ -177,33 +186,42 @@ function SignupFormContent({
|
||||
{
|
||||
onError: (ctx) => {
|
||||
console.error('Signup error:', ctx.error)
|
||||
let errorMessage = 'Failed to create account'
|
||||
let errorMessage: string[] = ['Failed to create account']
|
||||
|
||||
// Handle all possible error cases from Better Auth
|
||||
if (ctx.error.status === 422 || ctx.error.message?.includes('already exists')) {
|
||||
errorMessage = 'An account with this email already exists. Please sign in instead.'
|
||||
if (ctx.error.code?.includes('USER_ALREADY_EXISTS')) {
|
||||
errorMessage.push(
|
||||
'An account with this email already exists. Please sign in instead.'
|
||||
)
|
||||
setEmailError(errorMessage[0])
|
||||
} else if (
|
||||
ctx.error.message?.includes('BAD_REQUEST') ||
|
||||
ctx.error.code?.includes('BAD_REQUEST') ||
|
||||
ctx.error.message?.includes('Email and password sign up is not enabled')
|
||||
) {
|
||||
errorMessage = 'Email signup is currently disabled.'
|
||||
} else if (ctx.error.message?.includes('INVALID_EMAIL')) {
|
||||
errorMessage = 'Please enter a valid email address.'
|
||||
} else if (ctx.error.message?.includes('PASSWORD_TOO_SHORT')) {
|
||||
errorMessage = 'Password must be at least 8 characters long.'
|
||||
} else if (ctx.error.message?.includes('MISSING_CREDENTIALS')) {
|
||||
errorMessage = 'Please enter all required fields.'
|
||||
} else if (ctx.error.message?.includes('EMAIL_PASSWORD_DISABLED')) {
|
||||
errorMessage = 'Email and password signup is disabled.'
|
||||
} else if (ctx.error.message?.includes('FAILED_TO_CREATE_USER')) {
|
||||
errorMessage = 'Failed to create account. Please try again later.'
|
||||
} 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.'
|
||||
errorMessage.push('Email signup is currently disabled.')
|
||||
setEmailError(errorMessage[0])
|
||||
} else if (ctx.error.code?.includes('INVALID_EMAIL')) {
|
||||
errorMessage.push('Please enter a valid email address.')
|
||||
setEmailError(errorMessage[0])
|
||||
} else if (ctx.error.code?.includes('PASSWORD_TOO_SHORT')) {
|
||||
errorMessage.push('Password must be at least 8 characters long.')
|
||||
setPasswordErrors(errorMessage)
|
||||
setShowValidationError(true)
|
||||
} else if (ctx.error.code?.includes('PASSWORD_TOO_LONG')) {
|
||||
errorMessage.push('Password must be less than 128 characters long.')
|
||||
setPasswordErrors(errorMessage)
|
||||
setShowValidationError(true)
|
||||
} else if (ctx.error.code?.includes('network')) {
|
||||
errorMessage.push('Network error. Please check your connection and try again.')
|
||||
setPasswordErrors(errorMessage)
|
||||
setShowValidationError(true)
|
||||
} else if (ctx.error.code?.includes('rate limit')) {
|
||||
errorMessage.push('Too many requests. Please wait a moment before trying again.')
|
||||
setPasswordErrors(errorMessage)
|
||||
setShowValidationError(true)
|
||||
} else {
|
||||
setPasswordErrors(errorMessage)
|
||||
setShowValidationError(true)
|
||||
}
|
||||
|
||||
addNotification('error', errorMessage, null)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -255,7 +273,8 @@ function SignupFormContent({
|
||||
router.push('/verify')
|
||||
} catch (error) {
|
||||
console.error('Failed to send verification code:', error)
|
||||
addNotification('error', 'Account created but failed to send verification code.', null)
|
||||
setPasswordErrors(['Account created but failed to send verification code.'])
|
||||
setShowValidationError(true)
|
||||
router.push('/login')
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -304,9 +323,17 @@ function SignupFormContent({
|
||||
autoComplete="email"
|
||||
autoCorrect="off"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="bg-neutral-900 border-neutral-700 text-white"
|
||||
onChange={handleEmailChange}
|
||||
className={cn(
|
||||
'bg-neutral-900 border-neutral-700 text-white',
|
||||
emailError && 'border-red-500 focus-visible:ring-red-500'
|
||||
)}
|
||||
/>
|
||||
{emailError && (
|
||||
<div className="text-xs text-red-400 mt-1">
|
||||
<p>{emailError}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password" className="text-neutral-300">
|
||||
|
||||
@@ -7,7 +7,6 @@ import { HelpCircle, ScrollText, Send, Settings } from 'lucide-react'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useSession } from '@/lib/auth-client'
|
||||
import { isProd } from '@/lib/environment'
|
||||
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { WorkflowMetadata } from '@/stores/workflows/registry/types'
|
||||
@@ -157,9 +156,6 @@ export function Sidebar() {
|
||||
mode === 'hover' &&
|
||||
((isHovered && !isAnyModalOpen && explicitMouseEnter) || workspaceDropdownOpen)
|
||||
|
||||
// Invite members is only shown in production
|
||||
const shouldShowInviteMembers = isProd
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={clsx(
|
||||
@@ -276,19 +272,17 @@ export function Sidebar() {
|
||||
<div className="flex-shrink-0 px-3 pb-3 pt-1">
|
||||
<div className="flex flex-col space-y-[1px]">
|
||||
{/* Invite members button */}
|
||||
{shouldShowInviteMembers && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
onClick={() => setShowInviteMembers(true)}
|
||||
className="flex items-center justify-center rounded-md text-sm font-medium text-muted-foreground hover:bg-accent/50 cursor-pointer w-8 h-8 mx-auto"
|
||||
>
|
||||
<Send className="h-[18px] w-[18px]" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Invite Members</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
onClick={() => setShowInviteMembers(true)}
|
||||
className="flex items-center justify-center rounded-md text-sm font-medium text-muted-foreground hover:bg-accent/50 cursor-pointer w-8 h-8 mx-auto"
|
||||
>
|
||||
<Send className="h-[18px] w-[18px]" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Invite Members</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{/* Help button */}
|
||||
<Tooltip>
|
||||
@@ -315,17 +309,15 @@ export function Sidebar() {
|
||||
) : (
|
||||
<>
|
||||
{/* Invite members bar */}
|
||||
{shouldShowInviteMembers && (
|
||||
<div className="flex-shrink-0 px-3 pt-1">
|
||||
<div
|
||||
onClick={() => setShowInviteMembers(true)}
|
||||
className="flex items-center rounded-md px-2 py-1.5 text-sm font-medium text-muted-foreground hover:bg-accent/50 cursor-pointer"
|
||||
>
|
||||
<Send className="h-[18px] w-[18px]" />
|
||||
<span className="ml-2">Invite members</span>
|
||||
</div>
|
||||
<div className="flex-shrink-0 px-3 pt-1">
|
||||
<div
|
||||
onClick={() => setShowInviteMembers(true)}
|
||||
className="flex items-center rounded-md px-2 py-1.5 text-sm font-medium text-muted-foreground hover:bg-accent/50 cursor-pointer"
|
||||
>
|
||||
<Send className="h-[18px] w-[18px]" />
|
||||
<span className="ml-2">Invite members</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bottom buttons container */}
|
||||
<div className="flex-shrink-0 px-3 pb-3 pt-1">
|
||||
|
||||
@@ -165,7 +165,7 @@ export const auth = betterAuth({
|
||||
otp: string
|
||||
type: 'sign-in' | 'email-verification' | 'forget-password'
|
||||
}) => {
|
||||
if (isDevOrDocker) {
|
||||
if (!isProd) {
|
||||
logger.info('Skipping email verification in dev/docker')
|
||||
return
|
||||
}
|
||||
@@ -205,7 +205,7 @@ export const auth = betterAuth({
|
||||
throw error
|
||||
}
|
||||
},
|
||||
sendVerificationOnSignUp: !isDevOrDocker,
|
||||
sendVerificationOnSignUp: isProd,
|
||||
otpLength: 6,
|
||||
expiresIn: 15 * 60,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user