Skip to content

Commit cc2be33

Browse files
waleedlatif1icecrasher321emir-karabeg
authored
v0.5.67: loading, password reset, ui improvements, helm updates (#2928)
* fix(zustand): updated to useShallow from deprecated createWithEqualityFn (#2919) * fix(logger): use direct env access for webpack inlining (#2920) * fix(notifications): text overflow with line-clamp (#2921) * chore(helm): add env vars for Vertex AI, orgs, and telemetry (#2922) * fix(auth): improve reset password flow and consolidate brand detection (#2924) * fix(auth): improve reset password flow and consolidate brand detection * fix(auth): set errorHandled for EMAIL_NOT_VERIFIED to prevent duplicate error * fix(auth): clear success message on login errors * chore(auth): fix import order per lint * fix(action-bar): duplicate subflows with children (#2923) * fix(action-bar): duplicate subflows with children * fix(action-bar): add validateTriggerPaste for subflow duplicate * fix(resolver): agent response format, input formats, root level (#2925) * fix(resolvers): agent response format, input formats, root level * fix response block initial seeding * fix tests * fix(messages-input): fix cursor alignment and auto-resize with overlay (#2926) * fix(messages-input): fix cursor alignment and auto-resize with overlay * fixed remaining zustand warnings * fix(stores): remove dead code causing log spam on startup (#2927) * fix(stores): remove dead code causing log spam on startup * fix(stores): replace custom tools zustand store with react query cache * improvement(ui): use BrandedButton and BrandedLink components (#2930) - Refactor auth forms to use BrandedButton component - Add BrandedLink component for changelog page - Reduce code duplication in login, signup, reset-password forms - Update star count default value * fix(custom-tools): remove unsafe title fallback in getCustomTool (#2929) * fix(custom-tools): remove unsafe title fallback in getCustomTool * fix(custom-tools): restore title fallback in getCustomTool lookup Custom tools are referenced by title (custom_${title}), not database ID. The title fallback is required for client-side tool resolution to work. * fix(null-bodies): empty bodies handling (#2931) * fix(null-statuses): empty bodies handling * address bugbot comment * fix(token-refresh): microsoft, notion, x, linear (#2933) * fix(microsoft): proactive refresh needed * fix(x): missing token refresh flag * notion and linear missing flag too * address bugbot comment * fix(auth): handle EMAIL_NOT_VERIFIED in onError callback (#2932) * fix(auth): handle EMAIL_NOT_VERIFIED in onError callback * refactor(auth): extract redirectToVerify helper to reduce duplication * fix(workflow-selector): use dedicated selector for workflow dropdown (#2934) * feat(workflow-block): preview (#2935) * improvement(copilot): tool configs to show nested props (#2936) * fix(auth): add genericOAuth providers to trustedProviders (#2937) --------- Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com> Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com>
2 parents 45371e5 + 376f7cb commit cc2be33

File tree

80 files changed

+1200
-1357
lines changed

Some content is hidden

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

80 files changed

+1200
-1357
lines changed

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

Lines changed: 54 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
import { useEffect, useState } from 'react'
44
import { createLogger } from '@sim/logger'
5-
import { ArrowRight, ChevronRight, Eye, EyeOff } from 'lucide-react'
5+
import { Eye, EyeOff } from 'lucide-react'
66
import Link from 'next/link'
77
import { useRouter, useSearchParams } from 'next/navigation'
8-
import { Button } from '@/components/ui/button'
98
import {
109
Dialog,
1110
DialogContent,
@@ -22,8 +21,10 @@ import { getBaseUrl } from '@/lib/core/utils/urls'
2221
import { quickValidateEmail } from '@/lib/messaging/email/validation'
2322
import { inter } from '@/app/_styles/fonts/inter/inter'
2423
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
24+
import { BrandedButton } from '@/app/(auth)/components/branded-button'
2525
import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons'
2626
import { SSOLoginButton } from '@/app/(auth)/components/sso-login-button'
27+
import { useBrandedButtonClass } from '@/hooks/use-branded-button-class'
2728

2829
const logger = createLogger('LoginForm')
2930

@@ -105,16 +106,14 @@ export default function LoginPage({
105106
const [password, setPassword] = useState('')
106107
const [passwordErrors, setPasswordErrors] = useState<string[]>([])
107108
const [showValidationError, setShowValidationError] = useState(false)
108-
const [buttonClass, setButtonClass] = useState('branded-button-gradient')
109-
const [isButtonHovered, setIsButtonHovered] = useState(false)
109+
const buttonClass = useBrandedButtonClass()
110110

111111
const [callbackUrl, setCallbackUrl] = useState('/workspace')
112112
const [isInviteFlow, setIsInviteFlow] = useState(false)
113113

114114
const [forgotPasswordOpen, setForgotPasswordOpen] = useState(false)
115115
const [forgotPasswordEmail, setForgotPasswordEmail] = useState('')
116116
const [isSubmittingReset, setIsSubmittingReset] = useState(false)
117-
const [isResetButtonHovered, setIsResetButtonHovered] = useState(false)
118117
const [resetStatus, setResetStatus] = useState<{
119118
type: 'success' | 'error' | null
120119
message: string
@@ -123,6 +122,7 @@ export default function LoginPage({
123122
const [email, setEmail] = useState('')
124123
const [emailErrors, setEmailErrors] = useState<string[]>([])
125124
const [showEmailValidationError, setShowEmailValidationError] = useState(false)
125+
const [resetSuccessMessage, setResetSuccessMessage] = useState<string | null>(null)
126126

127127
useEffect(() => {
128128
setMounted(true)
@@ -139,32 +139,12 @@ export default function LoginPage({
139139

140140
const inviteFlow = searchParams.get('invite_flow') === 'true'
141141
setIsInviteFlow(inviteFlow)
142-
}
143-
144-
const checkCustomBrand = () => {
145-
const computedStyle = getComputedStyle(document.documentElement)
146-
const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim()
147142

148-
if (brandAccent && brandAccent !== '#6f3dfa') {
149-
setButtonClass('branded-button-custom')
150-
} else {
151-
setButtonClass('branded-button-gradient')
143+
const resetSuccess = searchParams.get('resetSuccess') === 'true'
144+
if (resetSuccess) {
145+
setResetSuccessMessage('Password reset successful. Please sign in with your new password.')
152146
}
153147
}
154-
155-
checkCustomBrand()
156-
157-
window.addEventListener('resize', checkCustomBrand)
158-
const observer = new MutationObserver(checkCustomBrand)
159-
observer.observe(document.documentElement, {
160-
attributes: true,
161-
attributeFilter: ['style', 'class'],
162-
})
163-
164-
return () => {
165-
window.removeEventListener('resize', checkCustomBrand)
166-
observer.disconnect()
167-
}
168148
}, [searchParams])
169149

170150
useEffect(() => {
@@ -202,6 +182,13 @@ export default function LoginPage({
202182
e.preventDefault()
203183
setIsLoading(true)
204184

185+
const redirectToVerify = (emailToVerify: string) => {
186+
if (typeof window !== 'undefined') {
187+
sessionStorage.setItem('verificationEmail', emailToVerify)
188+
}
189+
router.push('/verify')
190+
}
191+
205192
const formData = new FormData(e.currentTarget)
206193
const emailRaw = formData.get('email') as string
207194
const email = emailRaw.trim().toLowerCase()
@@ -221,6 +208,7 @@ export default function LoginPage({
221208

222209
try {
223210
const safeCallbackUrl = validateCallbackUrl(callbackUrl) ? callbackUrl : '/workspace'
211+
let errorHandled = false
224212

225213
const result = await client.signIn.email(
226214
{
@@ -231,11 +219,16 @@ export default function LoginPage({
231219
{
232220
onError: (ctx) => {
233221
logger.error('Login error:', ctx.error)
234-
const errorMessage: string[] = ['Invalid email or password']
235222

236223
if (ctx.error.code?.includes('EMAIL_NOT_VERIFIED')) {
224+
errorHandled = true
225+
redirectToVerify(email)
237226
return
238227
}
228+
229+
errorHandled = true
230+
const errorMessage: string[] = ['Invalid email or password']
231+
239232
if (
240233
ctx.error.code?.includes('BAD_REQUEST') ||
241234
ctx.error.message?.includes('Email and password sign in is not enabled')
@@ -271,22 +264,33 @@ export default function LoginPage({
271264
errorMessage.push('Too many requests. Please wait a moment before trying again.')
272265
}
273266

267+
setResetSuccessMessage(null)
274268
setPasswordErrors(errorMessage)
275269
setShowValidationError(true)
276270
},
277271
}
278272
)
279273

280274
if (!result || result.error) {
275+
// Show error if not already handled by onError callback
276+
if (!errorHandled) {
277+
setResetSuccessMessage(null)
278+
const errorMessage = result?.error?.message || 'Login failed. Please try again.'
279+
setPasswordErrors([errorMessage])
280+
setShowValidationError(true)
281+
}
281282
setIsLoading(false)
282283
return
283284
}
285+
286+
// Clear reset success message on successful login
287+
setResetSuccessMessage(null)
288+
289+
// Explicit redirect fallback if better-auth doesn't redirect
290+
router.push(safeCallbackUrl)
284291
} catch (err: any) {
285292
if (err.message?.includes('not verified') || err.code?.includes('EMAIL_NOT_VERIFIED')) {
286-
if (typeof window !== 'undefined') {
287-
sessionStorage.setItem('verificationEmail', email)
288-
}
289-
router.push('/verify')
293+
redirectToVerify(email)
290294
return
291295
}
292296

@@ -400,6 +404,13 @@ export default function LoginPage({
400404
</div>
401405
)}
402406

407+
{/* Password reset success message */}
408+
{resetSuccessMessage && (
409+
<div className={`${inter.className} mt-1 space-y-1 text-[#4CAF50] text-xs`}>
410+
<p>{resetSuccessMessage}</p>
411+
</div>
412+
)}
413+
403414
{/* Email/Password Form - show unless explicitly disabled */}
404415
{!isFalsy(getEnv('NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED')) && (
405416
<form onSubmit={onSubmit} className={`${inter.className} mt-8 space-y-8`}>
@@ -482,24 +493,14 @@ export default function LoginPage({
482493
</div>
483494
</div>
484495

485-
<Button
496+
<BrandedButton
486497
type='submit'
487-
onMouseEnter={() => setIsButtonHovered(true)}
488-
onMouseLeave={() => setIsButtonHovered(false)}
489-
className='group inline-flex w-full items-center justify-center gap-2 rounded-[10px] border border-[#6F3DFA] bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] py-[6px] pr-[10px] pl-[12px] text-[15px] text-white shadow-[inset_0_2px_4px_0_#9B77FF] transition-all'
490498
disabled={isLoading}
499+
loading={isLoading}
500+
loadingText='Signing in'
491501
>
492-
<span className='flex items-center gap-1'>
493-
{isLoading ? 'Signing in...' : 'Sign in'}
494-
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
495-
{isButtonHovered ? (
496-
<ArrowRight className='h-4 w-4' aria-hidden='true' />
497-
) : (
498-
<ChevronRight className='h-4 w-4' aria-hidden='true' />
499-
)}
500-
</span>
501-
</span>
502-
</Button>
502+
Sign in
503+
</BrandedButton>
503504
</form>
504505
)}
505506

@@ -610,25 +611,15 @@ export default function LoginPage({
610611
<p>{resetStatus.message}</p>
611612
</div>
612613
)}
613-
<Button
614+
<BrandedButton
614615
type='button'
615616
onClick={handleForgotPassword}
616-
onMouseEnter={() => setIsResetButtonHovered(true)}
617-
onMouseLeave={() => setIsResetButtonHovered(false)}
618-
className='group inline-flex w-full items-center justify-center gap-2 rounded-[10px] border border-[#6F3DFA] bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] py-[6px] pr-[10px] pl-[12px] text-[15px] text-white shadow-[inset_0_2px_4px_0_#9B77FF] transition-all'
619617
disabled={isSubmittingReset}
618+
loading={isSubmittingReset}
619+
loadingText='Sending'
620620
>
621-
<span className='flex items-center gap-1'>
622-
{isSubmittingReset ? 'Sending...' : 'Send Reset Link'}
623-
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
624-
{isResetButtonHovered ? (
625-
<ArrowRight className='h-4 w-4' aria-hidden='true' />
626-
) : (
627-
<ChevronRight className='h-4 w-4' aria-hidden='true' />
628-
)}
629-
</span>
630-
</span>
631-
</Button>
621+
Send Reset Link
622+
</BrandedButton>
632623
</div>
633624
</DialogContent>
634625
</Dialog>

apps/sim/app/(auth)/reset-password/reset-password-form.tsx

Lines changed: 14 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use client'
22

3-
import { useEffect, useState } from 'react'
4-
import { ArrowRight, ChevronRight, Eye, EyeOff } from 'lucide-react'
5-
import { Button } from '@/components/ui/button'
3+
import { useState } from 'react'
4+
import { Eye, EyeOff } from 'lucide-react'
65
import { Input } from '@/components/ui/input'
76
import { Label } from '@/components/ui/label'
87
import { cn } from '@/lib/core/utils/cn'
98
import { inter } from '@/app/_styles/fonts/inter/inter'
9+
import { BrandedButton } from '@/app/(auth)/components/branded-button'
1010

1111
interface RequestResetFormProps {
1212
email: string
@@ -27,36 +27,6 @@ export function RequestResetForm({
2727
statusMessage,
2828
className,
2929
}: RequestResetFormProps) {
30-
const [buttonClass, setButtonClass] = useState('branded-button-gradient')
31-
const [isButtonHovered, setIsButtonHovered] = useState(false)
32-
33-
useEffect(() => {
34-
const checkCustomBrand = () => {
35-
const computedStyle = getComputedStyle(document.documentElement)
36-
const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim()
37-
38-
if (brandAccent && brandAccent !== '#6f3dfa') {
39-
setButtonClass('branded-button-custom')
40-
} else {
41-
setButtonClass('branded-button-gradient')
42-
}
43-
}
44-
45-
checkCustomBrand()
46-
47-
window.addEventListener('resize', checkCustomBrand)
48-
const observer = new MutationObserver(checkCustomBrand)
49-
observer.observe(document.documentElement, {
50-
attributes: true,
51-
attributeFilter: ['style', 'class'],
52-
})
53-
54-
return () => {
55-
window.removeEventListener('resize', checkCustomBrand)
56-
observer.disconnect()
57-
}
58-
}, [])
59-
6030
const handleSubmit = async (e: React.FormEvent) => {
6131
e.preventDefault()
6232
onSubmit(email)
@@ -94,24 +64,14 @@ export function RequestResetForm({
9464
)}
9565
</div>
9666

97-
<Button
67+
<BrandedButton
9868
type='submit'
9969
disabled={isSubmitting}
100-
onMouseEnter={() => setIsButtonHovered(true)}
101-
onMouseLeave={() => setIsButtonHovered(false)}
102-
className='group inline-flex w-full items-center justify-center gap-2 rounded-[10px] border border-[#6F3DFA] bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] py-[6px] pr-[10px] pl-[12px] text-[15px] text-white shadow-[inset_0_2px_4px_0_#9B77FF] transition-all'
70+
loading={isSubmitting}
71+
loadingText='Sending'
10372
>
104-
<span className='flex items-center gap-1'>
105-
{isSubmitting ? 'Sending...' : 'Send Reset Link'}
106-
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
107-
{isButtonHovered ? (
108-
<ArrowRight className='h-4 w-4' aria-hidden='true' />
109-
) : (
110-
<ChevronRight className='h-4 w-4' aria-hidden='true' />
111-
)}
112-
</span>
113-
</span>
114-
</Button>
73+
Send Reset Link
74+
</BrandedButton>
11575
</form>
11676
)
11777
}
@@ -138,35 +98,6 @@ export function SetNewPasswordForm({
13898
const [validationMessage, setValidationMessage] = useState('')
13999
const [showPassword, setShowPassword] = useState(false)
140100
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
141-
const [buttonClass, setButtonClass] = useState('branded-button-gradient')
142-
const [isButtonHovered, setIsButtonHovered] = useState(false)
143-
144-
useEffect(() => {
145-
const checkCustomBrand = () => {
146-
const computedStyle = getComputedStyle(document.documentElement)
147-
const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim()
148-
149-
if (brandAccent && brandAccent !== '#6f3dfa') {
150-
setButtonClass('branded-button-custom')
151-
} else {
152-
setButtonClass('branded-button-gradient')
153-
}
154-
}
155-
156-
checkCustomBrand()
157-
158-
window.addEventListener('resize', checkCustomBrand)
159-
const observer = new MutationObserver(checkCustomBrand)
160-
observer.observe(document.documentElement, {
161-
attributes: true,
162-
attributeFilter: ['style', 'class'],
163-
})
164-
165-
return () => {
166-
window.removeEventListener('resize', checkCustomBrand)
167-
observer.disconnect()
168-
}
169-
}, [])
170101

171102
const handleSubmit = async (e: React.FormEvent) => {
172103
e.preventDefault()
@@ -296,24 +227,14 @@ export function SetNewPasswordForm({
296227
)}
297228
</div>
298229

299-
<Button
300-
disabled={isSubmitting || !token}
230+
<BrandedButton
301231
type='submit'
302-
onMouseEnter={() => setIsButtonHovered(true)}
303-
onMouseLeave={() => setIsButtonHovered(false)}
304-
className='group inline-flex w-full items-center justify-center gap-2 rounded-[10px] border border-[#6F3DFA] bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] py-[6px] pr-[10px] pl-[12px] text-[15px] text-white shadow-[inset_0_2px_4px_0_#9B77FF] transition-all'
232+
disabled={isSubmitting || !token}
233+
loading={isSubmitting}
234+
loadingText='Resetting'
305235
>
306-
<span className='flex items-center gap-1'>
307-
{isSubmitting ? 'Resetting...' : 'Reset Password'}
308-
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
309-
{isButtonHovered ? (
310-
<ArrowRight className='h-4 w-4' aria-hidden='true' />
311-
) : (
312-
<ChevronRight className='h-4 w-4' aria-hidden='true' />
313-
)}
314-
</span>
315-
</span>
316-
</Button>
236+
Reset Password
237+
</BrandedButton>
317238
</form>
318239
)
319240
}

0 commit comments

Comments
 (0)