mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
fix(signup): show multiple signup errors at once (#3987)
* fix(signup): show multiple signup errors at once * Fix reset password error formatting * Remove dead code * Fix unit tests --------- Co-authored-by: Theodore Li <theo@sim.ai>
This commit is contained in:
@@ -97,49 +97,49 @@ export function SetNewPasswordForm({
|
||||
}: SetNewPasswordFormProps) {
|
||||
const [password, setPassword] = useState('')
|
||||
const [confirmPassword, setConfirmPassword] = useState('')
|
||||
const [validationMessage, setValidationMessage] = useState('')
|
||||
const [validationMessages, setValidationMessages] = useState<string[]>([])
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
const errors: string[] = []
|
||||
|
||||
if (password.length < 8) {
|
||||
setValidationMessage('Password must be at least 8 characters long')
|
||||
return
|
||||
errors.push('Password must be at least 8 characters long')
|
||||
}
|
||||
|
||||
if (password.length > 100) {
|
||||
setValidationMessage('Password must not exceed 100 characters')
|
||||
return
|
||||
errors.push('Password must not exceed 100 characters')
|
||||
}
|
||||
|
||||
if (!/[A-Z]/.test(password)) {
|
||||
setValidationMessage('Password must contain at least one uppercase letter')
|
||||
return
|
||||
errors.push('Password must contain at least one uppercase letter')
|
||||
}
|
||||
|
||||
if (!/[a-z]/.test(password)) {
|
||||
setValidationMessage('Password must contain at least one lowercase letter')
|
||||
return
|
||||
errors.push('Password must contain at least one lowercase letter')
|
||||
}
|
||||
|
||||
if (!/[0-9]/.test(password)) {
|
||||
setValidationMessage('Password must contain at least one number')
|
||||
return
|
||||
errors.push('Password must contain at least one number')
|
||||
}
|
||||
|
||||
if (!/[^A-Za-z0-9]/.test(password)) {
|
||||
setValidationMessage('Password must contain at least one special character')
|
||||
return
|
||||
errors.push('Password must contain at least one special character')
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setValidationMessage('Passwords do not match')
|
||||
errors.push('Passwords do not match')
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
setValidationMessages(errors)
|
||||
return
|
||||
}
|
||||
|
||||
setValidationMessage('')
|
||||
setValidationMessages([])
|
||||
onSubmit(password)
|
||||
}
|
||||
|
||||
@@ -162,7 +162,10 @@ export function SetNewPasswordForm({
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
placeholder='Enter new password'
|
||||
className={cn('pr-10', validationMessage && 'border-red-500 focus:border-red-500')}
|
||||
className={cn(
|
||||
'pr-10',
|
||||
validationMessages.length > 0 && 'border-red-500 focus:border-red-500'
|
||||
)}
|
||||
/>
|
||||
<button
|
||||
type='button'
|
||||
@@ -190,7 +193,10 @@ export function SetNewPasswordForm({
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
placeholder='Confirm new password'
|
||||
className={cn('pr-10', validationMessage && 'border-red-500 focus:border-red-500')}
|
||||
className={cn(
|
||||
'pr-10',
|
||||
validationMessages.length > 0 && 'border-red-500 focus:border-red-500'
|
||||
)}
|
||||
/>
|
||||
<button
|
||||
type='button'
|
||||
@@ -203,9 +209,11 @@ export function SetNewPasswordForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{validationMessage && (
|
||||
{validationMessages.length > 0 && (
|
||||
<div className='mt-1 space-y-1 text-red-400 text-xs'>
|
||||
<p>{validationMessage}</p>
|
||||
{validationMessages.map((error, index) => (
|
||||
<p key={index}>{error}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -228,18 +228,6 @@ function SignupFormContent({
|
||||
emailValidationErrors.length > 0 ||
|
||||
errors.length > 0
|
||||
) {
|
||||
if (nameValidationErrors.length > 0) {
|
||||
setNameErrors([nameValidationErrors[0]])
|
||||
setShowNameValidationError(true)
|
||||
}
|
||||
if (emailValidationErrors.length > 0) {
|
||||
setEmailErrors([emailValidationErrors[0]])
|
||||
setShowEmailValidationError(true)
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
setPasswordErrors([errors[0]])
|
||||
setShowValidationError(true)
|
||||
}
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
@@ -400,7 +388,7 @@ function SignupFormContent({
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
'absolute right-0 left-0 z-10 grid transition-[grid-template-rows] duration-200 ease-out',
|
||||
'grid transition-[grid-template-rows] duration-200 ease-out',
|
||||
showNameValidationError && nameErrors.length > 0
|
||||
? 'grid-rows-[1fr]'
|
||||
: 'grid-rows-[0fr]'
|
||||
@@ -438,7 +426,7 @@ function SignupFormContent({
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
'absolute right-0 left-0 z-10 grid transition-[grid-template-rows] duration-200 ease-out',
|
||||
'grid transition-[grid-template-rows] duration-200 ease-out',
|
||||
(showEmailValidationError && emailErrors.length > 0) ||
|
||||
(emailError && !showEmailValidationError)
|
||||
? 'grid-rows-[1fr]'
|
||||
@@ -497,7 +485,7 @@ function SignupFormContent({
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'absolute right-0 left-0 z-10 grid transition-[grid-template-rows] duration-200 ease-out',
|
||||
'grid transition-[grid-template-rows] duration-200 ease-out',
|
||||
showValidationError && passwordErrors.length > 0
|
||||
? 'grid-rows-[1fr]'
|
||||
: 'grid-rows-[0fr]'
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('Reset Password API Route', () => {
|
||||
|
||||
it('should handle missing token', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
newPassword: 'newSecurePassword123',
|
||||
newPassword: 'newSecurePassword123!',
|
||||
})
|
||||
|
||||
const response = await POST(req)
|
||||
@@ -97,7 +97,7 @@ describe('Reset Password API Route', () => {
|
||||
it('should handle empty token', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
token: '',
|
||||
newPassword: 'newSecurePassword123',
|
||||
newPassword: 'newSecurePassword123!',
|
||||
})
|
||||
|
||||
const response = await POST(req)
|
||||
@@ -119,7 +119,11 @@ describe('Reset Password API Route', () => {
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
expect(data.message).toBe('Password must be at least 8 characters long')
|
||||
expect(data.message).toContain('Password must be at least 8 characters long')
|
||||
expect(data.message).toContain('Password must contain at least one uppercase letter')
|
||||
expect(data.message).toContain('Password must contain at least one lowercase letter')
|
||||
expect(data.message).toContain('Password must contain at least one number')
|
||||
expect(data.message).toContain('Password must contain at least one special character')
|
||||
|
||||
expect(mockResetPassword).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -26,8 +26,7 @@ export async function POST(request: NextRequest) {
|
||||
const validationResult = resetPasswordSchema.safeParse(body)
|
||||
|
||||
if (!validationResult.success) {
|
||||
const firstError = validationResult.error.errors[0]
|
||||
const errorMessage = firstError?.message || 'Invalid request data'
|
||||
const errorMessage = validationResult.error.errors.map((e) => e.message).join(' ')
|
||||
|
||||
logger.warn('Invalid password reset request data', {
|
||||
errors: validationResult.error.format(),
|
||||
|
||||
Reference in New Issue
Block a user