Skip to content

Commit 461d7b2

Browse files
committed
Merge branch 'staging' of github.com:simstudioai/sim into staging
2 parents 4273161 + 54d42b3 commit 461d7b2

File tree

94 files changed

+8042
-2620
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+8042
-2620
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/api/environment/route.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import type { EnvironmentVariable } from '@/stores/settings/environment/types'
1010

1111
const logger = createLogger('EnvironmentAPI')
1212

13-
// Schema for environment variable updates
1413
const EnvVarSchema = z.object({
1514
variables: z.record(z.string()),
1615
})
@@ -30,15 +29,13 @@ export async function POST(req: NextRequest) {
3029
try {
3130
const { variables } = EnvVarSchema.parse(body)
3231

33-
// Encrypt all variables
3432
const encryptedVariables = await Promise.all(
3533
Object.entries(variables).map(async ([key, value]) => {
3634
const { encrypted } = await encryptSecret(value)
3735
return [key, encrypted] as const
3836
})
3937
).then((entries) => Object.fromEntries(entries))
4038

41-
// Replace all environment variables for user
4239
await db
4340
.insert(environment)
4441
.values({
@@ -78,7 +75,6 @@ export async function GET(request: Request) {
7875
const requestId = crypto.randomUUID().slice(0, 8)
7976

8077
try {
81-
// Get the session directly in the API route
8278
const session = await getSession()
8379
if (!session?.user?.id) {
8480
logger.warn(`[${requestId}] Unauthorized environment variables access attempt`)
@@ -97,18 +93,15 @@ export async function GET(request: Request) {
9793
return NextResponse.json({ data: {} }, { status: 200 })
9894
}
9995

100-
// Decrypt the variables for client-side use
10196
const encryptedVariables = result[0].variables as Record<string, string>
10297
const decryptedVariables: Record<string, EnvironmentVariable> = {}
10398

104-
// Decrypt each variable
10599
for (const [key, encryptedValue] of Object.entries(encryptedVariables)) {
106100
try {
107101
const { decrypted } = await decryptSecret(encryptedValue)
108102
decryptedVariables[key] = { key, value: decrypted }
109103
} catch (error) {
110104
logger.error(`[${requestId}] Error decrypting variable ${key}`, error)
111-
// If decryption fails, provide a placeholder
112105
decryptedVariables[key] = { key, value: '' }
113106
}
114107
}

0 commit comments

Comments
 (0)