Menu

Change language

GitHub repo

Order Confirmation

blockcommerce

Post-purchase order confirmation & tracking page featuring a success banner with emerald checkmark halo, 4-step shipment progress bar with pulsing active state, itemized order breakdown, and customer delivery & payment cards.

Installation

$npx uipkge-ng@latest add order-confirmation
Manifest:https://uipkge.dev/r/angular/order-confirmation.jsonInstalls to:src/app/components/blocks/order-confirmation/

Variants

Loading interactive previews…

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
  quantity: number
  price: string
  image: string
}
TrackingStep
interface TrackingStep {
  id: string
  title: string
  date: string
  status: 'completed' | 'current' | 'upcoming'
  description: string
}

Files installed (2)

  • src/app/components/blocks/order-confirmation/order-confirmation.component.ts19.5 kB
    // Post-purchase order confirmation & tracking page: success banner, 4-step shipment
    // stepper (grid on md+, vertical list below), itemized summary, and delivery / payment /
    // help cards. The order data lives in the constants below -- edit them in place.
    import { Component, Input, ChangeDetectionStrategy } from '@angular/core'
    import {
      Check,
      CheckCircle2,
      CreditCard,
      Download,
      ExternalLink,
      HelpCircle,
      LucideAngularModule,
      Mail,
      MapPin,
      MessageSquare,
      Package,
      Truck,
    } from 'lucide-angular'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import { cn } from '@/lib/utils'
    
    export interface OrderItem {
      id: string
      name: string
      variant: string
      quantity: number
      price: string
      image: string
    }
    
    export interface TrackingStep {
      id: string
      title: string
      date: string
      status: 'completed' | 'current' | 'upcoming'
      description: string
    }
    
    const orderItems: OrderItem[] = [
      {
        id: 'item-1',
        name: 'Studio Wireless Noise-Canceling Headphones',
        variant: 'Space Gray • Over-Ear',
        quantity: 1,
        price: '$179.00',
        image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=160&auto=format&fit=crop&q=80',
      },
      {
        id: 'item-2',
        name: 'Braided USB-C Fast Charging Cable (2m)',
        variant: 'Midnight Black • 100W PD',
        quantity: 2,
        price: '$38.00',
        image: 'https://images.unsplash.com/photo-1546868871-7041f2a55e12?w=160&auto=format&fit=crop&q=80',
      },
      {
        id: 'item-3',
        name: 'Magnetic Aluminum Desk Stand',
        variant: 'Silver • Anodized Aluminum',
        quantity: 1,
        price: '$35.00',
        image: 'https://images.unsplash.com/photo-1586105251261-72a756497a11?w=160&auto=format&fit=crop&q=80',
      },
    ]
    
    const trackingSteps: TrackingStep[] = [
      {
        id: 'step-1',
        title: 'Order Placed',
        date: 'Aug 21, 2026 • 10:42 AM',
        status: 'completed',
        description: 'Payment authorized & order confirmed',
      },
      {
        id: 'step-2',
        title: 'Processing',
        date: 'Aug 22, 2026 • 02:15 PM',
        status: 'completed',
        description: 'Item picked & packed at warehouse',
      },
      {
        id: 'step-3',
        title: 'Shipped',
        date: 'Aug 23, 2026 • 09:30 AM',
        status: 'current',
        description: 'In transit via FedEx Priority • Chicago, IL',
      },
      {
        id: 'step-4',
        title: 'Delivered',
        date: 'Est. Aug 27, 2026',
        status: 'upcoming',
        description: 'Estimated delivery to front door',
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-order-confirmation, [ui-order-confirmation]',
      standalone: true,
      host: {
        'data-slot': 'order-confirmation',
        '[class]': 'hostClass',
      },
      imports: [
        LucideAngularModule,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiCardDescriptionComponent,
        UiCardContentComponent,
        UiSeparatorComponent,
      ],
      template: `
        <!-- Success Banner Card -->
        <div ui-card class="border shadow-xs">
          <div ui-card-content class="flex flex-col items-center p-6 text-center sm:p-10">
            <div
              class="bg-success/10 text-success ring-success/10 relative flex size-16 items-center justify-center rounded-full ring-8 sm:size-20"
            >
              <lucide-icon [img]="CheckCircle2" class="size-8 sm:size-10" aria-hidden="true" />
            </div>
    
            <div class="mt-6 max-w-xl space-y-2">
              <div
                class="border-success/20 bg-success/10 text-success inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium"
              >
                <span class="bg-success size-1.5 rounded-full"></span>
                Order Confirmed
              </div>
              <h1 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl">Thank you for your order!</h1>
              <p class="text-muted-foreground text-sm sm:text-base">
                We've received your order <span class="text-foreground font-mono font-semibold">#ORD-92841</span> and sent a
                confirmation email to <span class="text-foreground font-medium">customer&#64;example.com</span>.
              </p>
            </div>
    
            <div class="mt-8 flex flex-wrap items-center justify-center gap-3">
              <button ui-button class="gap-2 shadow-xs">
                <lucide-icon [img]="ExternalLink" class="size-4" aria-hidden="true" />
                Track Shipment
              </button>
              <button ui-button aria-label="Download attachment" variant="outline" class="gap-2 shadow-xs">
                <lucide-icon [img]="Download" class="size-4" aria-hidden="true" />
                Download Receipt
              </button>
              <button ui-button variant="ghost" class="text-muted-foreground hover:text-foreground">
                Continue Shopping
              </button>
            </div>
          </div>
        </div>
    
        <!-- Tracking Stepper Card -->
        <div ui-card class="border shadow-xs">
          <div ui-card-header class="flex flex-col gap-3 pb-4 sm:flex-row sm:items-center sm:justify-between">
            <div class="space-y-1">
              <div class="flex items-center gap-2">
                <lucide-icon [img]="Truck" class="text-primary size-4" aria-hidden="true" />
                <h3 ui-card-title class="text-base font-semibold">Shipment Progress</h3>
                <span ui-badge variant="secondary" class="bg-primary/10 text-primary text-xs font-medium">In Transit</span>
              </div>
              <p class="text-muted-foreground text-xs">
                Carrier: <span class="text-foreground font-medium">FedEx Priority</span> • Tracking:
                <span class="text-foreground font-mono font-medium">#FX-9821734910</span>
              </p>
            </div>
            <div class="border-border/80 bg-muted/40 rounded-lg border px-3 py-1.5 text-xs">
              <span class="text-muted-foreground">Estimated Delivery:</span>
              <span class="text-foreground ml-1 font-semibold">Thursday, Aug 27, 2026</span>
            </div>
          </div>
          <div ui-card-content class="pt-4">
            <!-- Desktop Stepper (md+) -->
            <div class="hidden md:block">
              <div class="grid grid-cols-4 gap-4">
                @for (step of trackingSteps; track step.id; let idx = $index, last = $last) {
                  <div class="relative flex flex-col">
                    <div class="relative flex items-center">
                      <!-- Connecting Line before current step -->
                      @if (idx > 0) {
                        <div
                          class="absolute top-1/2 right-1/2 -z-0 h-0.5 w-full -translate-y-1/2"
                          [class.bg-muted]="step.status === 'upcoming'"
                          [class.bg-primary]="step.status !== 'upcoming'"
                        ></div>
                      }
    
                      <!-- Connecting Line after current step -->
                      @if (!last) {
                        <div
                          class="absolute top-1/2 left-1/2 -z-0 h-0.5 w-full -translate-y-1/2"
                          [class.bg-muted]="nextIsUpcoming(idx)"
                          [class.bg-primary]="!nextIsUpcoming(idx)"
                        ></div>
                      }
    
                      <!-- Step Indicator Circle -->
                      <div class="relative z-10 mx-auto flex items-center justify-center">
                        @switch (step.status) {
                          @case ('completed') {
                            <div
                              class="bg-primary text-primary-foreground ring-background flex size-8 items-center justify-center rounded-full ring-4"
                            >
                              <lucide-icon [img]="Check" class="size-4" aria-hidden="true" />
                            </div>
                          }
                          @case ('current') {
                            <div
                              class="border-primary bg-background text-primary ring-background flex size-8 items-center justify-center rounded-full border-2 ring-4"
                            >
                              <span class="relative flex size-2.5">
                                <span class="bg-primary absolute inline-flex h-full w-full rounded-full opacity-75"></span>
                                <span class="bg-primary relative inline-flex size-2.5 rounded-full"></span>
                              </span>
                            </div>
                          }
                          @default {
                            <div
                              class="border-muted-foreground/30 bg-muted/40 text-muted-foreground ring-background flex size-8 items-center justify-center rounded-full border-2 ring-4"
                            >
                              <lucide-icon [img]="Package" class="size-3.5" aria-hidden="true" />
                            </div>
                          }
                        }
                      </div>
                    </div>
    
                    <!-- Step Info -->
                    <div class="mt-3 text-center">
                      <p
                        class="text-sm font-semibold"
                        [class.text-muted-foreground]="step.status === 'upcoming'"
                        [class.text-foreground]="step.status !== 'upcoming'"
                      >
                        {{ step.title }}
                      </p>
                      <p class="text-muted-foreground mt-0.5 text-xs tabular-nums">{{ step.date }}</p>
                      <p class="text-muted-foreground/80 mt-0.5 text-xs">{{ step.description }}</p>
                    </div>
                  </div>
                }
              </div>
            </div>
    
            <!-- Mobile Stepper (< md) -->
            <div class="space-y-4 md:hidden">
              @for (step of trackingSteps; track step.id; let idx = $index, last = $last) {
                <div class="flex items-start gap-3">
                  <div class="flex flex-col items-center">
                    @switch (step.status) {
                      @case ('completed') {
                        <div
                          class="bg-primary text-primary-foreground flex size-7 shrink-0 items-center justify-center rounded-full"
                        >
                          <lucide-icon [img]="Check" class="size-3.5" aria-hidden="true" />
                        </div>
                      }
                      @case ('current') {
                        <div
                          class="border-primary bg-background text-primary flex size-7 shrink-0 items-center justify-center rounded-full border-2"
                        >
                          <span class="relative flex size-2">
                            <span class="bg-primary absolute inline-flex h-full w-full rounded-full opacity-75"></span>
                            <span class="bg-primary relative inline-flex size-2 rounded-full"></span>
                          </span>
                        </div>
                      }
                      @default {
                        <div
                          class="border-muted-foreground/30 bg-muted/40 text-muted-foreground flex size-7 shrink-0 items-center justify-center rounded-full border-2"
                        >
                          <lucide-icon [img]="Package" class="size-3" aria-hidden="true" />
                        </div>
                      }
                    }
    
                    @if (!last) {
                      <div
                        class="mt-1 h-8 w-0.5"
                        [class.bg-muted]="nextIsUpcoming(idx)"
                        [class.bg-primary]="!nextIsUpcoming(idx)"
                      ></div>
                    }
                  </div>
    
                  <div class="min-w-0 flex-1 pt-0.5 pb-2">
                    <div class="flex items-center justify-between gap-2">
                      <p
                        class="text-sm font-semibold"
                        [class.text-muted-foreground]="step.status === 'upcoming'"
                        [class.text-foreground]="step.status !== 'upcoming'"
                      >
                        {{ step.title }}
                      </p>
                      <span class="text-muted-foreground text-xs tabular-nums">{{ step.date }}</span>
                    </div>
                    <p class="text-muted-foreground mt-0.5 text-xs">{{ step.description }}</p>
                  </div>
                </div>
              }
            </div>
          </div>
        </div>
    
        <!-- 2-Column Details Grid -->
        <div class="grid grid-cols-1 gap-6 lg:grid-cols-12">
          <!-- Left Column: Itemized Order Summary -->
          <div class="lg:col-span-7">
            <div ui-card class="border shadow-xs">
              <div ui-card-header class="pb-4">
                <h3 ui-card-title class="text-base font-semibold">Order Summary</h3>
                <p ui-card-description>3 items in shipment</p>
              </div>
              <div ui-card-content class="space-y-6">
                <!-- Items list -->
                <div class="space-y-4">
                  @for (item of orderItems; track item.id) {
                    <div class="flex items-center gap-4">
                      <div class="border-border bg-muted/40 relative size-16 shrink-0 overflow-hidden rounded-lg border">
                        <img [src]="item.image" [alt]="item.name" class="h-full w-full object-cover" />
                      </div>
                      <div class="min-w-0 flex-1">
                        <p class="text-foreground truncate text-sm font-medium">{{ item.name }}</p>
                        <p class="text-muted-foreground text-xs">{{ item.variant }}</p>
                        <p class="text-muted-foreground mt-1 text-xs">Qty: {{ item.quantity }}</p>
                      </div>
                      <div class="text-right">
                        <p class="text-foreground text-sm font-semibold tabular-nums">{{ item.price }}</p>
                      </div>
                    </div>
                  }
                </div>
    
                <div ui-separator></div>
    
                <!-- Cost breakdown -->
                <div class="space-y-2 text-sm">
                  <div class="flex items-center justify-between">
                    <span class="text-muted-foreground">Subtotal</span>
                    <span class="text-foreground font-medium tabular-nums">$252.00</span>
                  </div>
                  <div class="flex items-center justify-between">
                    <span class="text-muted-foreground">Shipping (FedEx Priority)</span>
                    <span class="text-success font-medium tabular-nums">Free</span>
                  </div>
                  <div class="flex items-center justify-between">
                    <span class="text-muted-foreground">Estimated Tax</span>
                    <span class="text-foreground font-medium tabular-nums">$12.50</span>
                  </div>
                </div>
    
                <div ui-separator></div>
    
                <!-- Total -->
                <div class="flex items-center justify-between">
                  <span class="text-foreground text-base font-semibold">Total</span>
                  <span class="text-foreground text-xl font-bold tabular-nums sm:text-2xl">$264.50</span>
                </div>
              </div>
            </div>
          </div>
    
          <!-- Right Column: Customer & Delivery Details -->
          <div class="space-y-6 lg:col-span-5">
            <!-- Shipping Address Card -->
            <div ui-card class="border shadow-xs">
              <div ui-card-header class="pb-3">
                <div class="flex items-center gap-2">
                  <lucide-icon [img]="MapPin" class="text-muted-foreground size-4" aria-hidden="true" />
                  <h3 ui-card-title class="text-base font-semibold">Shipping Address</h3>
                </div>
              </div>
              <div ui-card-content class="space-y-3 text-sm">
                <div class="space-y-0.5">
                  <p class="text-foreground font-medium">Alex Morgan</p>
                  <p class="text-muted-foreground">742 Evergreen Terrace, Suite 4B</p>
                  <p class="text-muted-foreground">Springfield, OR 97477</p>
                  <p class="text-muted-foreground">United States</p>
                  <p class="text-muted-foreground mt-1 text-xs">+1 (555) 234-5678</p>
                </div>
                <div class="border-border bg-muted/30 rounded-md border p-2.5 text-xs">
                  <span class="text-muted-foreground">Delivery Method:</span>
                  <span class="text-foreground ml-1 font-medium">FedEx Priority Express</span>
                </div>
              </div>
            </div>
    
            <!-- Payment Method Card -->
            <div ui-card class="border shadow-xs">
              <div ui-card-header class="pb-3">
                <div class="flex items-center gap-2">
                  <lucide-icon [img]="CreditCard" class="text-muted-foreground size-4" aria-hidden="true" />
                  <h3 ui-card-title class="text-base font-semibold">Payment Method</h3>
                </div>
              </div>
              <div ui-card-content class="space-y-3 text-sm">
                <div class="flex items-center justify-between">
                  <div class="flex items-center gap-2.5">
                    <div
                      class="border-border bg-muted/40 text-foreground flex h-7 w-11 items-center justify-center rounded border font-mono text-xs font-bold"
                    >
                      VISA
                    </div>
                    <div>
                      <p class="text-foreground text-sm font-medium">Visa ending in 4242</p>
                      <p class="text-muted-foreground text-xs">Expires 08/29</p>
                    </div>
                  </div>
                  <span ui-badge variant="secondary" class="bg-success/10 text-success">Paid</span>
                </div>
                <p class="text-muted-foreground text-xs">Billing address matches shipping address</p>
              </div>
            </div>
    
            <!-- Need Help Card -->
            <div ui-card class="border shadow-xs">
              <div ui-card-header class="pb-3">
                <div class="flex items-center gap-2">
                  <lucide-icon [img]="HelpCircle" class="text-muted-foreground size-4" aria-hidden="true" />
                  <h3 ui-card-title class="text-base font-semibold">Need Help?</h3>
                </div>
              </div>
              <div ui-card-content class="space-y-3 text-sm">
                <p class="text-muted-foreground text-xs">
                  Questions about your order? Chat with support or email support&#64;example.com.
                </p>
                <div class="space-y-2 pt-1">
                  <button ui-button variant="outline" size="sm" class="w-full justify-start gap-2 text-xs shadow-xs">
                    <lucide-icon [img]="MessageSquare" class="size-3.5" aria-hidden="true" />
                    Chat with Support
                  </button>
                  <button
                    ui-button
                    variant="ghost"
                    size="sm"
                    class="text-muted-foreground hover:text-foreground w-full justify-start gap-2 text-xs"
                  >
                    <lucide-icon [img]="Mail" class="size-3.5" aria-hidden="true" />
                    support&#64;example.com
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>
      `,
    })
    export class UiOrderConfirmationComponent {
      @Input('class') className?: string
    
      protected readonly orderItems = orderItems
      protected readonly trackingSteps = trackingSteps
    
      protected readonly Check = Check
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly CreditCard = CreditCard
      protected readonly Download = Download
      protected readonly ExternalLink = ExternalLink
      protected readonly HelpCircle = HelpCircle
      protected readonly Mail = Mail
      protected readonly MapPin = MapPin
      protected readonly MessageSquare = MessageSquare
      protected readonly Package = Package
      protected readonly Truck = Truck
    
      get hostClass(): string {
        return cn('block w-full space-y-6', this.className)
      }
    
      /** The connector into step `idx + 1` is muted while that step is still upcoming. */
      protected nextIsUpcoming(idx: number): boolean {
        return trackingSteps[idx + 1]?.status === 'upcoming'
      }
    }
    
  • src/app/components/blocks/order-confirmation/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/order-confirmation.json