'use client' import * as React from 'react' import { cn } from '../../../utils/cn' import { useDeferredError } from '../../../hooks/ui/use-deferred-error' import { Button } from '../../ui/button' import { CheckboxBlock } from '../../ui/checkbox-block' import { Input } from '../../ui/input' import type { AuthSsoProvider } from './sso-providers' import { SsoProviderButtons } from './sso-providers' import { TermsAgreementLabel } from './terms-agreement-label' export interface CreateOrganizationFormProps { /** Controlled field values */ email: string organizationName: string domain: string agreedToTerms: boolean /** Change handlers */ onEmailChange: (value: string) => void onOrganizationNameChange: (value: string) => void onDomainChange: (value: string) => void onAgreedToTermsChange: (checked: boolean) => void /** Primary submit ("Continue") */ onSubmit: () => void /** Suffix rendered inside the domain input, e.g. ".openframe.ai" */ domainSuffix?: string domainPlaceholder?: string termsUrl?: string privacyPolicyUrl?: string submitLabel?: string /** Disables just the primary submit (fields stay editable). */ submitDisabled?: boolean loading?: boolean disabled?: boolean errors?: { email?: string organizationName?: string domain?: string terms?: string } /** Informational status under the email field (e.g. live availability). `errors.email` wins. */ emailStatus?: { message: string; variant: 'error' | 'warning' | 'success' | 'muted' } /** Informational status under the domain field (e.g. live availability). `errors.domain` wins. */ domainStatus?: { message: string; variant: 'error' | 'warning' | 'success' | 'muted' } /** Extra content rendered under the domain field, e.g. suggested available domains. */ domainSlot?: React.ReactNode /** * SSO registration alternatives rendered below the primary submit behind an * "or continue with" divider. The form fields stay editable — gate the * buttons with `ssoDisabled` (e.g. until the form validates). */ ssoProviders?: AuthSsoProvider[] onSsoClick?: (provider: AuthSsoProvider) => void /** Disables the provider buttons (e.g. until the form validates). */ ssoDisabled?: boolean /** Verb prefix for provider buttons, e.g. "Continue with". Ignored for "openframe". */ ssoActionLabel?: string /** Divider text between the primary submit and the SSO buttons. */ dividerLabel?: string className?: string } /** * Create Organization form (Sign Up tab). Presentational + controlled — the * consumer owns state, validation and submission. Covers the empty, filled and * SSO states from the auth redesign. */ export function CreateOrganizationForm({ email, organizationName, domain, agreedToTerms, onEmailChange, onOrganizationNameChange, onDomainChange, onAgreedToTermsChange, onSubmit, domainSuffix, domainPlaceholder = 'company-name', termsUrl = '#', privacyPolicyUrl = '#', submitLabel = 'Continue', submitDisabled = false, loading = false, disabled = false, errors, emailStatus, domainStatus, domainSlot, ssoProviders, onSsoClick, ssoDisabled = false, ssoActionLabel = 'Continue with', dividerLabel = 'or continue with', className, }: CreateOrganizationFormProps) { const hasSso = !!ssoProviders && ssoProviders.length > 0 const fieldsDisabled = disabled || loading // Validation messages are deferred while the user is typing (shown on blur or after a pause). const emailErr = useDeferredError(errors?.email, email) const orgNameErr = useDeferredError(errors?.organizationName, organizationName) const domainErr = useDeferredError(errors?.domain, domain) const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter' && !fieldsDisabled) { onSubmit() } } return (
{/* Header */}

Create Organization

Start your journey with OpenFrame.

{/* Email + Organization Name — single column on every breakpoint */} onEmailChange(event.target.value)} onKeyDown={handleKeyDown} /> onOrganizationNameChange(event.target.value)} onKeyDown={handleKeyDown} /> {/* Domain */}
{domainSuffix} : undefined} onChange={(event) => onDomainChange(event.target.value)} onKeyDown={handleKeyDown} /> {domainSlot &&
{domainSlot}
}
{/* Terms & Privacy — deliberately NOT `truncateLabel`. That prop suits a one-line value; this label is a sentence carrying the Terms and Privacy Policy links, and ellipsizing it clipped the row to "Agree to Terms & Privacy Policy by sig…" below ~500px, so on a phone the user could not read what they were agreeing to. Wrapping is the only acceptable overflow behaviour for consent copy. */} } checked={agreedToTerms} disabled={fieldsDisabled} error={errors?.terms} onCheckedChange={onAgreedToTermsChange} /> {/* Actions */} {/* SSO registration alternatives */} {hasSso && ( <>
{dividerLabel}
)}
) }