UIPackage
Menu

Framework

Change language

Boilerplate repo

Returns Portal

blockcommerce

Self-serve e-commerce returns & exchange portal wizard: Stepper header (Find Order → Select Items → Choose Resolution → Confirm & Print Label), order lookup with email and order number, item selection list with return reasons and quantities, resolution selector (size exchange, store credit with +10% bonus, or original payment refund), and confirmation screen with printable prepaid shipping label, QR code drop-off instructions, and return summary.

Also available for Vue ->

Installation

$npx shadcn@latest add https://uipkge.dev/r/react/returns-portal.json
Named registry:npx shadcn@latest add @uipkge-react/returns-portalInstalls to:components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
initialStepnumberoptional
initialOrderNumberstringoptional
initialEmailstringoptional
classNamestringoptional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

OrderItem
interface OrderItem {
  id: string
  name: string
  variant: string
  sku: string
  price: number
  maxQty: number
}

Files installed (1)

  • components/blocks/ReturnsPortal.tsx32.4 kB
    'use client'
    
    import * as React from 'react'
    import {
      Check,
      CheckCircle2,
      ChevronLeft,
      ChevronRight,
      CreditCard,
      Download,
      Printer,
      RefreshCw,
      ShieldCheck,
      Gift,
      Shirt,
      Truck,
    } from 'lucide-react'
    import { cn } from '@/lib/utils'
    import { Badge } from '@/components/ui/badge'
    import { Button } from '@/components/ui/button'
    import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
    import { Checkbox } from '@/components/ui/checkbox'
    import { Input } from '@/components/ui/input'
    import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
    import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
    import { Stepper } from '@/components/ui/stepper'
    
    export interface OrderItem {
      id: string
      name: string
      variant: string
      sku: string
      price: number
      maxQty: number
    }
    
    export interface ReturnsPortalProps {
      initialStep?: number
      initialOrderNumber?: string
      initialEmail?: string
      className?: string
    }
    
    const steps = [
      { id: 1, title: 'Find Order' },
      { id: 2, title: 'Select Items' },
      { id: 3, title: 'Choose Resolution' },
      { id: 4, title: 'Confirm & Print' },
    ]
    
    const orderItems: OrderItem[] = [
      {
        id: 'item-1',
        name: 'Merino Wool Crewneck Sweater',
        variant: 'Midnight Navy / Size L',
        sku: 'MWC-NAV-L',
        price: 120,
        maxQty: 1,
      },
      {
        id: 'item-2',
        name: 'Classic Canvas High-Tops',
        variant: 'Off-White / US 10.5',
        sku: 'CCHT-OW-105',
        price: 85,
        maxQty: 1,
      },
      {
        id: 'item-3',
        name: 'Tailored Chino Trousers',
        variant: 'Olive / 32x32',
        sku: 'TCT-OLV-32',
        price: 95,
        maxQty: 2,
      },
    ]
    
    const returnReasonsList = ['Wrong size', 'Item defective', 'Not as described', 'Changed mind', 'Arrived late']
    
    const exchangeSizesList = ['Small (S)', 'Medium (M)', 'Large (L)', 'Extra Large (XL)']
    
    export function ReturnsPortal({
      initialStep = 1,
      initialOrderNumber = '#ORD-84920',
      initialEmail = '[email protected]',
      className,
    }: ReturnsPortalProps) {
      const [step, setStep] = React.useState(initialStep)
      const [orderNumber, setOrderNumber] = React.useState(initialOrderNumber)
      const [email, setEmail] = React.useState(initialEmail)
    
      const [selectedItems, setSelectedItems] = React.useState<Record<string, boolean>>({
        'item-1': true,
        'item-2': false,
        'item-3': false,
      })
    
      const [returnQuantities, setReturnQuantities] = React.useState<Record<string, number>>({
        'item-1': 1,
        'item-2': 1,
        'item-3': 1,
      })
    
      const [returnReasons, setReturnReasons] = React.useState<Record<string, string>>({
        'item-1': 'Wrong size',
        'item-2': 'Changed mind',
        'item-3': 'Item defective',
      })
    
      const [resolution, setResolution] = React.useState<'exchange' | 'store-credit' | 'original-payment'>('store-credit')
      const [exchangeSize, setExchangeSize] = React.useState('Medium (M)')
      const [isDownloaded, setIsDownloaded] = React.useState(false)
    
      React.useEffect(() => {
        setStep(initialStep)
      }, [initialStep])
    
      const selectedItemsCount = orderItems.reduce((acc, item) => {
        if (selectedItems[item.id]) {
          return acc + (returnQuantities[item.id] || 1)
        }
        return acc
      }, 0)
    
      const itemsSubtotal = orderItems.reduce((acc, item) => {
        if (selectedItems[item.id]) {
          return acc + item.price * (returnQuantities[item.id] || 1)
        }
        return acc
      }, 0)
    
      const storeCreditBonus = itemsSubtotal * 0.1
      const totalStoreCredit = itemsSubtotal + storeCreditBonus
    
      function formatCurrency(val: number) {
        return new Intl.NumberFormat('en-US', {
          style: 'currency',
          currency: 'USD',
        }).format(val)
      }
    
      function onStepperInput(value: number) {
        if (value < step && step !== 4) {
          setStep(value)
        }
      }
    
      function goToNext() {
        if (step < 4) {
          setStep(step + 1)
        }
      }
    
      function goToPrevious() {
        if (step > 1 && step < 4) {
          setStep(step - 1)
        }
      }
    
      function toggleItemSelection(id: string) {
        setSelectedItems((prev) => ({
          ...prev,
          [id]: !prev[id],
        }))
      }
    
      function reset() {
        setStep(1)
        setIsDownloaded(false)
        setSelectedItems({
          'item-1': true,
          'item-2': false,
          'item-3': false,
        })
        setReturnQuantities({
          'item-1': 1,
          'item-2': 1,
          'item-3': 1,
        })
        setReturnReasons({
          'item-1': 'Wrong size',
          'item-2': 'Changed mind',
          'item-3': 'Item defective',
        })
        setResolution('store-credit')
        setExchangeSize('Medium (M)')
      }
    
      return (
        <Card data-slot="returns-portal" className={cn('border-border mx-auto w-full max-w-3xl shadow-xs', className)}>
          <CardHeader className="pb-4">
            <div className="flex items-center justify-between gap-4">
              <div>
                <CardTitle className="text-base font-semibold">Returns & Exchange Portal</CardTitle>
                <CardDescription className="text-xs">
                  Self-serve return, exchange, or refund for your recent order.
                </CardDescription>
              </div>
              <Badge variant="outline" className="shrink-0 text-xs">
                <Truck className="text-muted-foreground mr-1 size-3" />
                Prepaid Shipping
              </Badge>
            </div>
          </CardHeader>
    
          <CardContent className="space-y-6">
            <Stepper steps={steps} value={step} onValueChange={onStepperInput} className="mb-6" />
    
            {/* Step 1: Find Order */}
            {step === 1 && (
              <div className="space-y-5">
                <div className="space-y-1">
                  <h3 className="text-foreground text-sm font-semibold">Find your order</h3>
                  <p className="text-muted-foreground text-xs">
                    Enter your order number and email address to start a return or exchange.
                  </p>
                </div>
    
                <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
                  <div className="space-y-1.5">
                    <label htmlFor="returns-order-number" className="text-foreground text-xs font-medium">
                      Order number
                    </label>
                    <Input
                      id="returns-order-number"
                      value={orderNumber}
                      onChange={(e) => setOrderNumber(e.target.value)}
                      placeholder="#ORD-84920"
                      autoComplete="off"
                    />
                    <p className="text-muted-foreground text-xs">Found on your order confirmation email.</p>
                  </div>
    
                  <div className="space-y-1.5">
                    <label htmlFor="returns-email" className="text-foreground text-xs font-medium">
                      Email address
                    </label>
                    <Input
                      id="returns-email"
                      value={email}
                      onChange={(e) => setEmail(e.target.value)}
                      type="email"
                      placeholder="[email protected]"
                      autoComplete="email"
                    />
                    <p className="text-muted-foreground text-xs">The email address used at checkout.</p>
                  </div>
                </div>
    
                <div className="border-border bg-muted/40 flex items-start gap-3 rounded-lg border p-3.5">
                  <ShieldCheck className="text-primary mt-0.5 size-4 shrink-0" />
                  <div className="space-y-0.5 text-xs">
                    <p className="text-foreground font-medium">30-Day Hassle-Free Return Policy</p>
                    <p className="text-muted-foreground">
                      Eligible items can be returned within 30 days of delivery. Free shipping on all exchanges and store
                      credit requests.
                    </p>
                  </div>
                </div>
              </div>
            )}
    
            {/* Step 2: Select Items */}
            {step === 2 && (
              <div className="space-y-5">
                <div className="flex flex-wrap items-center justify-between gap-2">
                  <div className="min-w-[12rem]">
                    <h3 className="text-foreground text-sm font-semibold">Select items to return</h3>
                    <p className="text-muted-foreground text-xs">
                      Order {orderNumber || '#ORD-84920'} • Placed Oct 14, 2026 • 3 items eligible
                    </p>
                  </div>
                  <Badge variant="secondary" className="text-xs">
                    {selectedItemsCount} of 3 selected
                  </Badge>
                </div>
    
                <div className="space-y-3">
                  {orderItems.map((item) => (
                    <div
                      key={item.id}
                      className={cn(
                        'rounded-lg border p-4 transition-colors',
                        selectedItems[item.id]
                          ? 'border-primary/50 bg-primary/[0.02] dark:bg-primary/10 shadow-xs'
                          : 'border-border bg-card hover:bg-muted/20',
                      )}
                    >
                      <div className="flex items-start gap-3.5">
                        <div className="pt-0.5">
                          <Checkbox
                            id={`check-${item.id}`}
                            checked={selectedItems[item.id]}
                            onCheckedChange={() => toggleItemSelection(item.id)}
                          />
                        </div>
    
                        <div className="border-border bg-muted/60 text-muted-foreground flex size-12 shrink-0 items-center justify-center rounded-md border">
                          <Shirt className="size-6" />
                        </div>
    
                        <div className="min-w-0 flex-1">
                          <div className="flex items-start justify-between gap-2">
                            <div>
                              <label
                                htmlFor={`check-${item.id}`}
                                className="text-foreground cursor-pointer text-sm font-medium select-none"
                              >
                                {item.name}
                              </label>
                              <p className="text-muted-foreground text-xs">
                                {item.variant} • SKU: {item.sku}
                              </p>
                            </div>
                            <span className="text-foreground shrink-0 text-sm font-semibold">
                              {formatCurrency(item.price)}
                            </span>
                          </div>
                        </div>
                      </div>
    
                      {selectedItems[item.id] && (
                        <div className="border-border/60 mt-3.5 grid grid-cols-1 gap-3 border-t pt-3.5 sm:grid-cols-2">
                          <div className="space-y-1.5">
                            <label className="text-foreground text-xs font-medium">Qty to return</label>
                            <Select
                              value={String(returnQuantities[item.id] || 1)}
                              onValueChange={(val) => setReturnQuantities((prev) => ({ ...prev, [item.id]: Number(val) }))}
                            >
                              <SelectTrigger className="h-8 w-full text-xs">
                                <SelectValue placeholder="Qty" />
                              </SelectTrigger>
                              <SelectContent>
                                {Array.from({ length: item.maxQty }, (_, idx) => idx + 1).map((q) => (
                                  <SelectItem key={q} value={String(q)}>
                                    {q} {q === 1 ? 'item' : 'items'} (of {item.maxQty})
                                  </SelectItem>
                                ))}
                              </SelectContent>
                            </Select>
                          </div>
    
                          <div className="space-y-1.5">
                            <label className="text-foreground text-xs font-medium">Return reason</label>
                            <Select
                              value={returnReasons[item.id]}
                              onValueChange={(val) => setReturnReasons((prev) => ({ ...prev, [item.id]: val }))}
                            >
                              <SelectTrigger className="h-8 w-full text-xs">
                                <SelectValue placeholder="Select reason" />
                              </SelectTrigger>
                              <SelectContent>
                                {returnReasonsList.map((reason) => (
                                  <SelectItem key={reason} value={reason}>
                                    {reason}
                                  </SelectItem>
                                ))}
                              </SelectContent>
                            </Select>
                          </div>
                        </div>
                      )}
                    </div>
                  ))}
                </div>
    
                <div className="border-border bg-muted/40 flex items-center justify-between rounded-lg border p-3 text-xs">
                  <span className="text-muted-foreground">
                    {selectedItemsCount > 0 ? (
                      <>
                        <span className="text-foreground font-medium">{selectedItemsCount}</span> item
                        {selectedItemsCount === 1 ? '' : 's'} selected
                      </>
                    ) : (
                      'No items selected yet'
                    )}
                  </span>
                  <span className="text-foreground font-medium">Estimated Value: {formatCurrency(itemsSubtotal)}</span>
                </div>
              </div>
            )}
    
            {/* Step 3: Choose Resolution */}
            {step === 3 && (
              <div className="space-y-5">
                <div className="space-y-1">
                  <h3 className="text-foreground text-sm font-semibold">Choose return resolution</h3>
                  <p className="text-muted-foreground text-xs">Select how you would like your return or refund handled.</p>
                </div>
    
                <RadioGroup
                  value={resolution}
                  onValueChange={(val) => setResolution(val as typeof resolution)}
                  className="gap-3"
                >
                  {/* Option A: Exchange */}
                  <div
                    className={cn(
                      'flex cursor-pointer flex-col gap-3 rounded-lg border p-4 transition-colors',
                      resolution === 'exchange'
                        ? 'border-primary bg-primary/[0.02] dark:bg-primary/10 shadow-xs'
                        : 'border-border hover:bg-muted/40',
                    )}
                    onClick={() => setResolution('exchange')}
                  >
                    <div className="flex items-start gap-3">
                      <RadioGroupItem id="res-exchange" value="exchange" className="mt-0.5" />
                      <div className="flex-1 space-y-1">
                        <div className="flex items-center gap-2">
                          <RefreshCw className="text-primary size-4" />
                          <label htmlFor="res-exchange" className="text-foreground cursor-pointer text-sm font-medium">
                            Exchange for different size / color
                          </label>
                          <Badge variant="outline" className="text-xs">
                            Free shipping
                          </Badge>
                        </div>
                        <p className="text-muted-foreground text-xs">
                          Reserve replacement item immediately. Ships as soon as carrier scans the return package.
                        </p>
                      </div>
                    </div>
    
                    {resolution === 'exchange' && (
                      <div
                        className="border-border/60 mt-1 space-y-1.5 border-t pt-3 pl-7"
                        onClick={(e) => e.stopPropagation()}
                      >
                        <label className="text-foreground text-xs font-medium">Select replacement size</label>
                        <Select value={exchangeSize} onValueChange={setExchangeSize}>
                          <SelectTrigger className="h-8 w-full max-w-xs text-xs">
                            <SelectValue placeholder="Select replacement size" />
                          </SelectTrigger>
                          <SelectContent>
                            {exchangeSizesList.map((size) => (
                              <SelectItem key={size} value={size}>
                                {size}
                              </SelectItem>
                            ))}
                          </SelectContent>
                        </Select>
                      </div>
                    )}
                  </div>
    
                  {/* Option B: Store Credit (+10% Bonus) */}
                  <div
                    className={cn(
                      'flex cursor-pointer flex-col gap-3 rounded-lg border p-4 transition-colors',
                      resolution === 'store-credit'
                        ? 'border-primary bg-primary/[0.02] dark:bg-primary/10 shadow-xs'
                        : 'border-border hover:bg-muted/40',
                    )}
                    onClick={() => setResolution('store-credit')}
                  >
                    <div className="flex items-start gap-3">
                      <RadioGroupItem id="res-store-credit" value="store-credit" className="mt-0.5" />
                      <div className="flex-1 space-y-1">
                        <div className="flex flex-wrap items-center gap-2">
                          <Gift className="text-success size-4" />
                          <label htmlFor="res-store-credit" className="text-foreground cursor-pointer text-sm font-medium">
                            Store Credit (+10% Bonus value)
                          </label>
                          <Badge variant="success" className="text-xs">
                            +10% Bonus
                          </Badge>
                          <Badge variant="secondary" className="text-xs">
                            Fastest refund
                          </Badge>
                        </div>
                        <p className="text-muted-foreground text-xs">
                          Get <span className="text-foreground font-medium">{formatCurrency(totalStoreCredit)}</span> in
                          store credit ({formatCurrency(itemsSubtotal)} + {formatCurrency(storeCreditBonus)} bonus). Digital
                          card delivered by email instantly upon drop-off scan.
                        </p>
                      </div>
                    </div>
                  </div>
    
                  {/* Option C: Original Payment Method */}
                  <div
                    className={cn(
                      'flex cursor-pointer flex-col gap-3 rounded-lg border p-4 transition-colors',
                      resolution === 'original-payment'
                        ? 'border-primary bg-primary/[0.02] dark:bg-primary/10 shadow-xs'
                        : 'border-border hover:bg-muted/40',
                    )}
                    onClick={() => setResolution('original-payment')}
                  >
                    <div className="flex items-start gap-3">
                      <RadioGroupItem id="res-original" value="original-payment" className="mt-0.5" />
                      <div className="flex-1 space-y-1">
                        <div className="flex flex-wrap items-center gap-2">
                          <CreditCard className="text-muted-foreground size-4" />
                          <label htmlFor="res-original" className="text-foreground cursor-pointer text-sm font-medium">
                            Original Payment Method
                          </label>
                          <Badge variant="outline" className="text-xs">
                            Visa •••• 4242
                          </Badge>
                        </div>
                        <p className="text-muted-foreground text-xs">
                          Refund of {formatCurrency(itemsSubtotal)} back to your card. Processed in 3–5 business days after
                          carrier drop-off.
                        </p>
                      </div>
                    </div>
                  </div>
                </RadioGroup>
    
                {/* Summary calculation box */}
                <div className="border-border bg-muted/40 space-y-2 rounded-lg border p-4 text-xs">
                  <div className="text-muted-foreground flex items-center justify-between">
                    <span>Selected items subtotal</span>
                    <span className="text-foreground font-medium">{formatCurrency(itemsSubtotal)}</span>
                  </div>
                  {resolution === 'store-credit' && (
                    <div className="text-success flex items-center justify-between">
                      <span>Bonus store credit (+10%)</span>
                      <span className="font-medium">+{formatCurrency(storeCreditBonus)}</span>
                    </div>
                  )}
                  <div className="text-muted-foreground flex items-center justify-between">
                    <span>Prepaid return shipping</span>
                    <span className="text-success font-medium">FREE</span>
                  </div>
                  <div className="border-border text-foreground flex items-center justify-between border-t pt-2 text-sm font-semibold">
                    <span>
                      {resolution === 'store-credit'
                        ? 'Total Store Credit'
                        : resolution === 'exchange'
                          ? 'Exchange Value'
                          : 'Total Refund'}
                    </span>
                    <span className={resolution === 'store-credit' ? 'text-success' : ''}>
                      {resolution === 'store-credit' ? formatCurrency(totalStoreCredit) : formatCurrency(itemsSubtotal)}
                    </span>
                  </div>
                </div>
              </div>
            )}
    
            {/* Step 4: Confirmation & Label */}
            {step === 4 && (
              <div className="space-y-6">
                <div className="space-y-2 py-2 text-center">
                  <div className="border-success/20 bg-success/10 text-success relative mx-auto flex size-12 items-center justify-center rounded-full border">
                    <CheckCircle2 className="size-6" />
                  </div>
                  <h3 className="text-foreground text-base font-semibold">Return Authorized & Confirmed</h3>
                  <p className="text-muted-foreground mx-auto max-w-md text-xs">
                    Your return request has been submitted. A prepaid shipping label and confirmation receipt have been sent
                    to <span className="text-foreground font-medium">{email || '[email protected]'}</span>.
                  </p>
                </div>
    
                {/* Return Details Overview */}
                <div className="border-border bg-muted/20 space-y-3 rounded-lg border p-4">
                  <div className="border-border flex flex-wrap items-center justify-between gap-2 border-b pb-3">
                    <div className="space-y-0.5">
                      <span className="text-muted-foreground text-xs">Return ID</span>
                      <p className="text-foreground font-mono text-sm font-semibold">#RET-2026-849</p>
                    </div>
                    <Badge variant="success" className="gap-1 text-xs">
                      <Check className="size-3" /> Authorized
                    </Badge>
                  </div>
    
                  <div className="grid grid-cols-1 gap-3 text-xs sm:grid-cols-3">
                    <div className="space-y-0.5">
                      <span className="text-muted-foreground">Carrier</span>
                      <p className="text-foreground font-medium">USPS Ground Advantage™</p>
                    </div>
                    <div className="space-y-0.5">
                      <span className="text-muted-foreground">Tracking Number</span>
                      <p className="text-foreground font-mono font-medium">9400 1118 9956 2849</p>
                    </div>
                    <div className="space-y-0.5">
                      <span className="text-muted-foreground">Selected Resolution</span>
                      <p className="text-foreground font-medium">
                        {resolution === 'store-credit'
                          ? `Store Credit (${formatCurrency(totalStoreCredit)})`
                          : resolution === 'exchange'
                            ? `Exchange (${exchangeSize})`
                            : `Refund to Visa •••• 4242`}
                      </p>
                    </div>
                  </div>
                </div>
    
                {/* Action Download Buttons */}
                <div className="flex flex-col gap-3 sm:flex-row">
                  <Button
                    aria-label="Download attachment"
                    className="flex-1 gap-2"
                    size="default"
                    onClick={() => setIsDownloaded(true)}
                  >
                    <Download className="size-4" />
                    {isDownloaded ? 'Label Downloaded (PDF)' : 'Download Prepaid Shipping Label (PDF)'}
                  </Button>
                  <Button variant="outline" className="gap-2" size="default">
                    <Printer className="size-4" />
                    Print Return Slip
                  </Button>
                </div>
    
                {/* Drop-off instructions & QR code card */}
                <div className="border-border bg-card space-y-4 rounded-lg border p-4">
                  <div className="flex items-center justify-between">
                    <h4 className="text-foreground text-xs font-semibold tracking-wide uppercase">
                      Carrier Drop-off Pass & Instructions
                    </h4>
                    <Badge variant="outline" className="text-xs">
                      No printer required
                    </Badge>
                  </div>
    
                  <div className="grid grid-cols-1 items-center gap-4 sm:grid-cols-3">
                    <div className="space-y-3 text-xs sm:col-span-2">
                      <div className="flex items-start gap-2.5">
                        <div className="bg-muted text-foreground mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-xs font-medium">
                          1
                        </div>
                        <div className="space-y-0.5">
                          <p className="text-foreground font-medium">Pack your items</p>
                          <p className="text-muted-foreground">
                            Place returned items with tags inside the original shipping bag or any sturdy box.
                          </p>
                        </div>
                      </div>
    
                      <div className="flex items-start gap-2.5">
                        <div className="bg-muted text-foreground mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-xs font-medium">
                          2
                        </div>
                        <div className="space-y-0.5">
                          <p className="text-foreground font-medium">Attach label or show QR code</p>
                          <p className="text-muted-foreground">
                            Tape the downloaded shipping label to the box, or present the digital QR pass at the drop-off
                            counter.
                          </p>
                        </div>
                      </div>
    
                      <div className="flex items-start gap-2.5">
                        <div className="bg-muted text-foreground mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full text-xs font-medium">
                          3
                        </div>
                        <div className="space-y-0.5">
                          <p className="text-foreground font-medium">Drop off before Nov 15, 2026</p>
                          <p className="text-muted-foreground">
                            Bring to any USPS Post Office, USPS drop box, or authorized FedEx shipping point.
                          </p>
                        </div>
                      </div>
                    </div>
    
                    {/* QR code visual */}
                    <div className="border-border bg-muted/40 flex flex-col items-center justify-center space-y-2 rounded-lg border p-3 text-center sm:col-span-1">
                      <svg
                        className="text-foreground size-24"
                        viewBox="0 0 100 100"
                        fill="currentColor"
                        aria-label="Prepaid return shipping QR dropoff code"
                      >
                        {/* Top-left finder */}
                        <rect
                          x="10"
                          y="10"
                          width="24"
                          height="24"
                          rx="2"
                          fill="none"
                          stroke="currentColor"
                          strokeWidth="4"
                        />
                        <rect x="17" y="17" width="10" height="10" rx="1" fill="currentColor" />
                        {/* Top-right finder */}
                        <rect
                          x="66"
                          y="10"
                          width="24"
                          height="24"
                          rx="2"
                          fill="none"
                          stroke="currentColor"
                          strokeWidth="4"
                        />
                        <rect x="73" y="17" width="10" height="10" rx="1" fill="currentColor" />
                        {/* Bottom-left finder */}
                        <rect
                          x="10"
                          y="66"
                          width="24"
                          height="24"
                          rx="2"
                          fill="none"
                          stroke="currentColor"
                          strokeWidth="4"
                        />
                        <rect x="17" y="73" width="10" height="10" rx="1" fill="currentColor" />
                        {/* Data dots */}
                        <rect x="42" y="12" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="52" y="12" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="42" y="24" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="52" y="24" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="12" y="42" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="24" y="42" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="36" y="42" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="48" y="42" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="60" y="42" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="72" y="42" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="84" y="42" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="42" y="54" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="54" y="54" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="66" y="54" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="78" y="54" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="42" y="66" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="54" y="66" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="66" y="78" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="78" y="66" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="42" y="78" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="54" y="78" width="6" height="6" rx="1" fill="currentColor" />
                        <rect x="84" y="78" width="6" height="6" rx="1" fill="currentColor" />
                      </svg>
                      <span className="text-foreground font-mono text-xs font-semibold">RET-2026-849</span>
                    </div>
                  </div>
                </div>
    
                <div className="pt-2 text-center">
                  <Button
                    variant="ghost"
                    size="sm"
                    className="text-muted-foreground hover:text-foreground text-xs"
                    onClick={reset}
                  >
                    Start another return
                  </Button>
                </div>
              </div>
            )}
          </CardContent>
    
          {step < 4 && (
            <CardFooter className="border-border flex flex-wrap items-center justify-between gap-2 border-t pt-2">
              {step > 1 ? (
                <Button variant="ghost" size="sm" onClick={goToPrevious}>
                  <ChevronLeft className="mr-1 size-4" />
                  Back
                </Button>
              ) : (
                <div />
              )}
    
              <span className="text-muted-foreground text-xs">Step {step} of 4</span>
    
              {step === 1 && (
                <Button size="sm" disabled={!orderNumber.trim() || !email.trim()} onClick={goToNext}>
                  Find Order
                  <ChevronRight className="ml-1 size-4" />
                </Button>
              )}
    
              {step === 2 && (
                <Button size="sm" disabled={selectedItemsCount === 0} onClick={goToNext}>
                  Choose Resolution
                  <ChevronRight className="ml-1 size-4" />
                </Button>
              )}
    
              {step === 3 && (
                <Button size="sm" onClick={goToNext}>
                  Confirm & Print Label
                  <Check className="ml-1 size-4" />
                </Button>
              )}
            </CardFooter>
          )}
        </Card>
      )
    }
    

Raw manifest:https://uipkge.dev/r/react/returns-portal.json