diff --git a/apps/sim/app/(auth)/login/login-form.tsx b/apps/sim/app/(auth)/login/login-form.tsx
index 16298b4204..ffd6c3515b 100644
--- a/apps/sim/app/(auth)/login/login-form.tsx
+++ b/apps/sim/app/(auth)/login/login-form.tsx
@@ -304,6 +304,15 @@ export default function LoginPage({
return
}
+ const emailValidation = quickValidateEmail(forgotPasswordEmail.trim().toLowerCase())
+ if (!emailValidation.isValid) {
+ setResetStatus({
+ type: 'error',
+ message: 'Please enter a valid email address',
+ })
+ return
+ }
+
try {
setIsSubmittingReset(true)
setResetStatus({ type: null, message: '' })
@@ -321,7 +330,23 @@ export default function LoginPage({
if (!response.ok) {
const errorData = await response.json()
- throw new Error(errorData.message || 'Failed to request password reset')
+ let errorMessage = errorData.message || 'Failed to request password reset'
+
+ if (
+ errorMessage.includes('Invalid body parameters') ||
+ errorMessage.includes('invalid email')
+ ) {
+ errorMessage = 'Please enter a valid email address'
+ } else if (errorMessage.includes('Email is required')) {
+ errorMessage = 'Please enter your email address'
+ } else if (
+ errorMessage.includes('user not found') ||
+ errorMessage.includes('User not found')
+ ) {
+ errorMessage = 'No account found with this email address'
+ }
+
+ throw new Error(errorMessage)
}
setResetStatus({
@@ -497,7 +522,8 @@ export default function LoginPage({
Reset Password
- Enter your email address and we'll send you a link to reset your password.
+ Enter your email address and we'll send you a link to reset your password if your
+ account exists.
@@ -512,14 +538,20 @@ export default function LoginPage({
placeholder='Enter your email'
required
type='email'
- className='border-neutral-700/80 bg-neutral-900 text-white placeholder:text-white/60 focus:border-[var(--brand-primary-hover-hex)]/70 focus:ring-[var(--brand-primary-hover-hex)]/20'
+ className={cn(
+ 'border-neutral-700/80 bg-neutral-900 text-white placeholder:text-white/60 focus:border-[var(--brand-primary-hover-hex)]/70 focus:ring-[var(--brand-primary-hover-hex)]/20',
+ resetStatus.type === 'error' && 'border-red-500 focus-visible:ring-red-500'
+ )}
/>
+ {resetStatus.type === 'error' && (
+
+
{resetStatus.message}
+
+ )}
- {resetStatus.type && (
-
- {resetStatus.message}
+ {resetStatus.type === 'success' && (
+
)}
{
})
})
- it('should prevent submission with invalid name validation', async () => {
+ it('should automatically trim spaces from name input', async () => {
const mockSignUp = vi.mocked(client.signUp.email)
+ mockSignUp.mockResolvedValue({ data: null, error: null })
render( )
@@ -176,22 +177,20 @@ describe('SignupPage', () => {
const passwordInput = screen.getByPlaceholderText(/enter your password/i)
const submitButton = screen.getByRole('button', { name: /create account/i })
- // Use name with leading/trailing spaces which should fail validation
fireEvent.change(nameInput, { target: { value: ' John Doe ' } })
fireEvent.change(emailInput, { target: { value: 'user@company.com' } })
fireEvent.change(passwordInput, { target: { value: 'Password123!' } })
fireEvent.click(submitButton)
- // Should not call signUp because validation failed
- expect(mockSignUp).not.toHaveBeenCalled()
-
- // Should show validation error
await waitFor(() => {
- expect(
- screen.getByText(
- /Name cannot contain consecutive spaces|Name cannot start or end with spaces/
- )
- ).toBeInTheDocument()
+ expect(mockSignUp).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: 'John Doe',
+ email: 'user@company.com',
+ password: 'Password123!',
+ }),
+ expect.any(Object)
+ )
})
})
diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx
index 3c645e082d..c80f89643c 100644
--- a/apps/sim/app/(auth)/signup/signup-form.tsx
+++ b/apps/sim/app/(auth)/signup/signup-form.tsx
@@ -49,10 +49,6 @@ const NAME_VALIDATIONS = {
regex: /^(?!.*\s\s).*$/,
message: 'Name cannot contain consecutive spaces.',
},
- noLeadingTrailingSpaces: {
- test: (value: string) => value === value.trim(),
- message: 'Name cannot start or end with spaces.',
- },
}
const validateEmailField = (emailValue: string): string[] => {
@@ -175,10 +171,6 @@ function SignupFormContent({
errors.push(NAME_VALIDATIONS.noConsecutiveSpaces.message)
}
- if (!NAME_VALIDATIONS.noLeadingTrailingSpaces.test(nameValue)) {
- errors.push(NAME_VALIDATIONS.noLeadingTrailingSpaces.message)
- }
-
return errors
}
@@ -193,11 +185,10 @@ function SignupFormContent({
}
const handleNameChange = (e: React.ChangeEvent) => {
- const newName = e.target.value
- setName(newName)
+ const rawValue = e.target.value
+ setName(rawValue)
- // Silently validate but don't show errors until submit
- const errors = validateName(newName)
+ const errors = validateName(rawValue)
setNameErrors(errors)
setShowNameValidationError(false)
}
@@ -224,23 +215,21 @@ function SignupFormContent({
const formData = new FormData(e.currentTarget)
const emailValue = formData.get('email') as string
const passwordValue = formData.get('password') as string
- const name = formData.get('name') as string
+ const nameValue = formData.get('name') as string
- // Validate name on submit
- const nameValidationErrors = validateName(name)
+ const trimmedName = nameValue.trim()
+
+ const nameValidationErrors = validateName(trimmedName)
setNameErrors(nameValidationErrors)
setShowNameValidationError(nameValidationErrors.length > 0)
- // Validate email on submit
const emailValidationErrors = validateEmailField(emailValue)
setEmailErrors(emailValidationErrors)
setShowEmailValidationError(emailValidationErrors.length > 0)
- // Validate password on submit
const errors = validatePassword(passwordValue)
setPasswordErrors(errors)
- // Only show validation errors if there are any
setShowValidationError(errors.length > 0)
try {
@@ -249,7 +238,6 @@ function SignupFormContent({
emailValidationErrors.length > 0 ||
errors.length > 0
) {
- // Prioritize name errors first, then email errors, then password errors
if (nameValidationErrors.length > 0) {
setNameErrors([nameValidationErrors[0]])
setShowNameValidationError(true)
@@ -266,8 +254,6 @@ function SignupFormContent({
return
}
- // Check if name will be truncated and warn user
- const trimmedName = name.trim()
if (trimmedName.length > 100) {
setNameErrors(['Name will be truncated to 100 characters. Please shorten your name.'])
setShowNameValidationError(true)
@@ -337,7 +323,6 @@ function SignupFormContent({
logger.info('Session refreshed after successful signup')
} catch (sessionError) {
logger.error('Failed to refresh session after signup:', sessionError)
- // Continue anyway - the verification flow will handle this
}
// For new signups, always require verification
diff --git a/apps/sim/app/(auth)/verify/use-verification.ts b/apps/sim/app/(auth)/verify/use-verification.ts
index 849b2bfef1..ecaf68036f 100644
--- a/apps/sim/app/(auth)/verify/use-verification.ts
+++ b/apps/sim/app/(auth)/verify/use-verification.ts
@@ -215,20 +215,28 @@ export function useVerification({
setOtp(value)
}
+ // Auto-submit when OTP is complete
+ useEffect(() => {
+ if (otp.length === 6 && email && !isLoading && !isVerified) {
+ const timeoutId = setTimeout(() => {
+ verifyCode()
+ }, 300) // Small delay to ensure UI is ready
+
+ return () => clearTimeout(timeoutId)
+ }
+ }, [otp, email, isLoading, isVerified])
+
useEffect(() => {
if (typeof window !== 'undefined') {
if (!isProduction || !hasResendKey) {
const storedEmail = sessionStorage.getItem('verificationEmail')
- logger.info('Auto-verifying user', { email: storedEmail })
}
const isDevOrDocker = !isProduction || isTruthy(env.DOCKER_BUILD)
- // Auto-verify and redirect in development/docker environments
if (isDevOrDocker || !hasResendKey) {
setIsVerified(true)
- // Clear verification requirement cookie (same as manual verification)
document.cookie =
'requiresEmailVerification=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/credential-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/credential-selector.tsx
index 6d5f05a8a5..ca5e3df6b4 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/credential-selector.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/credential-selector.tsx
@@ -279,7 +279,10 @@ export function CredentialSelector({
-
+
{isLoading ? (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/trigger-config/components/trigger-config-section.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/trigger-config/components/trigger-config-section.tsx
index 333fc1ab13..448a1ca4fb 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/trigger-config/components/trigger-config-section.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/trigger-config/components/trigger-config-section.tsx
@@ -149,9 +149,15 @@ export function TriggerConfigSection({
-
-
-
+
+
+ e.stopPropagation()}
+ >
{availableOptions.length === 0
? 'No options available. Please select credentials first.'
diff --git a/apps/sim/components/ui/switch.tsx b/apps/sim/components/ui/switch.tsx
index 7ea9f2a74d..f07fc75f05 100644
--- a/apps/sim/components/ui/switch.tsx
+++ b/apps/sim/components/ui/switch.tsx
@@ -10,7 +10,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (