Velocity UI
Loading…
Menu

Dynamic Checkout Flow

A seamless, token-aware multi-step checkout flow with animated transitions, an interactive card, and success celebration.

Installation

Add this component to your project using the CLI:

terminal
npx vui-registry-cli-v1 add dynamic-checkout

Source Code

dynamic-checkout.tsx
'use client'

import React, { useState, useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { 
  CreditCard, 
  MapPin, 
  Check, 
  ChevronRight, 
  ChevronLeft, 
  ShieldCheck, 
  Loader2,
  Package,
  Truck,
  Sparkles,
  Wifi,
  ScanLine
} from 'lucide-react'
import { cn } from '@/lib/utils'
// @ts-ignore
import confetti from 'canvas-confetti'

type Step = 'shipping' | 'payment' | 'review' | 'success'

interface FormData {
  name: string
  address: string
  city: string
  zip: string
  cardNumber: string
  expiry: string
  cvv: string
}

export default function DynamicCheckout() {
  const [step, setStep] = useState<Step>('shipping')
  const [direction, setDirection] = useState(1)
  const [loading, setLoading] = useState(false)
  const [focusedField, setFocusedField] = useState<string | null>(null)
  
  const [formData, setFormData] = useState<FormData>({
    name: '',
    address: '',
    city: '',
    zip: '',
    cardNumber: '',
    expiry: '',
    cvv: ''
  })

  const handleNext = () => {
    setDirection(1)
    if (step === 'shipping') setStep('payment')
    else if (step === 'payment') setStep('review')
    else if (step === 'review') handleSubmit()
  }

  const handleBack = () => {
    setDirection(-1)
    if (step === 'payment') setStep('shipping')
    else if (step === 'review') setStep('payment')
  }

  const handleSubmit = () => {
    setLoading(true)
    setTimeout(() => {
      setLoading(false)
      setStep('success')
      confetti({
        particleCount: 150,
        spread: 80,
        origin: { y: 0.6 },
        colors: ['#FFD700', '#FFA500', '#FF4500'] // Gold/Premium colors
      })
    }, 1500)
  }

  const variants = {
    enter: (direction: number) => ({
      x: direction > 0 ? 20 : -20,
      opacity: 0,
      scale: 0.98,
      filter: "blur(4px)"
    }),
    center: {
      x: 0,
      opacity: 1,
      scale: 1,
      filter: "blur(0px)"
    },
    exit: (direction: number) => ({
      x: direction > 0 ? -20 : 20,
      opacity: 0,
      scale: 0.98,
      filter: "blur(4px)"
    })
  }

  return (
    <div className="min-h-[600px] w-full flex items-center justify-center bg-neutral-100 dark:bg-neutral-950 p-4 font-sans relative overflow-hidden">
      
      {/* Background Elements */}
      <div className="absolute inset-0 bg-[linear-gradient(to_right,#80808008_1px,transparent_1px),linear-gradient(to_bottom,#80808008_1px,transparent_1px)] bg-[size:24px_24px]"></div>
      
      <motion.div
        layout
        transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
        className="bg-white/80 dark:bg-neutral-900/80 backdrop-blur-xl rounded-2xl shadow-2xl shadow-black/10 dark:shadow-black/50 border border-neutral-200/50 dark:border-white/10 w-full max-w-[360px] overflow-hidden relative z-10"
      >
        {/* Header */}
        <div className="px-5 pt-5 pb-3 flex items-center justify-between border-b border-neutral-100 dark:border-white/5">
            <div className="flex items-center gap-3">
                <div className="w-7 h-7 rounded-lg bg-gradient-to-br from-neutral-900 to-neutral-700 dark:from-white dark:to-neutral-300 flex items-center justify-center shadow-lg shadow-neutral-500/20 group cursor-pointer overflow-hidden relative">
                    <motion.div 
                        className="absolute inset-0 bg-gradient-to-tr from-transparent via-white/40 to-transparent z-10"
                        animate={{ x: ['-100%', '200%'] }}
                        transition={{ duration: 1.5, repeat: Infinity, repeatDelay: 3 }}
                    />
                    {/* Removed icon for a cleaner Velocity Pay logo */}
                </div>
                <div>
                    <span className="font-bold text-sm tracking-tight text-neutral-900 dark:text-white block leading-none">Velocity Pay</span>
                    <span className="text-[9px] text-neutral-500 font-medium tracking-wider uppercase">Secure Checkout</span>
                </div>
            </div>
            {step !== 'success' && (
                <div className="flex gap-1">
                    {['shipping', 'payment', 'review'].map((s, i) => {
                        const currentIndex = ['shipping', 'payment', 'review'].indexOf(step);
                        const isActive = currentIndex >= i;
                        return (
                            <motion.div 
                                key={s}
                                initial={false}
                                animate={{ 
                                    backgroundColor: isActive ? 'var(--active-color)' : 'var(--inactive-color)',
                                    scale: currentIndex === i ? 1 : 0.9,
                                    width: currentIndex === i ? 16 : 4,
                                    opacity: isActive ? 1 : 0.3
                                }}
                                className="h-1 rounded-full transition-all duration-300"
                                style={{ 
                                    // @ts-ignore
                                    '--active-color': isActive ? '#171717' : '#e5e5e5',
                                    '--inactive-color': '#e5e5e5' 
                                } as React.CSSProperties}
                            />
                        )
                    })}
                </div>
            )}
        </div>

        {/* Content */}
        <div className="p-5">
          <AnimatePresence mode="wait" custom={direction}>
            {step === 'shipping' && (
              <motion.div
                key="shipping"
                custom={direction}
                variants={variants}
                initial="enter"
                animate="center"
                exit="exit"
                transition={{ type: "spring", stiffness: 300, damping: 30 }}
                className="space-y-3"
              >
                <div className="mb-1">
                    <h2 className="text-lg font-bold text-neutral-900 dark:text-white">Shipping</h2>
                    <p className="text-[10px] text-neutral-500 mt-0.5">Where should we send your order?</p>
                </div>
                
                <div className="space-y-2.5">
                    <div className="group">
                        <label className="text-[9px] font-bold text-neutral-500 uppercase tracking-wider mb-1 block">Full Name</label>
                        <div className="relative group-focus-within:scale-[1.01] transition-transform duration-200">
                            <input 
                                type="text" 
                                placeholder="John Doe"
                                className="w-full pl-3 pr-3 py-2 rounded-lg bg-neutral-50 dark:bg-neutral-800/50 border border-neutral-200 dark:border-neutral-700 focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white transition-all shadow-sm focus:shadow-md text-xs"
                                value={formData.name}
                                onChange={(e) => setFormData({...formData, name: e.target.value})}
                            />
                            <div className="absolute right-3 top-2.5 w-1.5 h-1.5 rounded-full bg-green-500 opacity-0 transition-opacity" style={{ opacity: formData.name.length > 2 ? 1 : 0 }} />
                        </div>
                    </div>
                    <div className="group">
                        <label className="text-[9px] font-bold text-neutral-500 uppercase tracking-wider mb-1 block">Address</label>
                        <input 
                            type="text" 
                            placeholder="123 Innovation Dr"
                            className="w-full px-3 py-2 rounded-lg bg-neutral-50 dark:bg-neutral-800/50 border border-neutral-200 dark:border-neutral-700 focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white transition-all shadow-sm focus:shadow-md text-xs"
                            value={formData.address}
                            onChange={(e) => setFormData({...formData, address: e.target.value})}
                        />
                    </div>
                    <div className="grid grid-cols-2 gap-2.5">
                        <div className="group">
                            <label className="text-[9px] font-bold text-neutral-500 uppercase tracking-wider mb-1 block">City</label>
                            <input 
                                type="text" 
                                placeholder="San Francisco"
                                className="w-full px-3 py-2 rounded-lg bg-neutral-50 dark:bg-neutral-800/50 border border-neutral-200 dark:border-neutral-700 focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white transition-all shadow-sm focus:shadow-md text-xs"
                                value={formData.city}
                                onChange={(e) => setFormData({...formData, city: e.target.value})}
                            />
                        </div>
                        <div className="group">
                            <label className="text-[9px] font-bold text-neutral-500 uppercase tracking-wider mb-1 block">ZIP Code</label>
                            <input 
                                type="text" 
                                placeholder="94103"
                                className="w-full px-3 py-2 rounded-lg bg-neutral-50 dark:bg-neutral-800/50 border border-neutral-200 dark:border-neutral-700 focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white transition-all shadow-sm focus:shadow-md text-xs"
                                value={formData.zip}
                                onChange={(e) => setFormData({...formData, zip: e.target.value})}
                            />
                        </div>
                    </div>
                </div>

                <div className="pt-2 flex justify-end">
                    <button 
                        onClick={handleNext}
                        className="group relative inline-flex h-9 items-center justify-center overflow-hidden rounded-lg bg-neutral-950 dark:bg-white px-5 font-medium text-white dark:text-neutral-950 shadow-lg transition-all hover:bg-neutral-800 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-neutral-400 focus:ring-offset-2 text-xs"
                    >
                        <motion.div 
                            className="absolute inset-0 bg-white/20"
                            initial={{ x: '-100%' }}
                            whileHover={{ x: '100%' }}
                            transition={{ duration: 0.5 }}
                        />
                        <span className="flex items-center gap-1.5 relative z-10">
                            Continue <ChevronRight className="w-3 h-3" />
                        </span>
                    </button>
                </div>
              </motion.div>
            )}

            {step === 'payment' && (
              <motion.div
                key="payment"
                custom={direction}
                variants={variants}
                initial="enter"
                animate="center"
                exit="exit"
                transition={{ type: "spring", stiffness: 300, damping: 30 }}
                className="space-y-4"
              >
                <div className="mb-1">
                    <h2 className="text-lg font-bold text-neutral-900 dark:text-white">Payment</h2>
                    <p className="text-[10px] text-neutral-500 mt-0.5">Secure credit card transaction.</p>
                </div>
                
                {/* Realistic 3D Card */}
                <div className="w-full aspect-[1.586] perspective-1000 group cursor-pointer" onClick={() => setFocusedField(focusedField === 'cvv' ? 'cardNumber' : 'cvv')}>
                    <motion.div 
                        className="w-full h-full relative preserve-3d transition-all duration-700"
                        animate={{ rotateY: focusedField === 'cvv' ? 180 : 0 }}
                        transition={{ type: "spring", stiffness: 260, damping: 20 }}
                    >
                        {/* Front */}
                        <div className="absolute inset-0 backface-hidden rounded-xl p-4 text-white shadow-2xl flex flex-col justify-between overflow-hidden border border-white/10 bg-[#0a0a0a]">
                            
                            {/* Texture & Gradients */}
                            <div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-30 mix-blend-overlay"></div>
                            <div className="absolute inset-0 bg-gradient-to-br from-neutral-900 via-[#1a1a1a] to-neutral-900"></div>
                            
                            {/* Moving Beams */}
                            <div className="absolute inset-0 overflow-hidden">
                                <motion.div 
                                    animate={{ 
                                        x: ['-100%', '200%'],
                                        opacity: [0, 0.5, 0]
                                    }}
                                    transition={{ 
                                        duration: 2.5, 
                                        repeat: Infinity, 
                                        repeatDelay: 1,
                                        ease: "easeInOut"
                                    }}
                                    className="absolute top-0 bottom-0 w-32 bg-gradient-to-r from-transparent via-white/10 to-transparent skew-x-12 blur-xl"
                                />
                                 <motion.div 
                                    animate={{ 
                                        x: ['-100%', '200%'],
                                        opacity: [0, 0.3, 0]
                                    }}
                                    transition={{ 
                                        duration: 2, 
                                        repeat: Infinity, 
                                        repeatDelay: 0.5,
                                        ease: "easeInOut",
                                        delay: 1
                                    }}
                                    className="absolute top-0 bottom-0 w-12 bg-gradient-to-r from-transparent via-blue-500/20 to-transparent skew-x-12 blur-lg"
                                />
                            </div>

                            {/* Holographic Foil */}
                            <div className="absolute inset-0 bg-gradient-to-tr from-white/5 via-transparent to-white/5 opacity-50 mix-blend-overlay pointer-events-none"></div>

                            <div className="flex justify-between items-start z-10 relative">
                                {/* EMV Chip */}
                                <div className="w-9 h-6 rounded bg-gradient-to-br from-[#e0c388] via-[#d4af37] to-[#b8860b] flex items-center justify-center shadow-inner border border-[#d4af37]/50 relative overflow-hidden">
                                     <div className="absolute inset-0 bg-gradient-to-br from-white/40 to-transparent opacity-50"></div>
                                     <div className="w-full h-[1px] bg-black/20 absolute top-1/3"></div>
                                     <div className="w-full h-[1px] bg-black/20 absolute bottom-1/3"></div>
                                     <div className="h-full w-[1px] bg-black/20 absolute left-1/3"></div>
                                     <div className="h-full w-[1px] bg-black/20 absolute right-1/3"></div>
                                </div>
                                <div className="flex items-center gap-1">
                                    <Wifi className="w-3.5 h-3.5 text-white/70 rotate-90" />
                                </div>
                            </div>

                            <div className="z-10 relative mt-1">
                                <div className="flex items-center gap-3 mb-2">
                                    <p className="font-mono text-[15px] tracking-[0.14em] text-white/90 drop-shadow-md">
                                        {formData.cardNumber || '4242 4242 4242 4242'}
                                    </p>
                                </div>
                                <div className="flex items-end gap-8">
                                    <div>
                                        <p className="text-[6px] text-white/50 uppercase tracking-widest mb-0.5 font-semibold">Card Holder</p>
                                        <p className="font-medium tracking-wide uppercase text-[9px] text-white/90">{formData.name || 'YOUR NAME'}</p>
                                    </div>
                                    <div>
                                        <p className="text-[6px] text-white/50 uppercase tracking-widest mb-0.5 font-semibold">Expires</p>
                                        <p className="font-medium tracking-wide text-[9px] text-white/90">{formData.expiry || 'MM/YY'}</p>
                                    </div>
                                </div>
                            </div>
                             <div className="absolute bottom-4 right-4 z-10">
                                <span className="font-black italic tracking-wider text-white text-lg drop-shadow-md opacity-90">VISA</span>
                             </div>
                        </div>

                        {/* Back */}
                        <div 
                            className="absolute inset-0 backface-hidden bg-[#0a0a0a] rounded-xl text-white shadow-xl overflow-hidden border border-white/10"
                            style={{ transform: "rotateY(180deg)" }}
                        >
                             <div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-30 mix-blend-overlay"></div>
                            <div className="w-full h-8 bg-black mt-4 relative"></div>
                            <div className="p-4 relative z-10">
                                <div className="flex flex-col items-end">
                                    <p className="text-[7px] text-white/60 uppercase tracking-wider mb-1 mr-1">CVV</p>
                                    <div className="w-full h-7 bg-white text-black font-mono flex items-center justify-end px-2 rounded shadow-inner text-xs font-bold">
                                        {formData.cvv || '123'}
                                    </div>
                                </div>
                                <div className="mt-3 flex items-center gap-2 opacity-60">
                                    <div className="w-6 h-4 bg-white/10 rounded flex items-center justify-center border border-white/10">
                                        <div className="w-4 h-2 border border-white/30 rounded-sm"></div>
                                    </div>
                                    <span className="text-[7px] max-w-[150px] leading-tight">Secure Transaction. Authorized Signature Not Required.</span>
                                </div>
                            </div>
                        </div>
                    </motion.div>
                </div>

                <div className="space-y-2.5 p-1">
                    <div className="group relative">
                        <label className="text-[9px] font-bold text-neutral-500 uppercase tracking-wider mb-1 block">Card Number</label>
                        <div className="relative group-focus-within:scale-[1.01] transition-transform duration-200">
                            <div className="absolute inset-0 bg-gradient-to-r from-blue-500 to-purple-500 rounded-lg blur opacity-0 group-focus-within:opacity-20 transition-opacity duration-500"></div>
                            <div className="relative bg-white dark:bg-neutral-900 rounded-lg border border-neutral-200 dark:border-neutral-700 overflow-hidden flex items-center">
                                <div className="pl-3 text-neutral-400">
                                    <CreditCard className="w-3.5 h-3.5" />
                                </div>
                                <input 
                                    type="text" 
                                    placeholder="0000 0000 0000 0000"
                                    maxLength={19}
                                    className="w-full pl-2 pr-3 py-2 bg-transparent focus:outline-none font-mono text-xs"
                                    value={formData.cardNumber}
                                    onChange={(e) => setFormData({...formData, cardNumber: e.target.value})}
                                    onFocus={() => setFocusedField('cardNumber')}
                                />
                            </div>
                        </div>
                    </div>
                    <div className="grid grid-cols-2 gap-2.5">
                        <div className="group relative">
                            <label className="text-[9px] font-bold text-neutral-500 uppercase tracking-wider mb-1 block">Expiry Date</label>
                            <input 
                                type="text" 
                                placeholder="MM/YY"
                                maxLength={5}
                                className="w-full px-3 py-2 rounded-lg bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white transition-all shadow-sm focus:shadow-md text-xs font-mono"
                                value={formData.expiry}
                                onChange={(e) => setFormData({...formData, expiry: e.target.value})}
                                onFocus={() => setFocusedField('expiry')}
                            />
                        </div>
                        <div className="group relative">
                            <label className="text-[9px] font-bold text-neutral-500 uppercase tracking-wider mb-1 block">CVV</label>
                            <input 
                                type="text" 
                                placeholder="123"
                                maxLength={3}
                                className="w-full px-3 py-2 rounded-lg bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white transition-all shadow-sm focus:shadow-md text-xs font-mono"
                                value={formData.cvv}
                                onChange={(e) => setFormData({...formData, cvv: e.target.value})}
                                onFocus={() => setFocusedField('cvv')}
                            />
                        </div>
                    </div>
                </div>

                <div className="flex justify-between pt-2">
                    <button 
                        onClick={handleBack}
                        className="text-xs text-neutral-500 hover:text-neutral-900 dark:hover:text-white font-medium flex items-center gap-1 transition-colors"
                    >
                        <ChevronLeft className="w-3 h-3" /> Back
                    </button>
                    <button 
                        onClick={handleNext}
                        className="group relative inline-flex h-9 items-center justify-center overflow-hidden rounded-lg bg-neutral-950 dark:bg-white px-5 font-medium text-white dark:text-neutral-950 shadow-lg transition-all hover:bg-neutral-800 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-neutral-400 focus:ring-offset-2 text-xs"
                    >
                        <span className="flex items-center gap-1.5 relative z-10">
                            Review <ChevronRight className="w-3 h-3" />
                        </span>
                    </button>
                </div>
              </motion.div>
            )}

            {step === 'review' && (
              <motion.div
                key="review"
                custom={direction}
                variants={variants}
                initial="enter"
                animate="center"
                exit="exit"
                transition={{ type: "spring", stiffness: 300, damping: 30 }}
                className="space-y-4"
              >
                <div className="mb-1">
                    <h2 className="text-lg font-bold text-neutral-900 dark:text-white">Review</h2>
                    <p className="text-[10px] text-neutral-500 mt-0.5">Please verify your information.</p>
                </div>

                <div className="space-y-3">
                    <div className="bg-neutral-50 dark:bg-neutral-800/30 rounded-xl p-3 border border-neutral-100 dark:border-white/5 space-y-3">
                        <div className="flex items-start gap-3">
                            <div className="w-8 h-8 rounded-full bg-blue-500/10 flex items-center justify-center shrink-0">
                                <MapPin className="w-4 h-4 text-blue-500" />
                            </div>
                            <div>
                                <p className="text-[10px] text-neutral-500 font-bold uppercase tracking-wider mb-0.5">Shipping To</p>
                                <p className="text-xs font-medium text-neutral-900 dark:text-white">{formData.name || 'John Doe'}</p>
                                <p className="text-xs text-neutral-500">{formData.address || '123 Innovation Dr'}, {formData.city || 'San Francisco'} {formData.zip || '94103'}</p>
                            </div>
                        </div>
                        <div className="h-px bg-neutral-200 dark:bg-white/5 w-full"></div>
                        <div className="flex items-start gap-3">
                            <div className="w-8 h-8 rounded-full bg-purple-500/10 flex items-center justify-center shrink-0">
                                <CreditCard className="w-4 h-4 text-purple-500" />
                            </div>
                            <div>
                                <p className="text-[10px] text-neutral-500 font-bold uppercase tracking-wider mb-0.5">Payment Method</p>
                                <div className="flex items-center gap-2">
                                    <span className="text-xs font-medium text-neutral-900 dark:text-white">Visa ending in {formData.cardNumber.slice(-4) || '4242'}</span>
                                    <div className="px-1.5 py-0.5 rounded bg-neutral-200 dark:bg-neutral-700 text-[9px] font-mono">
                                        {formData.expiry || '12/25'}
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div className="bg-neutral-50 dark:bg-neutral-800/30 rounded-xl p-3 border border-neutral-100 dark:border-white/5">
                        <div className="flex justify-between items-center mb-1.5">
                            <span className="text-xs text-neutral-500">Subtotal</span>
                            <span className="text-xs font-medium text-neutral-900 dark:text-white">$129.00</span>
                        </div>
                        <div className="flex justify-between items-center mb-1.5">
                            <span className="text-xs text-neutral-500">Shipping</span>
                            <span className="text-xs font-medium text-green-600">Free</span>
                        </div>
                        <div className="flex justify-between items-center mb-1.5">
                            <span className="text-xs text-neutral-500">Tax</span>
                            <span className="text-xs font-medium text-neutral-900 dark:text-white">$12.90</span>
                        </div>
                        <div className="h-px bg-neutral-200 dark:bg-white/5 w-full my-2"></div>
                        <div className="flex justify-between items-center">
                            <span className="text-sm font-bold text-neutral-900 dark:text-white">Total</span>
                            <span className="text-sm font-bold text-neutral-900 dark:text-white">$141.90</span>
                        </div>
                    </div>
                </div>

                <div className="flex justify-between pt-2">
                    <button 
                        onClick={handleBack}
                        className="text-xs text-neutral-500 hover:text-neutral-900 dark:hover:text-white font-medium flex items-center gap-1 transition-colors"
                    >
                        <ChevronLeft className="w-3 h-3" /> Back
                    </button>
                    <button 
                        onClick={handleSubmit}
                        disabled={loading}
                        className="group relative inline-flex h-9 items-center justify-center overflow-hidden rounded-lg bg-neutral-950 dark:bg-white px-6 font-medium text-white dark:text-neutral-950 shadow-lg transition-all hover:bg-neutral-800 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-neutral-400 focus:ring-offset-2 text-xs disabled:opacity-70 disabled:cursor-not-allowed min-w-[120px]"
                    >
                        {loading ? (
                            <Loader2 className="w-4 h-4 animate-spin" />
                        ) : (
                            <span className="flex items-center gap-1.5 relative z-10">
                                Pay $141.90 <ShieldCheck className="w-3 h-3" />
                            </span>
                        )}
                         {!loading && (
                            <motion.div 
                                className="absolute inset-0 bg-white/20"
                                initial={{ x: '-100%' }}
                                whileHover={{ x: '100%' }}
                                transition={{ duration: 0.5 }}
                            />
                        )}
                    </button>
                </div>
              </motion.div>
            )}

            {step === 'success' && (
                <motion.div
                    key="success"
                    initial={{ scale: 0.9, opacity: 0 }}
                    animate={{ scale: 1, opacity: 1 }}
                    transition={{ type: "spring", duration: 0.8 }}
                    className="flex flex-col items-center justify-center py-8 text-center"
                >
                    <div className="w-20 h-20 rounded-full bg-green-500/10 flex items-center justify-center mb-4 relative">
                        <motion.div 
                            initial={{ scale: 0 }}
                            animate={{ scale: 1 }}
                            transition={{ type: "spring", delay: 0.2 }}
                        >
                            <div className="w-16 h-16 rounded-full bg-green-500 flex items-center justify-center shadow-lg shadow-green-500/30">
                                <Check className="w-8 h-8 text-white stroke-[3px]" />
                            </div>
                        </motion.div>
                        <div className="absolute inset-0 border-2 border-green-500/20 rounded-full animate-ping"></div>
                    </div>
                    
                    <h2 className="text-xl font-bold text-neutral-900 dark:text-white mb-2">Order Confirmed!</h2>
                    <p className="text-xs text-neutral-500 max-w-[200px] leading-relaxed mb-6">
                        Thank you for your purchase. A confirmation email has been sent to your inbox.
                    </p>

                    <div className="flex gap-3">
                         <button 
                            onClick={() => {
                                setStep('shipping')
                                setFormData({
                                    name: '',
                                    address: '',
                                    city: '',
                                    zip: '',
                                    cardNumber: '',
                                    expiry: '',
                                    cvv: ''
                                })
                            }}
                            className="text-xs text-neutral-500 hover:text-neutral-900 dark:hover:text-white font-medium"
                        >
                            Start New Order
                        </button>
                    </div>
                </motion.div>
            )}
          </AnimatePresence>
        </div>
      </motion.div>
    </div>
  )
}

Dependencies

  • framer-motion: latest
  • lucide-react: latest

Props

Component property reference.

NameTypeDefaultDescription
step'shipping' | 'payment' | 'review' | 'success''shipping'Current step in the flow.
onNext() => voidundefinedCallback to advance to the next step.
onSuccess() => voidundefinedCallback fired when checkout completes.
classNamestringundefinedAdditional CSS classes.
Context Worth Keeping In Orbit

Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.

I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.

I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.