Skip to content

Commit 19442f1

Browse files
authored
v0.6.7: kb improvements, edge z index fix, captcha, new trust center, block classifications
2 parents 1731a4d + 951c8fd commit 19442f1

File tree

256 files changed

+16970
-1086
lines changed

Some content is hidden

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

256 files changed

+16970
-1086
lines changed

.claude/commands/add-block.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ When the user asks you to create a block:
1919
```typescript
2020
import { {ServiceName}Icon } from '@/components/icons'
2121
import type { BlockConfig } from '@/blocks/types'
22-
import { AuthMode } from '@/blocks/types'
22+
import { AuthMode, IntegrationType } from '@/blocks/types'
2323
import { getScopesForService } from '@/lib/oauth/utils'
2424

2525
export const {ServiceName}Block: BlockConfig = {
@@ -29,6 +29,8 @@ export const {ServiceName}Block: BlockConfig = {
2929
longDescription: 'Detailed description for docs',
3030
docsLink: 'https://docs.sim.ai/tools/{service}',
3131
category: 'tools', // 'tools' | 'blocks' | 'triggers'
32+
integrationType: IntegrationType.X, // Primary category (see IntegrationType enum)
33+
tags: ['oauth', 'api'], // Cross-cutting tags (see IntegrationTag type)
3234
bgColor: '#HEXCOLOR', // Brand color
3335
icon: {ServiceName}Icon,
3436

@@ -629,7 +631,7 @@ export const registry: Record<string, BlockConfig> = {
629631
```typescript
630632
import { ServiceIcon } from '@/components/icons'
631633
import type { BlockConfig } from '@/blocks/types'
632-
import { AuthMode } from '@/blocks/types'
634+
import { AuthMode, IntegrationType } from '@/blocks/types'
633635
import { getScopesForService } from '@/lib/oauth/utils'
634636

