Skip to content

Commit b9cc9bc

Browse files
committed
feat(emcn): add SearchInput component and unify search bars platform-wide
- Add SearchInput to emcn: 30px chip-family filled search input matching the integrations page pattern (border-1, surface-5, leading Search icon) - Migrate all 22 search bars across settings, EE pages, and integrations to SearchInput (only layout classes allowed at callsites) - Rename Sim Keys -> Sim API Keys in nav/title; page copy now says API key - Remove components/ui input, label, and verified-badge; migrate consumers to emcn equivalents or raw inputs (table cell editor, wand prompt bar) - Delete dead EE skeleton files (data-drains, data-retention) - General settings: Home Page chip moves to header left as navigation
1 parent e495979 commit b9cc9bc

46 files changed

Lines changed: 754 additions & 900 deletions

File tree

Some content is hidden

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

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

Lines changed: 54 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import { Eye, EyeOff } from 'lucide-react'
77
import Link from 'next/link'
88
import { useRouter, useSearchParams } from 'next/navigation'
99
import {
10+
Chip,
11+
ChipModal,
12+
ChipModalBody,
13+
ChipModalError,
14+
ChipModalField,
15+
ChipModalFooter,
16+
ChipModalHeader,
1017
Input,
1118
Label,
1219
Loader,
13-
Modal,
14-
ModalBody,
15-
ModalContent,
16-
ModalDescription,
17-
ModalHeader,
1820
} from '@/components/emcn'
1921
import { requestJson } from '@/lib/api/client/request'
2022
import { forgetPasswordContract } from '@/lib/api/contracts'
@@ -527,60 +529,53 @@ export default function LoginPage({
527529
</Link>
528530
</div>
529531

530-
<Modal open={forgotPasswordOpen} onOpenChange={setForgotPasswordOpen}>
531-
<ModalContent className='dark' size='sm'>
532-
<ModalHeader>Reset Password</ModalHeader>
533-
<ModalBody>
534-
<form
535-
onSubmit={(e) => {
536-
e.preventDefault()
537-
handleForgotPassword()
538-
}}
539-
>
540-
<ModalDescription className='mb-4 text-[var(--text-muted)] text-sm'>
541-
Enter your email address and we'll send you a link to reset your password if your
542-
account exists.
543-
</ModalDescription>
544-
<div className='space-y-4'>
545-
<div className='space-y-2'>
546-
<Label htmlFor='reset-email'>Email</Label>
547-
<Input
548-
id='reset-email'
549-
value={forgotPasswordEmail}
550-
onChange={(e) => setForgotPasswordEmail(e.target.value)}
551-
placeholder='Enter your email'
552-
required
553-
type='email'
554-
className={cn(
555-
resetStatus.type === 'error' && 'border-red-500 focus:border-red-500'
556-
)}
557-
/>
558-
{resetStatus.type === 'error' && (
559-
<div className='mt-1 text-red-400 text-xs'>
560-
<p>{resetStatus.message}</p>
561-
</div>
562-
)}
563-
</div>
564-
{resetStatus.type === 'success' && (
565-
<div className='mt-1 text-[#4CAF50] text-xs'>
566-
<p>{resetStatus.message}</p>
567-
</div>
568-
)}
569-
<button type='submit' disabled={isSubmittingReset} className={AUTH_SUBMIT_BTN}>
570-
{isSubmittingReset ? (
571-
<span className='flex items-center gap-2'>
572-
<Loader className='size-4' animate />
573-
Sending…
574-
</span>
575-
) : (
576-
'Send Reset Link'
577-
)}
578-
</button>
579-
</div>
580-
</form>
581-
</ModalBody>
582-
</ModalContent>
583-
</Modal>
532+
<ChipModal
533+
open={forgotPasswordOpen}
534+
onOpenChange={setForgotPasswordOpen}
535+
srTitle='Reset Password'
536+
>
537+
<ChipModalHeader onClose={() => setForgotPasswordOpen(false)}>
538+
Reset Password
539+
</ChipModalHeader>
540+
<ChipModalBody>
541+
<p className='px-2 text-[var(--text-secondary)] text-sm'>
542+
Enter your email address and we'll send you a link to reset your password if your
543+
account exists.
544+
</p>
545+
<ChipModalField
546+
type='email'
547+
title='Email'
548+
value={forgotPasswordEmail}
549+
onChange={(value) => setForgotPasswordEmail(value)}
550+
required
551+
placeholder='you@example.com'
552+
/>
553+
{resetStatus.type === 'success' && (
554+
<p className='px-2 text-[var(--text-secondary)] text-sm'>{resetStatus.message}</p>
555+
)}
556+
<ChipModalError>
557+
{resetStatus.type === 'error' ? resetStatus.message : null}
558+
</ChipModalError>
559+
</ChipModalBody>
560+
<ChipModalFooter>
561+
<Chip
562+
variant='filled'
563+
flush
564+
onClick={() => setForgotPasswordOpen(false)}
565+
disabled={isSubmittingReset}
566+
>
567+
Cancel
568+
</Chip>
569+
<Chip
570+
variant='primary'
571+
flush
572+
onClick={handleForgotPassword}
573+
disabled={!forgotPasswordEmail || isSubmittingReset}
574+
>
575+
{isSubmittingReset ? 'Sending…' : 'Send Reset Link'}
576+
</Chip>
577+
</ChipModalFooter>
578+
</ChipModal>
584579
</>
585580
)
586581
}

