Checkout Forms 02
Checkout Forms 02 é um componente de formulário para React e Tailwind CSS, da biblioteca Bund UI, com licença MIT. Copia e cola no teu projeto.
Bund UI
MIT
forms
O que é
Checkout Forms 02 é um componente de formulário para React e Tailwind CSS, da biblioteca Bund UI, com licença MIT. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Cinco cópias grátis por mês. Sem cartão.
Precisa de
npm i lucide-react
Licença
Este componente vem de Bund UI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.
Checkout Forms 02
import { Card, CardContent } from "@/components/ui/card";
import { MapPin, Truck } from "lucide-react";
interface DeliveryCardProps {
address: {
name: string;
street: string;
apt: string;
city: string;
country: string;
};
delivery: {
type: string;
date: string;
time: string;
};
}
const DeliveryCard = ({ address, delivery }: DeliveryCardProps) => {
return (
<div className="grid gap-4 md:grid-cols-2">
<Card className="shadow-none">
<CardContent className="flex gap-4">
<div className="bg-muted flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-lg">
<MapPin className="text-muted-foreground h-6 w-6" />
</div>
<div className="flex-1">
<h3 className="text-foreground mb-3 font-semibold">{address.name}</h3>
<div className="text-foreground space-y-1 text-sm">
<p>{address.street}</p>
<p>{address.apt}</p>
<p>{address.city}</p>
<p>{address.country}</p>
</div>
</div>
</CardContent>
</Card>
<Card className="shadow-none">
<CardContent className="flex gap-4">
<div className="bg-muted flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-lg">
<Truck className="text-muted-foreground h-6 w-6" />
</div>
<div className="flex-1">
<h3 className="text-foreground mb-3 font-semibold">{delivery.type}</h3>
<div className="text-foreground space-y-1 text-sm">
<p>{delivery.date}</p>
<p>{delivery.time}</p>
</div>
</div>
</CardContent>
</Card>
</div>
);
};
export default DeliveryCard;
import { Card } from "@/components/ui/card";
interface OrderItemProps {
image: string;
name: string;
quantity: number;
price: string;
}
const OrderItem = ({ image, name, quantity, price }: OrderItemProps) => {
return (
<div className="bg-muted/50 mb-3 flex flex-row items-center justify-between gap-4 rounded-lg border-none pe-4 shadow-none">
<img src={image} alt={name} className="aspect-square w-20 rounded-md object-cover" />
<div className="min-w-0 flex-1">
<h3 className="font-medium">{name}</h3>
</div>
<div className="flex flex-shrink-0 items-center gap-6">
<span className="text-muted-foreground">{quantity} pc</span>
<span className="font-medium">{price}</span>
</div>
</div>
);
};
export default OrderItem;
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
interface OrderSummaryProps {
subtotal: string;
discount: string;
delivery: string;
total: string;
}
const OrderSummary = ({ subtotal, discount, delivery, total }: OrderSummaryProps) => {
return (
<Card className="shadow-none">
<CardContent className="space-y-6">
<div className="space-y-4">
<div className="font-heading flex items-center justify-between text-2xl">
<div>Total</div>
<div>{total}</div>
</div>
<div className="border-border space-y-3 border-t pt-4 text-sm">
<div className="flex items-center justify-between">
<span className="">Subtotal</span>
<span className="font-medium">{subtotal}</span>
</div>
<div className="flex items-center justify-between">
<span className="">Discount</span>
<span className="text-destructive font-medium">-{discount}</span>
</div>
<div className="flex items-center justify-between">
<span className="">Delivery</span>
<span className="font-medium">{delivery}</span>
</div>
</div>
</div>
<Button size="lg" className="w-full">
Pay {total}
</Button>
<p className="text-muted-foreground text-xs leading-relaxed">
By placing an order, you're agreeing to our{" "}
<a href="#" className="underline">
Privacy Policy
</a>
,{" "}
<a href="#" className="underline">
Terms and Conditions
</a>{" "}
and{" "}
<a href="#" className="underline">
Cancellation policy
</a>
.
</p>
</CardContent>
</Card>
);
};
export default OrderSummary;
import { Card, CardContent } from "@/components/ui/card";
interface PaymentMethodCardProps {
icon: React.ReactNode;
title: string;
description: string;
}
const PaymentMethodCard = ({ icon, title, description }: PaymentMethodCardProps) => {
return (
<Card className="shadow-none">
<CardContent className="flex gap-4">
<div className="flex-shrink-0">{icon}</div>
<div className="flex-1">
<h3 className="mb-1 font-semibold">{title}</h3>
<p className="text-muted-foreground text-sm">{description}</p>
</div>
</CardContent>
</Card>
);
};
export default PaymentMethodCard;
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";
interface Step {
number: number;
label: string;
completed: boolean;
active: boolean;
}
interface PaymentStepperProps {
currentStep: number;
}
const PaymentStepper = ({ currentStep }: PaymentStepperProps) => {
const steps: Step[] = [
{ number: 1, label: "Cart", completed: currentStep > 1, active: currentStep === 1 },
{
number: 2,
label: "Delivery and payment",
completed: currentStep > 2,
active: currentStep === 2
},
{ number: 3, label: "Summary", completed: currentStep > 3, active: currentStep === 3 },
{ number: 4, label: "Done", completed: false, active: currentStep === 4 }
];
return (
<div className="w-full py-8">
<div className="mx-auto flex max-w-5xl items-center justify-between px-4">
{steps.map((step, index) => (
<div key={step.number} className="flex flex-1 items-center">
<div className="flex flex-1 flex-col items-center">
<div className="flex w-full items-center">
<div
className={cn(
"flex h-10 w-10 items-center justify-center rounded-full border-2 font-medium transition-colors",
step.active && "bg-accent border-accent text-accent-foreground",
step.completed && "bg-accent border-accent text-accent-foreground",
!step.active &&
!step.completed &&
"bg-muted border-border text-muted-foreground"
)}>
{step.completed ? <Check className="h-5 w-5" /> : step.number}
</div>
{index < steps.length - 1 && (
<div
className={cn(
"mx-2 h-0.5 flex-1 transition-colors",
step.completed ? "bg-accent" : "bg-border"
)}
/>
)}
</div>
<span
className={cn(
"mt-2 text-center text-sm font-medium whitespace-nowrap",
step.active ? "text-foreground" : "text-muted-foreground"
)}>
{step.label}
</span>
</div>
</div>
))}
</div>
</div>
);
};
export default PaymentStepper;
export const orderItems = [
{
image: "/images/products/list3.png",
name: "Ilia C Beyond Triple Serum SPF 40",
quantity: 1,
price: "$23.99"
},
{
image: "/images/products/list1.png",
name: "Our Place Always Pan",
quantity: 1,
price: "$129.99"
}
];
export const deliveryAddress = {
name: "Emily Anderson",
street: "123 Main Street",
apt: "Apt 4B",
city: "Cityville, State 56789",
country: "United States"
};
export const deliveryInfo = {
type: "Courier delivery",
date: "September 17, 2023",
time: "12:00 - 14:00"
};
import { Button } from "@/components/ui/button";
import OrderItem from "./components/order-item";
import DeliveryCard from "./components/delivery-card";
import PaymentMethodCard from "./components/payment-method-card";
import OrderSummary from "./components/order-summary";
import { orderItems, deliveryAddress, deliveryInfo } from "./data";
import {
Stepper,
StepperIndicator,
StepperItem,
StepperNav,
StepperSeparator,
StepperTrigger
} from "@/components/ui/stepper";
import { Check, LoaderCircleIcon } from "lucide-react";
const steps = [1, 2, 3, 4];
export default function CheckoutPage() {
const ApplePayIcon = () => (
<div className="bg-muted flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
className="bi bi-apple size-6"
viewBox="0 0 16 16">
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282" />
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282" />
</svg>
</div>
);
return (
<section className="py-10 lg:py-20">
<div className="px-4 lg:px-6">
<div className="mx-auto max-w-3xl">
<Stepper
defaultValue={2}
indicators={{
completed: <Check className="size-4" />,
loading: <LoaderCircleIcon className="size-4 animate-spin" />
}}
className="mb-14 space-y-8">
<StepperNav>
{steps.map((step) => (
<StepperItem key={step} step={step}>
<StepperTrigger>
<StepperIndicator className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=completed]:bg-green-500 data-[state=completed]:text-white data-[state=inactive]:text-gray-500">
{step}
</StepperIndicator>
</StepperTrigger>
{steps.length > step && (
<StepperSeparator className="group-data-[state=completed]/step:bg-primary" />
)}
</StepperItem>
))}
</StepperNav>
</Stepper>
</div>
<div className="grid gap-8 lg:grid-cols-[1fr_400px]">
{/* Left Column */}
<div className="space-y-12">
{/* Order Details */}
<section>
<div className="mb-4 flex items-center justify-between">
<h2 className="font-heading text-xl">Order details</h2>
</div>
<div>
{orderItems.map((item, index) => (
<OrderItem key={index} {...item} />
))}
</div>
</section>
{/* Delivery */}
<section>
<div className="mb-4 flex items-center justify-between">
<h2 className="font-heading text-xl">Delivery</h2>
<Button variant="secondary" size="sm">
Edit
</Button>
</div>
<DeliveryCard address={deliveryAddress} delivery={deliveryInfo} />
</section>
{/* Payment Method */}
<section>
<div className="mb-4 flex items-center justify-between">
<h2 className="font-heading text-xl">Payment method</h2>
<Button variant="secondary" size="sm">
Edit
</Button>
</div>
<PaymentMethodCard
icon={<ApplePayIcon />}
title="Apple Pay"
description="Swiftly make contactless payments with Apple Pay, ensuring a seamless and secure checkout experience."
/>
</section>
</div>
{/* Right Column - Summary */}
<div>
<OrderSummary subtotal="$153.98" discount="$10.00" delivery="Free" total="$143.98" />
</div>
</div>
</div>
</section>
);
}