{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "checkout-form", "title": "Checkout Form", "description": "A payment-agnostic checkout: contact and shipping fields with validation, a payment slot, an order summary, and submit states. Wire your own PSP.", "dependencies": [ "react-hook-form", "zod", "@hookform/resolvers" ], "registryDependencies": [ "https://lumo-www.vercel.app/r/lib.json", "https://lumo-www.vercel.app/r/order-summary.json" ], "files": [ { "path": "packages/core/src/components/lumo/checkout-form.tsx", "content": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { useForm } from \"react-hook-form\"\r\nimport { zodResolver } from \"@hookform/resolvers/zod\"\r\nimport { z } from \"zod\"\r\nimport type { CartLine, Money } from \"@/lib/lumo/types\"\r\n\r\nimport { cn } from \"@/lib/utils\"\r\nimport { OrderSummary } from \"@/components/lumo/order-summary\"\r\n\r\nconst checkoutSchema = z.object({\r\n email: z.string().min(1, \"Required\").email(\"Enter a valid email\"),\r\n firstName: z.string().min(1, \"Required\"),\r\n lastName: z.string().min(1, \"Required\"),\r\n line1: z.string().min(1, \"Required\"),\r\n line2: z.string().optional(),\r\n city: z.string().min(1, \"Required\"),\r\n region: z.string().optional(),\r\n postalCode: z.string().min(1, \"Required\"),\r\n country: z.string().min(2, \"Use a 2-letter country code\"),\r\n phone: z.string().optional(),\r\n})\r\n\r\nexport type CheckoutValues = z.infer\r\n\r\nconst inputClass =\r\n \"h-10 w-full rounded-md border border-input bg-background px-3 text-sm transition-colors placeholder:text-muted-foreground/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\r\n\r\nfunction Field({\r\n label,\r\n error,\r\n children,\r\n className,\r\n}: {\r\n label: string\r\n error?: string\r\n children: React.ReactElement\r\n className?: string\r\n}) {\r\n const id = React.useId()\r\n return (\r\n
\r\n \r\n {React.cloneElement(children, { id, \"aria-invalid\": error ? true : undefined } as Record)}\r\n {error && {error}}\r\n
\r\n )\r\n}\r\n\r\nexport interface CheckoutFormProps {\r\n lines: CartLine[]\r\n subtotal: Money\r\n shipping?: Money\r\n tax?: Money\r\n total?: Money\r\n locale?: string\r\n /** Slot for your payment provider UI, e.g. Stripe Elements. */\r\n paymentSlot?: React.ReactNode\r\n onSubmit?: (values: CheckoutValues) => void | Promise\r\n submitLabel?: string\r\n className?: string\r\n}\r\n\r\nexport function CheckoutForm({\r\n lines,\r\n subtotal,\r\n shipping,\r\n tax,\r\n total,\r\n locale,\r\n paymentSlot,\r\n onSubmit,\r\n submitLabel = \"Place order\",\r\n className,\r\n}: CheckoutFormProps) {\r\n const [error, setError] = React.useState(null)\r\n const {\r\n register,\r\n handleSubmit,\r\n formState: { errors, isSubmitting },\r\n } = useForm({ resolver: zodResolver(checkoutSchema) })\r\n\r\n const submit = async (values: CheckoutValues) => {\r\n setError(null)\r\n try {\r\n await onSubmit?.(values)\r\n } catch (e) {\r\n setError(e instanceof Error ? e.message : \"Something went wrong. Please try again.\")\r\n }\r\n }\r\n\r\n return (\r\n
\r\n
\r\n
\r\n

Contact

\r\n \r\n \r\n \r\n
\r\n\r\n
\r\n

Shipping address

\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n
\r\n\r\n {paymentSlot && (\r\n
\r\n

Payment

\r\n {paymentSlot}\r\n
\r\n )}\r\n\r\n {error && (\r\n

\r\n {error}\r\n

\r\n )}\r\n
\r\n\r\n \r\n
\r\n )\r\n}\r\n", "type": "registry:ui", "target": "components/lumo/checkout-form.tsx" } ], "type": "registry:ui" }