apps/sim/app/(landing)/integrations/components/request-integration-modal.tsx

Lines changed: 112 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@
22

33
import { useCallback, useState } from 'react'
44
import {
5-
Button,
6-
Input,
7-
Label,
8-
Modal,
9-
ModalBody,
10-
ModalContent,
11-
ModalDescription,
12-
ModalFooter,
13-
ModalHeader,
14-
Textarea,
5+
Chip,
6+
ChipModal,
7+
ChipModalBody,
8+
ChipModalError,
9+
ChipModalField,
10+
ChipModalFooter,
11+
ChipModalHeader,
1512
} from '@/components/emcn'
1613
import { requestJson } from '@/lib/api/client/request'
1714
import { integrationRequestContract } from '@/lib/api/contracts/common'
@@ -41,30 +38,26 @@ export function RequestIntegrationModal() {
4138
[resetForm]
4239
)
4340

44-
const handleSubmit = useCallback(
45-
async (e: React.FormEvent) => {
46-
e.preventDefault()
47-
if (!integrationName.trim() || !email.trim()) return
41+
const handleSubmit = useCallback(async () => {
42+
if (!integrationName.trim() || !email.trim()) return
4843

49-
setStatus('submitting')
44+
setStatus('submitting')
5045

51-
try {
52-
await requestJson(integrationRequestContract, {
53-
body: {
54-
integrationName: integrationName.trim(),
55-
email: email.trim(),
56-
useCase: useCase.trim() || undefined,
57-
},
58-
})
46+
try {
47+
await requestJson(integrationRequestContract, {
48+
body: {
49+
integrationName: integrationName.trim(),
50+
email: email.trim(),
51+
useCase: useCase.trim() || undefined,
52+
},
53+
})
5954

60-
setStatus('success')
61-
setTimeout(() => setOpen(false), 1500)
62-
} catch {
63-
setStatus('error')
64-
}
65-
},
66-
[integrationName, email, useCase]
67-
)
55+
setStatus('success')
56+
setTimeout(() => setOpen(false), 1500)
57+
} catch {
58+
setStatus('error')
59+
}
60+
}, [integrationName, email, useCase])
6861

6962
const canSubmit = integrationName.trim() && email.trim() && status === 'idle'
7063

