Files
wehub-resource-sync d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

156 lines
4.7 KiB
TypeScript

'use client'
import { useCallback, useState } from 'react'
import {
Chip,
ChipModal,
ChipModalBody,
ChipModalError,
ChipModalField,
ChipModalFooter,
ChipModalHeader,
} from '@sim/emcn'
import { requestJson } from '@/lib/api/client/request'
import { integrationRequestContract } from '@/lib/api/contracts/common'
type SubmitStatus = 'idle' | 'submitting' | 'success' | 'error'
export function RequestIntegrationModal() {
const [open, setOpen] = useState(false)
const [status, setStatus] = useState<SubmitStatus>('idle')
const [integrationName, setIntegrationName] = useState('')
const [email, setEmail] = useState('')
const [useCase, setUseCase] = useState('')
const resetForm = useCallback(() => {
setIntegrationName('')
setEmail('')
setUseCase('')
setStatus('idle')
}, [])
const handleOpenChange = useCallback(
(nextOpen: boolean) => {
setOpen(nextOpen)
if (!nextOpen) resetForm()
},
[resetForm]
)
const handleSubmit = useCallback(async () => {
if (!integrationName.trim() || !email.trim()) return
setStatus('submitting')
try {
await requestJson(integrationRequestContract, {
body: {
integrationName: integrationName.trim(),
email: email.trim(),
useCase: useCase.trim() || undefined,
},
})
setStatus('success')
setTimeout(() => setOpen(false), 1500)
} catch {
setStatus('error')
}
}, [integrationName, email, useCase])
const canSubmit = integrationName.trim() && email.trim() && status === 'idle'
return (
<>
<Chip className='border border-[var(--border-1)]' onClick={() => setOpen(true)}>
Request an integration
</Chip>
<ChipModal open={open} onOpenChange={handleOpenChange} srTitle='Request an Integration'>
<ChipModalHeader onClose={() => handleOpenChange(false)}>
Request an Integration
</ChipModalHeader>
<ChipModalBody>
{status === 'success' ? (
<div className='flex flex-col items-center gap-3 py-6 text-center'>
<div className='flex size-10 items-center justify-center rounded-full bg-[var(--brand-accent)]/10'>
<svg
className='size-5 text-[var(--brand-accent)]'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth={2}
strokeLinecap='round'
strokeLinejoin='round'
>
<polyline points='20 6 9 17 4 12' />
</svg>
</div>
<p className='text-[14px] text-[var(--text-primary)]'>
Request submitted. We&apos;ll follow up at <span>{email}</span>.
</p>
</div>
) : (
<>
<ChipModalField
type='input'
title='Integration name'
value={integrationName}
onChange={(value) => setIntegrationName(value)}
placeholder='e.g. Stripe, HubSpot, Snowflake'
maxLength={200}
autoComplete='off'
required
/>
<ChipModalField
type='email'
title='Your email'
value={email}
onChange={(value) => setEmail(value)}
placeholder='you@company.com'
autoComplete='email'
required
/>
<ChipModalField
type='textarea'
title={
<>
Use case <span className='text-[var(--text-subtle)]'>(optional)</span>
</>
}
value={useCase}
onChange={(value) => setUseCase(value)}
placeholder='What would you automate with this integration?'
rows={3}
maxLength={2000}
/>
{status === 'error' && (
<ChipModalError>Something went wrong. Please try again.</ChipModalError>
)}
</>
)}
</ChipModalBody>
{status === 'success' ? (
<ChipModalFooter
onCancel={() => handleOpenChange(false)}
primaryAction={{ label: 'Done', onClick: () => handleOpenChange(false) }}
/>
) : (
<ChipModalFooter
onCancel={() => setOpen(false)}
cancelDisabled={status === 'submitting'}
primaryAction={{
label: status === 'submitting' ? 'Submitting...' : 'Submit request',
onClick: handleSubmit,
disabled: !canSubmit && status !== 'error',
}}
/>
)}
</ChipModal>
</>
)
}