635637
export const ServiceBlock: BlockConfig = {
@@ -639,6 +641,8 @@ export const ServiceBlock: BlockConfig = {
639641
longDescription: 'Full description for documentation...',
640642
docsLink: 'https://docs.sim.ai/tools/service',
641643
category: 'tools',
644+
integrationType: IntegrationType.DeveloperTools,
645+
tags: ['oauth', 'api'],
642646
bgColor: '#FF6B6B',
643647
icon: ServiceIcon,
644648
authMode: AuthMode.OAuth,
@@ -796,6 +800,8 @@ All tool IDs referenced in `tools.access` and returned by `tools.config.tool` MU
796800

797801
## Checklist Before Finishing
798802

803+
- [ ] `integrationType` is set to the correct `IntegrationType` enum value
804+
- [ ] `tags` array includes all applicable `IntegrationTag` values
799805
- [ ] All subBlocks have `id`, `title` (except switch), and `type`
800806
- [ ] Conditions use correct syntax (field, value, not, and)
801807
- [ ] DependsOn set for fields that need other values

.claude/commands/add-integration.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export const {service}{Action}Tool: ToolConfig<Params, Response> = {
113113
```typescript
114114
import { {Service}Icon } from '@/components/icons'
115115
import type { BlockConfig } from '@/blocks/types'
116-
import { AuthMode } from '@/blocks/types'
116+
import { AuthMode, IntegrationType } from '@/blocks/types'
117117
import { getScopesForService } from '@/lib/oauth/utils'
118118

119119
export const {Service}Block: BlockConfig = {
@@ -123,6 +123,8 @@ export const {Service}Block: BlockConfig = {
123123
longDescription: '...',
124124
docsLink: 'https://docs.sim.ai/tools/{service}',
125125
category: 'tools',
126+
integrationType: IntegrationType.X, // Primary category (see IntegrationType enum)
127+
tags: ['oauth', 'api'], // Cross-cutting tags (see IntegrationTag type)
126128
bgColor: '#HEXCOLOR',
127129
icon: {Service}Icon,
128130
authMode: AuthMode.OAuth, // or AuthMode.ApiKey
@@ -410,6 +412,8 @@ If creating V2 versions (API-aligned outputs):
410412

411413
### Block
412414
- [ ] Created `blocks/blocks/{service}.ts`
415+
- [ ] Set `integrationType` to the correct `IntegrationType` enum value
416+
- [ ] Set `tags` array with all applicable `IntegrationTag` values
413417
- [ ] Defined operation dropdown with all operations
414418
- [ ] Added credential field with `requiredScopes: getScopesForService('{service}')`
415419
- [ ] Added conditional fields per operation
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Skeleton } from '@/components/emcn'
2+
3+
export default function LoginLoading() {
4+
return (
5+
<div className='flex flex-col items-center'>
6+
<Skeleton className='h-[38px] w-[80px] rounded-[4px]' />
7+
<div className='mt-[32px] w-full space-y-[8px]'>
8+
<Skeleton className='h-[14px] w-[40px] rounded-[4px]' />
9+
<Skeleton className='h-[44px] w-full rounded-[10px]' />
10+
</div>
11+
<div className='mt-[16px] w-full space-y-[8px]'>
12+
<Skeleton className='h-[14px] w-[64px] rounded-[4px]' />
13+
<Skeleton className='h-[44px] w-full rounded-[10px]' />
14+
</div>
15+
<Skeleton className='mt-[24px] h-[44px] w-full rounded-[10px]' />
16+
<Skeleton className='mt-[24px] h-[1px] w-full rounded-[1px]' />
17+
<div className='mt-[24px] flex w-full gap-[12px]'>
18+
<Skeleton className='h-[44px] flex-1 rounded-[10px]' />
19+
<Skeleton className='h-[44px] flex-1 rounded-[10px]' />
20+
</div>
21+
<Skeleton className='mt-[24px] h-[14px] w-[200px] rounded-[4px]' />
22+
</div>
23+
)
24+
}

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

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

3-
import { useEffect, useRef, useState } from 'react'
3+
import { useMemo, useRef, useState } from 'react'
4+
import { Turnstile, type TurnstileInstance } from '@marsidev/react-turnstile'
45
import { createLogger } from '@sim/logger'
56
import { Eye, EyeOff } from 'lucide-react'
67
import Link from 'next/link'
@@ -86,6 +87,9 @@ export default function LoginPage({
8687
const [password, setPassword] = useState('')
8788
const [passwordErrors, setPasswordErrors] = useState<string[]>([])
8889
const [showValidationError, setShowValidationError] = useState(false)
90+
const [formError, setFormError] = useState<string | null>(null)
91+
const turnstileRef = useRef<TurnstileInstance>(null)
92+
const turnstileSiteKey = useMemo(() => getEnv('NEXT_PUBLIC_TURNSTILE_SITE_KEY'), [])
8993
const buttonClass = useBrandedButtonClass()
9094

9195
const callbackUrlParam = searchParams?.get('callbackUrl')
@@ -115,19 +119,6 @@ export default function LoginPage({
115119
: null
116120
)
117121

118-
useEffect(() => {
119-
const handleKeyDown = (event: KeyboardEvent) => {
120-
if (event.key === 'Enter' && forgotPasswordOpen) {
121-
handleForgotPassword()
122-
}
123-
}
124-
125-
window.addEventListener('keydown', handleKeyDown)
126-
return () => {
127-
window.removeEventListener('keydown', handleKeyDown)
128-
}
129-
}, [forgotPasswordEmail, forgotPasswordOpen])
130-
131122
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
132123
const newEmail = e.target.value
133124
setEmail(newEmail)
@@ -178,13 +169,33 @@ export default function LoginPage({
178169
const safeCallbackUrl = callbackUrl
179170
let errorHandled = false
180171

172+
// Execute Turnstile challenge on submit and get a fresh token
173+
let token: string | undefined
174+
if (turnstileSiteKey && turnstileRef.current) {
175+
try {
176+
turnstileRef.current.reset()
177+
turnstileRef.current.execute()
178+
token = await turnstileRef.current.getResponsePromise(15_000)
179+
} catch {
180+
setFormError('Captcha verification failed. Please try again.')
181+
setIsLoading(false)
182+
return
183+
}
184+
}
185+
186+
setFormError(null)
181187
const result = await client.signIn.email(
182188
{
183189
email,
184190
password,
185191
callbackURL: safeCallbackUrl,
186192
},
187193
{
194+
fetchOptions: {
195+
headers: {
196+
...(token ? { 'x-captcha-response': token } : {}),
197+
},
198+
},
188199
onError: (ctx) => {
189200
logger.error('Login error:', ctx.error)
190201

@@ -460,6 +471,20 @@ export default function LoginPage({
460471
</div>
461472
</div>
462473

474+
{turnstileSiteKey && (
475+
<Turnstile
476+
ref={turnstileRef}
477+
siteKey={turnstileSiteKey}
478+
options={{ size: 'invisible', execution: 'execute' }}
479+
/>
480+
)}
481+
482+
{formError && (
483+
<div className='text-red-400 text-xs'>
484+
<p>{formError}</p>
485+
</div>
486+
)}
487+
463488
<BrandedButton
464489
type='submit'
465490
disabled={isLoading}
@@ -540,45 +565,51 @@ export default function LoginPage({
540565
<ModalContent className='dark' size='sm'>
541566
<ModalHeader>Reset Password</ModalHeader>
542567
<ModalBody>
543-
<ModalDescription className='mb-4 text-[var(--text-muted)] text-sm'>
544-
Enter your email address and we'll send you a link to reset your password if your
545-
account exists.
546-
</ModalDescription>
547-
<div className='space-y-4'>
548-
<div className='space-y-2'>
549-
<Label htmlFor='reset-email'>Email</Label>
550-
<Input
551-
id='reset-email'
552-
value={forgotPasswordEmail}
553-
onChange={(e) => setForgotPasswordEmail(e.target.value)}
554-
placeholder='Enter your email'
555-
required
556-
type='email'
557-
className={cn(
558-
resetStatus.type === 'error' && 'border-red-500 focus:border-red-500'
568+
<form
569+
onSubmit={(e) => {
570+
e.preventDefault()
571+
handleForgotPassword()
572+
}}
573+
>
574+
<ModalDescription className='mb-4 text-[var(--text-muted)] text-sm'>
575+
Enter your email address and we'll send you a link to reset your password if your
576+
account exists.
577+
</ModalDescription>
578+
<div className='space-y-4'>
579+
<div className='space-y-2'>
580+
<Label htmlFor='reset-email'>Email</Label>
581+
<Input
582+
id='reset-email'
583+
value={forgotPasswordEmail}
584+
onChange={(e) => setForgotPasswordEmail(e.target.value)}
585+
placeholder='Enter your email'
586+
required
587+
type='email'
588+
className={cn(
589+
resetStatus.type === 'error' && 'border-red-500 focus:border-red-500'
590+
)}
591+
/>
592+
{resetStatus.type === 'error' && (
593+
<div className='mt-1 text-red-400 text-xs'>
594+
<p>{resetStatus.message}</p>
595+
</div>
559596
)}
560-
/>
561-
{resetStatus.type === 'error' && (
562-
<div className='mt-1 text-red-400 text-xs'>
597+
</div>
598+
{resetStatus.type === 'success' && (
599+
<div className='mt-1 text-[#4CAF50] text-xs'>
563600
<p>{resetStatus.message}</p>
564601
</div>
565602
)}
603+
<BrandedButton
604+
type='submit'
605+
disabled={isSubmittingReset}
606+
loading={isSubmittingReset}
607+
loadingText='Sending'
608+
>
609+
Send Reset Link
610+
</BrandedButton>
566611
</div>
567-
{resetStatus.type === 'success' && (
568-
<div className='mt-1 text-[#4CAF50] text-xs'>
569-
<p>{resetStatus.message}</p>
570-
</div>
571-
)}
572-
<BrandedButton
573-
type='button'
574-
onClick={handleForgotPassword}
575-
disabled={isSubmittingReset}
576-
loading={isSubmittingReset}
577-
loadingText='Sending'
578-
>
579-
Send Reset Link
580-
</BrandedButton>
581-
</div>
612+
</form>
582613
</ModalBody>
583614
</ModalContent>
584615
</Modal>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Skeleton } from '@/components/emcn'
2+
3+
export default function OAuthConsentLoading() {
4+
return (
5+
<div className='flex flex-col items-center'>
6+
<div className='flex items-center gap-[16px]'>
7+
<Skeleton className='h-[48px] w-[48px] rounded-[12px]' />
8+
<Skeleton className='h-[20px] w-[20px] rounded-[4px]' />
9+
<Skeleton className='h-[48px] w-[48px] rounded-[12px]' />
10+
</div>
11+
<Skeleton className='mt-[24px] h-[38px] w-[220px] rounded-[4px]' />
12+
<Skeleton className='mt-[8px] h-[14px] w-[280px] rounded-[4px]' />
13+
<Skeleton className='mt-[24px] h-[56px] w-full rounded-[8px]' />
14+
<Skeleton className='mt-[16px] h-[120px] w-full rounded-[8px]' />
15+
<div className='mt-[24px] flex w-full max-w-[410px] gap-[12px]'>
16+
<Skeleton className='h-[44px] flex-1 rounded-[10px]' />
17+
<Skeleton className='h-[44px] flex-1 rounded-[10px]' />
18+
</div>
19+
</div>
20+
)
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Skeleton } from '@/components/emcn'
2+
3+
export default function ResetPasswordLoading() {
4+
return (
5+
<div className='flex flex-col items-center'>
6+
<Skeleton className='h-[38px] w-[160px] rounded-[4px]' />
7+
<Skeleton className='mt-[12px] h-[14px] w-[280px] rounded-[4px]' />
8+
<div className='mt-[32px] w-full space-y-[8px]'>
9+
<Skeleton className='h-[14px] w-[40px] rounded-[4px]' />
10+
<Skeleton className='h-[44px] w-full rounded-[10px]' />
11+
</div>
12+
<Skeleton className='mt-[24px] h-[44px] w-full rounded-[10px]' />
13+
<Skeleton className='mt-[24px] h-[14px] w-[120px] rounded-[4px]' />
14+
</div>
15+
)
16+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Skeleton } from '@/components/emcn'
2+
3+
export default function SignupLoading() {
4+
return (
5+
<div className='flex flex-col items-center'>
6+
<Skeleton className='h-[38px] w-[100px] rounded-[4px]' />
7+
<div className='mt-[32px] w-full space-y-[8px]'>
8+
<Skeleton className='h-[14px] w-[40px] rounded-[4px]' />
9+
<Skeleton className='h-[44px] w-full rounded-[10px]' />
10+
</div>
11+
<div className='mt-[16px] w-full space-y-[8px]'>
12+
<Skeleton className='h-[14px] w-[40px] rounded-[4px]' />
13+
<Skeleton className='h-[44px] w-full rounded-[10px]' />
14+
</div>
15+
<div className='mt-[16px] w-full space-y-[8px]'>
16+
<Skeleton className='h-[14px] w-[64px] rounded-[4px]' />
17+
<Skeleton className='h-[44px] w-full rounded-[10px]' />
18+
</div>
19+
<Skeleton className='mt-[24px] h-[44px] w-full rounded-[10px]' />
20+
<Skeleton className='mt-[24px] h-[1px] w-full rounded-[1px]' />
21+
<div className='mt-[24px] flex w-full gap-[12px]'>
22+
<Skeleton className='h-[44px] flex-1 rounded-[10px]' />
23+
<Skeleton className='h-[44px] flex-1 rounded-[10px]' />
24+
</div>
25+
<Skeleton className='mt-[24px] h-[14px] w-[220px] rounded-[4px]' />
26+
</div>
27+
)
28+
}

0 commit comments

Comments
 (0)