@@ -78,108 +71,100 @@ export function RequestIntegrationModal() {
7871
Request an integration
7972
</button>
8073

81-
<Modal open={open} onOpenChange={handleOpenChange}>
82-
<ModalContent size='sm'>
83-
<ModalHeader>Request an Integration</ModalHeader>
74+
<ChipModal open={open} onOpenChange={handleOpenChange} srTitle='Request an Integration'>
75+
<ChipModalHeader onClose={() => handleOpenChange(false)}>
76+
Request an Integration
77+
</ChipModalHeader>
8478

79+
<ChipModalBody>
8580
{status === 'success' ? (
86-
<ModalBody>
87-
<ModalDescription className='sr-only'>
88-
Integration request submitted successfully
89-
</ModalDescription>
90-
<div className='flex flex-col items-center gap-3 py-6 text-center'>
91-
<div className='flex size-10 items-center justify-center rounded-full bg-[#33C482]/10'>
92-
<svg
93-
className='size-5 text-[var(--brand-accent)]'
94-
viewBox='0 0 24 24'
95-
fill='none'
96-
stroke='currentColor'
97-
strokeWidth={2}
98-
strokeLinecap='round'
99-
strokeLinejoin='round'
100-
>
101-
<polyline points='20 6 9 17 4 12' />
102-
</svg>
103-
</div>
104-
<p className='text-[14px] text-[var(--landing-text)]'>
105-
Request submitted. We&apos;ll follow up at{' '}
106-
<span className='font-medium'>{email}</span>.
107-
</p>
81+
<div className='flex flex-col items-center gap-3 py-6 text-center'>
82+
<div className='flex size-10 items-center justify-center rounded-full bg-[#33C482]/10'>
83+
<svg
84+
className='size-5 text-[var(--brand-accent)]'
85+
viewBox='0 0 24 24'
86+
fill='none'
87+
stroke='currentColor'
88+
strokeWidth={2}
89+
strokeLinecap='round'
90+
strokeLinejoin='round'
91+
>
92+
<polyline points='20 6 9 17 4 12' />
93+
</svg>
10894
</div>
109-
</ModalBody>
95+
<p className='text-[14px] text-[var(--landing-text)]'>
96+
Request submitted. We&apos;ll follow up at{' '}
97+
<span className='font-medium'>{email}</span>.
98+
</p>
99+
</div>
110100
) : (
111-
<form onSubmit={handleSubmit} className='flex min-h-0 flex-1 flex-col'>
112-
<ModalBody>
113-
<ModalDescription className='sr-only'>
114-
Submit a request for a new integration by entering the integration name and your
115-
email
116-
</ModalDescription>
117-
<div className='space-y-3'>
118-
<div className='flex flex-col gap-1'>
119-
<Label htmlFor='integration-name'>Integration name</Label>
120-
<Input
121-
id='integration-name'
122-
placeholder='e.g. Stripe, HubSpot, Snowflake'
123-
value={integrationName}
124-
onChange={(e) => setIntegrationName(e.target.value)}
125-
maxLength={200}
126-
autoComplete='off'
127-
required
128-
/>
129-
</div>
130-
131-
<div className='flex flex-col gap-1'>
132-
<Label htmlFor='requester-email'>Your email</Label>
133-
<Input
134-
id='requester-email'
135-
type='email'
136-
placeholder='you@company.com'
137-
value={email}
138-
onChange={(e) => setEmail(e.target.value)}
139-
autoComplete='email'
140-
required
141-
/>
142-
</div>
143-
144-
<div className='flex flex-col gap-1'>
145-
<Label htmlFor='use-case'>
146-
Use case <span className='text-[var(--text-tertiary)]'>(optional)</span>
147-
</Label>
148-
<Textarea
149-
id='use-case'
150-
placeholder='What would you automate with this integration?'
151-
value={useCase}
152-
onChange={(e) => setUseCase(e.target.value)}
153-
rows={3}
154-
maxLength={2000}
155-
/>
156-
</div>
157-
158-
{status === 'error' && (
159-
<p className='text-[13px] text-[var(--text-error)]'>
160-
Something went wrong. Please try again.
161-
</p>
162-
)}
163-
</div>
164-
</ModalBody>
101+
<>
102+
<ChipModalField
103+
type='input'
104+
title='Integration name'
105+
value={integrationName}
106+
onChange={(value) => setIntegrationName(value)}
107+
placeholder='e.g. Stripe, HubSpot, Snowflake'
108+
maxLength={200}
109+
autoComplete='off'
110+
required
111+
/>
112+
<ChipModalField
113+
type='email'
114+
title='Your email'
115+
value={email}
116+
onChange={(value) => setEmail(value)}
117+
placeholder='you@company.com'
118+
autoComplete='email'
119+
required
120+
/>
121+
<ChipModalField
122+
type='textarea'
123+
title={
124+
<>
125+
Use case <span className='text-[var(--text-tertiary)]'>(optional)</span>
126+
</>
127+
}
128+
value={useCase}
129+
onChange={(value) => setUseCase(value)}
130+
placeholder='What would you automate with this integration?'
131+
rows={3}
132+
maxLength={2000}
133+
/>
134+
{status === 'error' && (
135+
<ChipModalError>Something went wrong. Please try again.</ChipModalError>
136+
)}
137+
</>
138+
)}
139+
</ChipModalBody>
165140

166-
<ModalFooter>
167-
<Button
168-
type='button'
169-
variant='default'
170-
onClick={() => setOpen(false)}
171-
disabled={status === 'submitting'}
172-
>
173-
Cancel
174-
</Button>
175-
<Button type='submit' variant='primary' disabled={!canSubmit && status !== 'error'}>
176-
{status === 'submitting' ? 'Submitting...' : 'Submit request'}
177-
</Button>
178-
</ModalFooter>
179-
</form>
141+
<ChipModalFooter>
142+
{status === 'success' ? (
143+
<Chip variant='primary' flush onClick={() => handleOpenChange(false)}>
144+
Done
145+
</Chip>
146+
) : (
147+
<>
148+
<Chip
149+
variant='filled'
150+
flush
151+
onClick={() => setOpen(false)}
152+
disabled={status === 'submitting'}
153+
>
154+
Cancel
155+
</Chip>
156+
<Chip
157+
variant='primary'
158+
flush
159+
onClick={handleSubmit}
160+
disabled={!canSubmit && status !== 'error'}
161+
>
162+
{status === 'submitting' ? 'Submitting...' : 'Submit request'}
163+
</Chip>
164+
</>
180165
)}
181-
</ModalContent>
182-
</Modal>
166+
</ChipModalFooter>
167+
</ChipModal>
183168
</>
184169
)
185170
}

0 commit comments

Comments
 (0)