Skip to content

Commit 65e8618

Browse files
authored
fix(ui): dark mode styling for switch, trigger modal UI, signup/login improvements with auto-submit for OTP (#1214)
* fix(ui): fix dark mode styling for switch, fix trigger modal UI * auto-submit OTP when characters are entered * trim leading and trailing whitespace from name on signup, throw more informative error messages on reset pass
1 parent 12135d2 commit 65e8618

File tree

7 files changed

+82
-49
lines changed

7 files changed

+82
-49
lines changed

apps/sim/app/(auth)/login/login-form.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,15 @@ export default function LoginPage({
304304
return
305305
}
306306

307+
const emailValidation = quickValidateEmail(forgotPasswordEmail.trim().toLowerCase())
308+
if (!emailValidation.isValid) {
309+
setResetStatus({
310+
type: 'error',
311+
message: 'Please enter a valid email address',
312+
})
313+
return
314+
}
315+
307316
try {
308317
setIsSubmittingReset(true)
309318
setResetStatus({ type: null, message: '' })
@@ -321,7 +330,23 @@ export default function LoginPage({
321330

322331
if (!response.ok) {
323332
const errorData = await response.json()
324-
throw new Error(errorData.message || 'Failed to request password reset')
333+
let errorMessage = errorData.message || 'Failed to request password reset'
334+
335+
if (
336+
errorMessage.includes('Invalid body parameters') ||
337+
errorMessage.includes('invalid email')
338+
) {
339+
errorMessage = 'Please enter a valid email address'
340+
} else if (errorMessage.includes('Email is required')) {
341+
errorMessage = 'Please enter your email address'
342+
} else if (
343+
errorMessage.includes('user not found') ||
344+
errorMessage.includes('User not found')
345+
) {
346+
errorMessage = 'No account found with this email address'
347+
}
348+
349+
throw new Error(errorMessage)
325350
}
326351

327352
setResetStatus({
@@ -497,7 +522,8 @@ export default function LoginPage({
497522
Reset Password
498523
</DialogTitle>
499524
<DialogDescription className='text-neutral-300 text-sm'>
500-
Enter your email address and we'll send you a link to reset your password.
525+
Enter your email address and we'll send you a link to reset your password if your
526+
account exists.
501527
</DialogDescription>
502528
</DialogHeader>
503529
<div className='space-y-4'>
@@ -512,14 +538,20 @@ export default function LoginPage({
512538
placeholder='Enter your email'
513539
required
514540
type='email'
515-
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'
541+
className={cn(
542+
'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',
543+
resetStatus.type === 'error' && 'border-red-500 focus-visible:ring-red-500'
544+
)}
516545
/>
546+
{resetStatus.type === 'error' && (
547+
<div className='mt-1 space-y-1 text-red-400 text-xs'>
548+
<p>{resetStatus.message}</p>
549+
</div>
550+
)}
517551
</div>
518-
{resetStatus.type && (
519-
<div
520-
className={`text-sm ${resetStatus.type === 'success' ? 'text-[#4CAF50]' : 'text-red-500'}`}
521-
>
522-
{resetStatus.message}
552+
{resetStatus.type === 'success' && (
553+
<div className='mt-1 space-y-1 text-[#4CAF50] text-xs'>
554+
<p>{resetStatus.message}</p>
523555
</div>
524556
)}
525557
<Button

apps/sim/app/(auth)/signup/signup-form.test.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,9 @@ describe('SignupPage', () => {
166166
})
167167
})
168168

169-
it('should prevent submission with invalid name validation', async () => {
169+
it('should automatically trim spaces from name input', async () => {
170170
const mockSignUp = vi.mocked(client.signUp.email)
171+
mockSignUp.mockResolvedValue({ data: null, error: null })
171172

172173
render(<SignupPage {...defaultProps} />)
173174

@@ -176,22 +177,20 @@ describe('SignupPage', () => {
176177
const passwordInput = screen.getByPlaceholderText(/enter your password/i)
177178
const submitButton = screen.getByRole('button', { name: /create account/i })
178179

179-
// Use name with leading/trailing spaces which should fail validation
180180
fireEvent.change(nameInput, { target: { value: ' John Doe ' } })
181181
fireEvent.change(emailInput, { target: { value: 'user@company.com' } })
182182
fireEvent.change(passwordInput, { target: { value: 'Password123!' } })
183183
fireEvent.click(submitButton)
184184

185-
// Should not call signUp because validation failed
186-
expect(mockSignUp).not.toHaveBeenCalled()
187-
188-
// Should show validation error
189185
await waitFor(() => {
190-
expect(
191-
screen.getByText(
192-
/Name cannot contain consecutive spaces|Name cannot start or end with spaces/
193-
)
194-
).toBeInTheDocument()
186+
expect(mockSignUp).toHaveBeenCalledWith(
187+
expect.objectContaining({
188+
name: 'John Doe',
189+
email: 'user@company.com',
190+
password: 'Password123!',
191+
}),
192+
expect.any(Object)
193+
)
195194
})
196195
})
197196

apps/sim/app/(auth)/signup/signup-form.tsx

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,6 @@ const NAME_VALIDATIONS = {
4949
regex: /^(?!.*\s\s).*$/,
5050
message: 'Name cannot contain consecutive spaces.',
5151
},
52-
noLeadingTrailingSpaces: {
53-
test: (value: string) => value === value.trim(),
54-
message: 'Name cannot start or end with spaces.',
55-
},
5652
}
5753

5854
const validateEmailField = (emailValue: string): string[] => {
@@ -175,10 +171,6 @@ function SignupFormContent({
175171
errors.push(NAME_VALIDATIONS.noConsecutiveSpaces.message)
176172
}
177173

178-
if (!NAME_VALIDATIONS.noLeadingTrailingSpaces.test(nameValue)) {
179-
errors.push(NAME_VALIDATIONS.noLeadingTrailingSpaces.message)
180-
}
181-
182174
return errors
183175
}
184176

@@ -193,11 +185,10 @@ function SignupFormContent({
193185
}
194186

195187
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
196-
const newName = e.target.value
197-
setName(newName)
188+
const rawValue = e.target.value
189+
setName(rawValue)
198190

199-
// Silently validate but don't show errors until submit
200-
const errors = validateName(newName)
191+
const errors = validateName(rawValue)
201192
setNameErrors(errors)
202193
setShowNameValidationError(false)
203194
}
@@ -224,23 +215,21 @@ function SignupFormContent({
224215
const formData = new FormData(e.currentTarget)
225216
const emailValue = formData.get('email') as string
226217
const passwordValue = formData.get('password') as string
227-
const name = formData.get('name') as string
218+
const nameValue = formData.get('name') as string
219+
220+
const trimmedName = nameValue.trim()
228221

229-
// Validate name on submit
230-
const nameValidationErrors = validateName(name)
222+
const nameValidationErrors = validateName(trimmedName)
231223
setNameErrors(nameValidationErrors)
232224
setShowNameValidationError(nameValidationErrors.length > 0)
233225

234-
// Validate email on submit
235226
const emailValidationErrors = validateEmailField(emailValue)
236227
setEmailErrors(emailValidationErrors)
237228
setShowEmailValidationError(emailValidationErrors.length > 0)
238229

239-
// Validate password on submit
240230
const errors = validatePassword(passwordValue)
241231
setPasswordErrors(errors)
242232

243-
// Only show validation errors if there are any
244233
setShowValidationError(errors.length > 0)
245234

246235
try {
@@ -249,7 +238,6 @@ function SignupFormContent({
249238
emailValidationErrors.length > 0 ||
250239
errors.length > 0
251240
) {
252-
// Prioritize name errors first, then email errors, then password errors
253241
if (nameValidationErrors.length > 0) {
254242
setNameErrors([nameValidationErrors[0]])
255243
setShowNameValidationError(true)
@@ -266,8 +254,6 @@ function SignupFormContent({
266254
return
267255
}
268256

269-
// Check if name will be truncated and warn user
270-
const trimmedName = name.trim()
271257
if (trimmedName.length > 100) {
272258
setNameErrors(['Name will be truncated to 100 characters. Please shorten your name.'])
273259
setShowNameValidationError(true)
@@ -337,7 +323,6 @@ function SignupFormContent({
337323
logger.info('Session refreshed after successful signup')
338324
} catch (sessionError) {
339325
logger.error('Failed to refresh session after signup:', sessionError)
340-
// Continue anyway - the verification flow will handle this
341326
}
342327

343328
// For new signups, always require verification

apps/sim/app/(auth)/verify/use-verification.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,20 +215,28 @@ export function useVerification({
215215
setOtp(value)
216216
}
217217

218+
// Auto-submit when OTP is complete
219+
useEffect(() => {
220+
if (otp.length === 6 && email && !isLoading && !isVerified) {
221+
const timeoutId = setTimeout(() => {
222+
verifyCode()
223+
}, 300) // Small delay to ensure UI is ready
224+
225+
return () => clearTimeout(timeoutId)
226+
}
227+
}, [otp, email, isLoading, isVerified])
228+
218229
useEffect(() => {
219230
if (typeof window !== 'undefined') {
220231
if (!isProduction || !hasResendKey) {
221232
const storedEmail = sessionStorage.getItem('verificationEmail')
222-
logger.info('Auto-verifying user', { email: storedEmail })
223233
}
224234

225235
const isDevOrDocker = !isProduction || isTruthy(env.DOCKER_BUILD)
226236

227-
// Auto-verify and redirect in development/docker environments
228237
if (isDevOrDocker || !hasResendKey) {
229238
setIsVerified(true)
230239

231-
// Clear verification requirement cookie (same as manual verification)
232240
document.cookie =
233241
'requiresEmailVerification=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'
234242

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/credential-selector.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,10 @@ export function CredentialSelector({
279279
</PopoverTrigger>
280280
<PopoverContent className='w-[250px] p-0' align='start'>
281281
<Command>
282-
<CommandInput placeholder='Search credentials...' />
282+
<CommandInput
283+
placeholder='Search credentials...'
284+
className='text-foreground placeholder:text-muted-foreground'
285+
/>
283286
<CommandList>
284287
<CommandEmpty>
285288
{isLoading ? (

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/trigger-config/components/trigger-config-section.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,15 @@ export function TriggerConfigSection({
149149
</Button>
150150
</PopoverTrigger>
151151
<PopoverContent className='w-[400px] p-0' align='start'>
152-
<Command>
153-
<CommandInput placeholder={`Search ${fieldDef.label.toLowerCase()}...`} />
154-
<CommandList className='max-h-[200px] overflow-y-auto'>
152+
<Command className='outline-none focus:outline-none'>
153+
<CommandInput
154+
placeholder={`Search ${fieldDef.label.toLowerCase()}...`}
155+
className='text-foreground placeholder:text-muted-foreground'
156+
/>
157+
<CommandList
158+
className='max-h-[200px] overflow-y-auto outline-none focus:outline-none'
159+
onWheel={(e) => e.stopPropagation()}
160+
>
155161
<CommandEmpty>
156162
{availableOptions.length === 0
157163
? 'No options available. Please select credentials first.'

apps/sim/components/ui/switch.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const Switch = React.forwardRef<
1010
>(({ className, ...props }, ref) => (
1111
<SwitchPrimitives.Root
1212
className={cn(
13-
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
13+
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-foreground data-[state=unchecked]:bg-input',
1414
className
1515
)}
1616
{...props}

0 commit comments

Comments
 (0)