{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "customer-information-section", "type": "registry:block", "title": "Customer information section", "description": "Hosted checkout personal information form from customer-information-section.tsx.", "dependencies": ["lucide-react", "react-phone-number-input"], "files": [ { "path": "components/lomi-ui/customer-information-section.tsx", "target": "components/lomi-ui/customer-information-section.tsx", "type": "registry:component", "content": "'use client';\n\nimport * as React from 'react';\nimport { ArrowRightLeft, Info } from 'lucide-react';\nimport type { Country } from 'react-phone-number-input';\nimport {\n CheckoutInput,\n checkoutCustomerFieldClass,\n} from './lib/checkout-input';\nimport { CheckoutPhoneNumberInput } from './lib/checkout-phone-number-input';\nimport { CheckoutWhatsAppNumberInput } from './lib/checkout-whatsapp-number-input';\nimport './lib/checkout-ui.css';\n\nconst INPUT_BORDER = 'clamp(1px, 0.12vw, 1.3px) solid #d1d5db';\n\nfunction cn(...classes: Array) {\n return classes.filter(Boolean).join(' ');\n}\n\nexport interface CustomerInformationSectionProps {\n fullName?: string;\n email?: string;\n phoneNumber?: string;\n whatsappNumber?: string;\n isDifferentWhatsApp?: boolean;\n defaultCountry?: Country;\n isPhoneValid?: boolean;\n onFullNameChange?: (value: string) => void;\n onEmailChange?: (value: string) => void;\n onPhoneNumberChange?: (value?: string) => void;\n onWhatsAppNumberChange?: (value?: string) => void;\n onDifferentWhatsAppChange?: (value: boolean) => void;\n onCountryChange?: (country: string) => void;\n onPhoneValidationChange?: (isValid: boolean | undefined) => void;\n title?: string;\n fullNamePlaceholder?: string;\n emailPlaceholder?: string;\n nameTooltip?: string;\n whatsappDifferentLabel?: string;\n switchToPhoneTitle?: string;\n phoneValidationError?: string;\n className?: string;\n}\n\nexport function CustomerInformationSection({\n fullName = '',\n email = '',\n phoneNumber = '',\n whatsappNumber = '',\n isDifferentWhatsApp = false,\n defaultCountry = 'CI',\n isPhoneValid = true,\n onFullNameChange,\n onEmailChange,\n onPhoneNumberChange,\n onWhatsAppNumberChange,\n onDifferentWhatsAppChange,\n onCountryChange,\n onPhoneValidationChange,\n title = 'Personal information',\n fullNamePlaceholder = 'Full name',\n emailPlaceholder = 'Email address',\n nameTooltip = 'In case we need to contact you about your order.',\n whatsappDifferentLabel = 'My WhatsApp number is different',\n switchToPhoneTitle = 'Switch to using phone number',\n phoneValidationError = 'Please enter a valid phone number for the selected country.',\n className,\n}: CustomerInformationSectionProps) {\n const [showNameTooltip, setShowNameTooltip] = React.useState(false);\n\n const handlePhoneNumberChange = (value: string | undefined) => {\n onPhoneNumberChange?.(value);\n if (!isDifferentWhatsApp) {\n onWhatsAppNumberChange?.(value);\n }\n };\n\n return (\n \n \n
\n
\n onFullNameChange?.(event.target.value)}\n placeholder={fullNamePlaceholder}\n autoComplete=\"name\"\n required\n className={cn(\n checkoutCustomerFieldClass,\n 'input-checkout rounded-tl rounded-tr',\n )}\n />\n
\n \n \n *\n \n
\n
\n\n
\n
\n onEmailChange?.(event.target.value)}\n placeholder={emailPlaceholder}\n autoComplete=\"email\"\n required\n className={cn(\n checkoutCustomerFieldClass,\n 'rounded-none shadow-sm',\n )}\n />\n \n *\n \n
\n
\n\n
\n
\n \n
\n
\n\n {!isDifferentWhatsApp ? (\n
\n \n onDifferentWhatsAppChange?.(true)}\n className=\"group flex h-10 cursor-pointer items-center justify-between px-3 transition-all duration-200\"\n >\n \n {whatsappDifferentLabel}\n \n \n \n \n \n \n
\n
\n \n ) : (\n
\n
\n onWhatsAppNumberChange?.(value)}\n defaultCountry={defaultCountry}\n />\n onDifferentWhatsAppChange?.(false)}\n title={switchToPhoneTitle}\n >\n \n
\n
\n \n )}\n\n {isPhoneValid === false &&\n phoneNumber &&\n phoneNumber.trim().length >= 7 ? (\n

{phoneValidationError}

\n ) : null}\n \n \n );\n}\n" }, { "path": "components/lomi-ui/lib/checkout-input.tsx", "target": "components/lomi-ui/lib/checkout-input.tsx", "type": "registry:component", "content": "import * as React from 'react';\n\nfunction cn(...classes: Array) {\n return classes.filter(Boolean).join(' ');\n}\n\n/** Mirrors apps/checkout/src/components/ui/input.tsx base field styles. */\nexport const CheckoutInput = React.forwardRef<\n HTMLInputElement,\n React.InputHTMLAttributes\n>(({ className, type, ...props }, ref) => (\n \n));\nCheckoutInput.displayName = 'CheckoutInput';\n\n/** Stacked customer form text fields (name, email). */\nexport const checkoutCustomerFieldClass =\n 'border border-gray-300 bg-white placeholder:text-sm focus:bg-white dark:border-gray-300 dark:bg-white dark:text-gray-900 dark:focus:bg-white dark:placeholder:text-gray-500';\n" }, { "path": "components/lomi-ui/lib/checkout-phone-flag.tsx", "target": "components/lomi-ui/lib/checkout-phone-flag.tsx", "type": "registry:component", "content": "'use client';\n\nimport * as React from 'react';\nimport { Phone } from 'lucide-react';\nimport type { FlagProps } from 'react-phone-number-input';\nimport flags from 'react-phone-number-input/flags';\n\nexport function CheckoutPhoneFlag({\n country,\n countryName,\n fallback,\n}: FlagProps & { fallback?: React.ReactNode }) {\n const mounted = React.useSyncExternalStore(\n () => () => {},\n () => true,\n () => false,\n );\n const Flag = flags[country];\n\n return (\n \n {mounted && Flag ? (\n \n ) : (\n (fallback ?? )\n )}\n \n );\n}\n" }, { "path": "components/lomi-ui/lib/checkout-country-select.tsx", "target": "components/lomi-ui/lib/checkout-country-select.tsx", "type": "registry:component", "content": "'use client';\n\nimport * as React from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport * as RPNInput from 'react-phone-number-input';\nimport type { FlagProps } from 'react-phone-number-input';\nimport { CheckoutPhoneFlag } from './checkout-phone-flag';\n\nconst INPUT_BORDER = 'clamp(1px, 0.12vw, 1.3px) solid #d1d5db';\n\nexport type CheckoutCountrySelectProps = {\n disabled?: boolean;\n value: RPNInput.Country;\n onChange: (value: RPNInput.Country) => void;\n options: { label: string; value: RPNInput.Country }[];\n flagComponent?: React.ComponentType;\n};\n\n/** Mirrors apps/checkout phone-number-input CountrySelect. */\nexport function CheckoutCountrySelect({\n disabled,\n value,\n onChange,\n options,\n flagComponent: FlagComponent = CheckoutPhoneFlag,\n}: CheckoutCountrySelectProps) {\n return (\n \n
\n \n \n \n \n
\n onChange(event.target.value as RPNInput.Country)}\n className=\"absolute inset-0 text-sm opacity-0\"\n aria-label=\"Select country\"\n data-lpignore=\"true\"\n >\n \n {options\n .filter((option) => option.value)\n .map((option) => (\n \n ))}\n \n \n );\n}\n" }, { "path": "components/lomi-ui/lib/checkout-phone-number-input.tsx", "target": "components/lomi-ui/lib/checkout-phone-number-input.tsx", "type": "registry:component", "content": "'use client';\n\nimport * as React from 'react';\nimport * as RPNInput from 'react-phone-number-input';\nimport { isValidPhoneNumber } from 'react-phone-number-input';\nimport { CheckoutInput } from './checkout-input';\nimport { CheckoutCountrySelect } from './checkout-country-select';\nimport { CheckoutPhoneFlag } from './checkout-phone-flag';\n\nexport interface CheckoutPhoneNumberInputProps {\n value: string;\n onChange: (value: string | undefined) => void;\n onCountryChange?: (country: string) => void;\n onValidationChange?: (isValid: boolean | undefined) => void;\n defaultCountry?: RPNInput.Country;\n}\n\nexport function CheckoutPhoneNumberInput({\n value,\n onChange,\n onCountryChange,\n onValidationChange,\n defaultCountry,\n}: CheckoutPhoneNumberInputProps) {\n const debounceRef = React.useRef>(undefined);\n const hasTouchedRef = React.useRef(false);\n\n const markTouched = React.useCallback(() => {\n hasTouchedRef.current = true;\n }, []);\n\n const validatePhoneNumber = React.useCallback(\n (phoneValue: string) => {\n if (phoneValue && phoneValue.trim().length >= 7) {\n onValidationChange?.(isValidPhoneNumber(phoneValue));\n } else if (phoneValue && phoneValue.trim().length > 0) {\n onValidationChange?.(undefined);\n } else {\n onValidationChange?.(undefined);\n }\n },\n [onValidationChange],\n );\n\n React.useEffect(() => {\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n\n debounceRef.current = setTimeout(() => {\n const hasMeaningfulValue = Boolean(value && value.trim().length >= 7);\n if (hasTouchedRef.current || hasMeaningfulValue) {\n validatePhoneNumber(value);\n }\n }, 500);\n\n return () => {\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n };\n }, [value, validatePhoneNumber]);\n\n return (\n
\n
\n
\n {\n markTouched();\n onChange(next);\n }}\n onCountryChange={(countryCode) => {\n if (countryCode) {\n markTouched();\n onCountryChange?.(countryCode);\n }\n }}\n onBlur={() => {\n markTouched();\n validatePhoneNumber(value);\n }}\n countryCallingCodeEditable\n />\n
\n \n *\n \n
\n
\n );\n}\n\ntype InputProps = React.InputHTMLAttributes;\n\nconst PhoneInput = React.forwardRef(\n ({ ...props }, ref) => (\n \n ),\n);\nPhoneInput.displayName = 'PhoneInput';\n" }, { "path": "components/lomi-ui/lib/checkout-whatsapp-number-input.tsx", "target": "components/lomi-ui/lib/checkout-whatsapp-number-input.tsx", "type": "registry:component", "content": "'use client';\n\nimport * as React from 'react';\nimport * as RPNInput from 'react-phone-number-input';\nimport type { FlagProps } from 'react-phone-number-input';\nimport flags from 'react-phone-number-input/flags';\nimport { CheckoutInput } from './checkout-input';\nimport { CheckoutCountrySelect } from './checkout-country-select';\nimport { CheckoutPhoneFlag } from './checkout-phone-flag';\n\nfunction WhatsAppIcon({ className }: { className?: string }) {\n return (\n \n \n \n );\n}\n\nfunction WhatsAppFlag(props: FlagProps) {\n const Flag = flags[props.country];\n if (Flag) {\n return ;\n }\n return (\n }\n />\n );\n}\n\nfunction WhatsAppCountrySelect(\n props: React.ComponentProps,\n) {\n return ;\n}\n\nexport interface CheckoutWhatsAppNumberInputProps {\n value: string;\n onChange: (value: string | undefined) => void;\n defaultCountry?: RPNInput.Country;\n}\n\nexport function CheckoutWhatsAppNumberInput({\n value,\n onChange,\n defaultCountry,\n}: CheckoutWhatsAppNumberInputProps) {\n return (\n
\n
\n
\n \n
\n
\n
\n );\n}\n\ntype InputProps = React.InputHTMLAttributes;\n\nconst PhoneInput = React.forwardRef(\n ({ ...props }, ref) => (\n \n ),\n);\nPhoneInput.displayName = 'PhoneInput';\n" }, { "path": "components/lomi-ui/lib/checkout-ui.css", "target": "components/lomi-ui/lib/checkout-ui.css", "type": "registry:component", "content": "/* Hosted checkout parity — mirrors apps/checkout hosted/checkout.css */\n\n.lomi-checkout-ui {\n color-scheme: light;\n --border: oklch(0.929 0.013 255.508);\n --card: oklch(1 0 0);\n --input-border: clamp(1px, 0.12vw, 1.3px) solid #d1d5db;\n}\n\n.lomi-checkout-ui .border.bg-card {\n background-color: white !important;\n}\n\n/* Override docs .prose img rules (aspect-ratio 16/9, object-fit cover) */\n.lomi-checkout-ui img {\n margin: 0 !important;\n max-width: none !important;\n aspect-ratio: auto !important;\n box-shadow: none !important;\n}\n\n.lomi-checkout-ui .border.bg-card img {\n display: block;\n object-fit: contain;\n}\n\n/*\n * Stacked customer form — mirrors checkout form [&_*]:!rounded-none\n * then checkout.css re-applies corners on first/last rows only.\n */\n.lomi-checkout-ui .customer-information-stack * {\n border-radius: 0 !important;\n}\n\n.lomi-checkout-ui input[name='fullName'].rounded-tl.rounded-tr,\n.lomi-checkout-ui input.rounded-tl.rounded-tr {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0 !important;\n border-bottom-right-radius: 0 !important;\n}\n\n.lomi-checkout-ui\n .customer-information-stack\n .w-full.rounded-none.box-border.bg-white {\n border-bottom-left-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.lomi-checkout-ui\n .customer-information-stack\n .whatsapp-input-container\n .flex.w-full.rounded-none {\n border-bottom-left-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n/* Text/email fields — full border; exclude phone tel inputs */\n.lomi-checkout-ui .customer-information-section input:not(.PhoneInputInput),\n.lomi-checkout-ui .customer-information-section select {\n border-color: #d1d5db !important;\n border-style: solid !important;\n border-width: clamp(1px, 0.12vw, 1.3px) !important;\n outline: none !important;\n box-shadow: none !important;\n}\n\n.lomi-checkout-ui\n .customer-information-section\n input:not(.PhoneInputInput):focus,\n.lomi-checkout-ui\n .customer-information-section\n input:not(.PhoneInputInput):focus-visible,\n.lomi-checkout-ui\n .customer-information-section\n input:not(.PhoneInputInput):hover:focus,\n.lomi-checkout-ui\n .customer-information-section\n input:not(.PhoneInputInput):active:focus,\n.lomi-checkout-ui .customer-information-section select:focus,\n.lomi-checkout-ui .customer-information-section select:focus-visible {\n border-color: #d1d5db !important;\n outline: none !important;\n box-shadow: none !important;\n}\n\n/* Phone / WhatsApp rows — full border like email, stacked with -mt-px */\n.lomi-checkout-ui .customer-information-section .checkout-phone-shell {\n border: var(--input-border) !important;\n border-radius: 0 !important;\n background-color: white !important;\n}\n\n.lomi-checkout-ui .whatsapp-input-container * {\n box-sizing: border-box;\n}\n\n.lomi-checkout-ui .customer-information-stack .checkout-phone-flag,\n.lomi-checkout-ui .customer-information-stack .checkout-phone-flag svg {\n border-radius: 0.25rem !important;\n}\n\n/* Phone input — mirrors checkout.css PhoneInput overrides */\n.lomi-checkout-ui .PhoneInput,\n.lomi-checkout-ui .PhoneInput *,\n.lomi-checkout-ui .PhoneInputCountry,\n.lomi-checkout-ui .PhoneInputInput {\n outline: none !important;\n box-shadow: none !important;\n border: none !important;\n background-color: transparent !important;\n}\n\n.lomi-checkout-ui .PhoneInput .relative.inline-flex.items-center.self-stretch,\n.lomi-checkout-ui .relative.inline-flex.items-center.self-stretch {\n border-right: var(--input-border) !important;\n}\n\n.lomi-checkout-ui .PhoneInputInput,\n.lomi-checkout-ui .whatsapp-input-container .PhoneInputInput {\n font-size: 0.875rem !important;\n line-height: 1.25rem !important;\n padding: 0.5rem 0.75rem !important;\n}\n\n.lomi-checkout-ui .customer-information-section .PhoneInputInput {\n padding-right: 1.5rem !important;\n}\n\n.lomi-checkout-ui .whatsapp-input-container input {\n padding-right: 2.5rem !important;\n}\n\n.lomi-checkout-ui .PhoneInputInput::placeholder,\n.lomi-checkout-ui .whatsapp-input-container .PhoneInputInput::placeholder {\n font-size: 0.875rem !important;\n line-height: 1.25rem !important;\n color: #9ca3af !important;\n}\n\n/* Country flag — w-5 (20px) with rounded-sm */\n.lomi-checkout-ui .checkout-phone-flag {\n width: 1.25rem !important;\n height: 1.25rem !important;\n min-width: 1.25rem;\n min-height: 1.25rem;\n overflow: hidden;\n border-radius: 0.25rem !important;\n}\n\n.lomi-checkout-ui .checkout-phone-flag svg {\n width: 1.25rem !important;\n height: 1.25rem !important;\n display: block;\n border-radius: 0.25rem !important;\n}\n\n.lomi-checkout-ui .PhoneInput select[aria-label='Select country'],\n.lomi-checkout-ui\n .whatsapp-input-container\n select[aria-label='Select country'] {\n border: none !important;\n}\n\n/* SPI alias inputs outside customer stack */\n.lomi-checkout-ui input[type='text']:not(.PhoneInputInput),\n.lomi-checkout-ui input[type='email']:not(.PhoneInputInput) {\n border-color: #d1d5db !important;\n border-style: solid !important;\n border-width: clamp(1px, 0.12vw, 1.3px) !important;\n outline: none !important;\n box-shadow: none !important;\n}\n" }, { "path": "components/lomi-ui/demo/customer-information-section-demo.tsx", "target": "components/lomi-ui/demo/customer-information-section-demo.tsx", "type": "registry:component", "content": "'use client';\n\nimport * as React from 'react';\nimport { LomiUiPreview } from '@/components/preview/lomi-ui-preview';\nimport { CustomerInformationSection } from '@/components/lomi-ui/customer-information-section';\n\nexport function CustomerInformationSectionDemo() {\n const [fullName, setFullName] = React.useState('');\n const [email, setEmail] = React.useState('');\n const [phoneNumber, setPhoneNumber] = React.useState('');\n const [whatsappNumber, setWhatsAppNumber] = React.useState('');\n const [isDifferentWhatsApp, setIsDifferentWhatsApp] = React.useState(false);\n\n return (\n \n setPhoneNumber(value ?? '')}\n onWhatsAppNumberChange={(value) => setWhatsAppNumber(value ?? '')}\n onDifferentWhatsAppChange={setIsDifferentWhatsApp}\n />\n \n );\n}\n" } ] }