Gift Card Balance Checker
blockcommerceCustomer storefront gift card balance lookup, live digital card passbook, quick fund reloading, and complete redemption audit history ledger.
Installation
$pnpm dlx uipkge-ng@latest add gift-card-balance-checker$npx uipkge-ng@latest add gift-card-balance-checker$yarn dlx uipkge-ng@latest add gift-card-balance-checker$bunx uipkge-ng@latest add gift-card-balance-checkerManifest:
https://uipkge.dev/r/angular/gift-card-balance-checker.jsonInstalls to:src/app/components/blocks/gift-card-balance-checker/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
GiftCardTransactionRecordinterface GiftCardTransactionRecord {
id: string
date: string
reference: string
title: string
subtitle?: string
type: 'activation' | 'redemption' | 'reload'
amount: number
balanceAfter: number
status: 'Completed' | 'Pending'
}Files installed (2)
src/app/components/blocks/gift-card-balance-checker/gift-card-balance-checker.component.ts30.5 kB
import { Component, DestroyRef, Input, computed, inject, signal, ChangeDetectionStrategy } from '@angular/core' import { ArrowDownLeft, ArrowUpRight, Check, CheckCircle2, Copy, CreditCard, Gift, HelpCircle, Lock, LucideAngularModule, Plus, RefreshCw, ShieldCheck, ShoppingBag, Wallet, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiInputComponent } from '@/ui/input/input.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' import { UiTooltipComponent, UiTooltipContentComponent, UiTooltipProviderComponent, UiTooltipTriggerComponent, } from '@/ui/tooltip/tooltip.component' export interface GiftCardTransactionRecord { id: string date: string reference: string title: string subtitle?: string type: 'activation' | 'redemption' | 'reload' amount: number balanceAfter: number status: 'Completed' | 'Pending' } export type GiftCardFilterType = 'all' | 'redemptions' | 'loads' const initialTransactions: GiftCardTransactionRecord[] = [ { id: 'tx-1', date: 'Dec 19, 2024', reference: '#ORD-84112', title: 'Store Checkout Redemption', subtitle: 'Order #ORD-84112 · Leather Travel Wallet', type: 'redemption', amount: -15.0, balanceAfter: 150.0, status: 'Completed', }, { id: 'tx-2', date: 'Nov 04, 2024', reference: '#ORD-84920', title: 'Store Checkout Redemption', subtitle: 'Order #ORD-84920 · Merino Knit Sweater', type: 'redemption', amount: -35.0, balanceAfter: 165.0, status: 'Completed', }, { id: 'tx-3', date: 'Oct 12, 2024', reference: '#ACT-99014', title: 'Initial Activation & Fund Issue', subtitle: 'Digital Storefront Pass Issued', type: 'activation', amount: 200.0, balanceAfter: 200.0, status: 'Completed', }, ] /** Groups digits in fours: "7482 - 9104 - 6382 - 8492" (max 16 digits). */ export function formatGiftCardInput(raw: string): string { const digits = raw.replace(/\D/g, '').slice(0, 16) return digits.replace(/(.{4})(?=.)/g, '$1 - ') } /** * Angular port of the React GiftCardBalanceChecker block: card lookup form with a digital * gift-card hero, quick fund reloads, balance metrics, a filterable redemption ledger and an * in-store barcode pass. */ @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-gift-card-balance-checker, [ui-gift-card-balance-checker]', standalone: true, host: { '[attr.data-slot]': '"gift-card-balance-checker"', '[class]': 'hostClass', }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, UiInputComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, UiTooltipComponent, UiTooltipContentComponent, UiTooltipProviderComponent, UiTooltipTriggerComponent, ], template: ` <!-- Header Section --> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-1"> <div class="flex items-center gap-2"> <h2 class="text-foreground text-2xl font-bold tracking-tight">Check Gift Card Balance</h2> <span ui-badge variant="outline" class="bg-primary/5 text-primary border-primary/20 gap-1"> <lucide-icon [img]="CheckCircle2" [class]="'text-primary size-3'" /> Storefront Pass </span> </div> <p class="text-muted-foreground text-sm"> Check your remaining card balance, review past order redemptions, or reload funds. </p> </div> <div class="flex items-center gap-2"> <ui-tooltip-provider [delayDuration]="150"> <ui-tooltip> <div ui-tooltip-trigger class="border-border bg-card text-muted-foreground hover:text-foreground flex cursor-pointer items-center gap-1.5 rounded-md border px-3 py-1.5 text-xs shadow-xs" > <lucide-icon [img]="HelpCircle" [class]="'text-primary size-3.5'" /> <span>PIN & Card Guide</span> </div> <ui-tooltip-content side="bottom" class="max-w-xs text-xs"> Locate the 16-digit card code on the back of your physical card or inside your digital gift email. The 4-digit security PIN is under the scratch-off foil. </ui-tooltip-content> </ui-tooltip> </ui-tooltip-provider> </div> </div> <!-- Feedback Banner if active --> @if (feedbackMessage()) { <div class="border-success/20 bg-success/10 text-success flex items-center justify-between rounded-lg border px-4 py-2.5 text-sm" > <div class="flex items-center gap-2"> <lucide-icon [img]="CheckCircle2" [class]="'text-success size-4 shrink-0'" /> <span>{{ feedbackMessage() }}</span> </div> </div> } <!-- 2-Column Storefront Layout --> <div class="grid grid-cols-1 gap-6 lg:grid-cols-12"> <!-- Left Column: Lookup Form & Digital Card Hero --> <div class="space-y-6 lg:col-span-5"> <!-- Lookup Card Form --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-4"> <div class="flex items-center justify-between"> <h3 ui-card-title class="text-base font-semibold">Card Lookup</h3> @if (isVerified()) { <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success gap-1 text-xs"> <lucide-icon [img]="Check" [class]="'size-3'" /> Active Card </span> } </div> <p ui-card-description class="text-xs"> Enter your gift card credentials to view live balance and activity. </p> </div> <div ui-card-content class="space-y-4 pb-4"> <!-- 16-Digit Card Number Input --> <div class="space-y-1.5"> <div class="flex items-center justify-between"> <label for="gc-number-input-angular" class="text-foreground text-xs font-medium"> 16-Digit Card Number </label> <ui-tooltip-provider [delayDuration]="150"> <ui-tooltip> <button ui-tooltip-trigger type="button" class="text-muted-foreground hover:text-foreground inline-flex items-center focus-visible:outline-none" aria-label="Gift card number info" > <lucide-icon [img]="HelpCircle" [class]="'size-3.5'" /> </button> <ui-tooltip-content side="top" class="max-w-xs text-xs"> Enter the 16 digits on the back of your card or in your receipt email. </ui-tooltip-content> </ui-tooltip> </ui-tooltip-provider> </div> <ui-input id="gc-number-input-angular" [value]="cardNumber()" placeholder="XXXX - XXXX - XXXX - XXXX" [maxLength]="25" class="font-mono text-sm" [prefixIcon]="cardIcon" (valueChange)="onCardNumberChange($event)" /> <ng-template #cardIcon><lucide-icon [img]="CreditCard" [class]="'size-4'" /></ng-template> </div> <!-- 4-Digit Security PIN Input --> <div class="space-y-1.5"> <div class="flex items-center justify-between"> <label for="gc-pin-input-angular" class="text-foreground text-xs font-medium"> Security PIN (4 digits) </label> <ui-tooltip-provider [delayDuration]="150"> <ui-tooltip> <button ui-tooltip-trigger type="button" class="text-muted-foreground hover:text-foreground inline-flex items-center focus-visible:outline-none" aria-label="Security PIN info" > <lucide-icon [img]="HelpCircle" [class]="'size-3.5'" /> </button> <ui-tooltip-content side="top" class="max-w-xs text-xs"> The 4-digit PIN is revealed by scratching the silver foil or listed in your digital claim email. </ui-tooltip-content> </ui-tooltip> </ui-tooltip-provider> </div> <ui-input id="gc-pin-input-angular" [value]="securityPin()" type="password" placeholder="••••" [maxLength]="4" showPasswordToggle class="font-mono text-sm" [prefixIcon]="lockIcon" (valueChange)="onPinChange($event)" /> <ng-template #lockIcon><lucide-icon [img]="Lock" [class]="'size-4'" /></ng-template> </div> <button ui-button type="button" class="w-full font-medium" [disabled]="isChecking()" (click)="checkBalance()" > @if (isChecking()) { <lucide-icon [img]="RefreshCw" [class]="'mr-2 size-4 animate-spin'" /> Verifying Balance... } @else { <lucide-icon [img]="ShieldCheck" [class]="'mr-2 size-4'" /> Check Balance } </button> </div> </div> <!-- Stylized Digital Gift Card Hero Display --> <div class="relative overflow-hidden rounded-2xl border border-white/10 bg-gradient-to-br from-zinc-950 via-neutral-900 to-zinc-900 p-6 text-white shadow-lg" > <div class="relative flex flex-col justify-between space-y-6"> <!-- Card Top Bar: Brand & Pass Badge --> <div class="flex items-start justify-between"> <div class="flex items-center gap-2.5"> <div class="flex size-9 items-center justify-center rounded-xl border border-white/10 bg-white/10 text-white shadow-inner backdrop-blur-md" > <lucide-icon [img]="Gift" [class]="'text-success size-5'" /> </div> <div> <p class="text-xs font-bold tracking-wider text-neutral-200 uppercase">Lumen Store</p> <p class="text-xs text-neutral-400">Digital Gift Pass</p> </div> </div> <span ui-badge variant="outline" class="gap-1.5 border-white/20 bg-white/10 px-2.5 py-0.5 text-xs text-white backdrop-blur-md" > <span class="bg-success size-1.5 animate-pulse rounded-full"></span> Verified Active </span> </div> <!-- Card Center: Chip Icon & Balance Display --> <div class="space-y-1 pt-1"> <div class="flex items-center justify-between"> <span class="text-xs font-medium tracking-wider text-neutral-400 uppercase">Available Balance</span> <!-- Chip graphic representation --> <div class="flex items-center gap-1 opacity-70"> <div class="border-warning/20/40 h-5 w-7 rounded-sm border bg-gradient-to-tr from-amber-400/20 to-amber-200/40" ></div> <span class="font-mono text-xs tracking-tighter text-neutral-400">NFC</span> </div> </div> <div class="flex items-baseline gap-2"> <span class="text-3xl font-bold tracking-tight text-white tabular-nums sm:text-4xl" >\${{ currentBalance().toFixed(2) }}</span > <span class="text-success text-xs font-medium">USD</span> </div> </div> <!-- Card Footer: Card Number & Expiry terms --> <div class="space-y-2 border-t border-white/10 pt-4"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2"> <span class="font-mono text-sm tracking-widest text-neutral-200" >•••• •••• •••• {{ lastFourDigits() }}</span > <button type="button" class="rounded p-1 text-neutral-400 transition-colors hover:text-white focus-visible:ring-1 focus-visible:ring-white/40 focus-visible:outline-none" aria-label="Copy card number" (click)="copyCardNumber()" > @if (copied()) { <lucide-icon [img]="Check" [class]="'text-success size-3.5'" /> } @else { <lucide-icon [img]="Copy" [class]="'size-3.5'" /> } </button> </div> @if (copied()) { <span class="animate-in fade-in text-success text-xs font-medium">Copied!</span> } </div> <div class="flex items-center justify-between text-xs text-neutral-400"> <span>Never Expires · No Inactivity Fees</span> <span class="font-mono text-neutral-500">PIN: ••••</span> </div> </div> </div> </div> <!-- Quick Action Buttons --> <div class="space-y-3"> <button ui-button variant="default" class="w-full font-medium" (click)="applyToOrder()"> @if (applied()) { <lucide-icon [img]="Check" [class]="'text-success mr-2 size-4'" /> Applied to Checkout! } @else { <lucide-icon [img]="ShoppingBag" [class]="'mr-2 size-4'" /> Apply to Next Order } </button> <!-- Quick Reload Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="p-4 pb-2"> <div class="flex items-center justify-between"> <h3 ui-card-title class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Quick Reload Funds </h3> <lucide-icon [img]="Plus" [class]="'text-muted-foreground size-3.5'" /> </div> </div> <div ui-card-content class="p-4 pt-0"> <p class="text-muted-foreground mb-3 text-xs"> Top up your gift card instantly with a saved payment method. </p> <div class="grid grid-cols-3 gap-2"> <button ui-button variant="outline" size="sm" class="hover:bg-primary/5 hover:text-primary hover:border-primary/30 w-full font-medium tabular-nums" (click)="reloadFunds(25)" > + $25.00 </button> <button ui-button variant="outline" size="sm" class="hover:bg-primary/5 hover:text-primary hover:border-primary/30 w-full font-medium tabular-nums" (click)="reloadFunds(50)" > + $50.00 </button> <button ui-button variant="outline" size="sm" class="hover:bg-primary/5 hover:text-primary hover:border-primary/30 w-full font-medium tabular-nums" (click)="reloadFunds(100)" > + $100.00 </button> </div> </div> </div> </div> </div> <!-- Right Column: Balance Stats & Redemption History Table --> <div class="space-y-6 lg:col-span-7"> <!-- Passbook Summary Metrics --> <div class="grid grid-cols-1 gap-3 sm:grid-cols-3"> <div class="border-border bg-card rounded-xl border p-4 shadow-xs"> <div class="text-muted-foreground mb-1 flex items-center justify-between text-xs"> <span>Available Balance</span> <lucide-icon [img]="Wallet" [class]="'text-primary size-3.5'" /> </div> <p class="text-foreground text-2xl font-bold tracking-tight tabular-nums"> \${{ currentBalance().toFixed(2) }} </p> <p class="text-success mt-1 flex items-center gap-1 text-xs font-medium"> <lucide-icon [img]="Check" [class]="'size-3'" /> Ready to spend </p> </div> <div class="border-border bg-card rounded-xl border p-4 shadow-xs"> <div class="text-muted-foreground mb-1 flex items-center justify-between text-xs"> <span>Total Value Loaded</span> <lucide-icon [img]="ArrowDownLeft" [class]="'text-success size-3.5'" /> </div> <p class="text-foreground text-2xl font-bold tracking-tight tabular-nums"> \${{ totalLoaded().toFixed(2) }} </p> <p class="text-muted-foreground mt-1 text-xs">All top-ups & issue</p> </div> <div class="border-border bg-card rounded-xl border p-4 shadow-xs"> <div class="text-muted-foreground mb-1 flex items-center justify-between text-xs"> <span>Total Redeemed</span> <lucide-icon [img]="ArrowUpRight" [class]="'text-muted-foreground size-3.5'" /> </div> <p class="text-foreground text-2xl font-bold tracking-tight tabular-nums"> -\${{ totalSpent().toFixed(2) }} </p> <p class="text-muted-foreground mt-1 text-xs">Past store orders</p> </div> </div> <!-- Redemption & Activity History Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div> <h3 ui-card-title class="text-base font-semibold">Redemption & Activity History</h3> <p ui-card-description class="text-xs"> Detailed ledger of card issuance, promotional top-ups, and store checkouts. </p> </div> <!-- Filter Pills --> <div class="border-border bg-muted/40 flex items-center gap-1.5 rounded-lg border p-1 text-xs"> <button type="button" [class]="filterClass('all')" (click)="filterType.set('all')"> All ({{ transactions().length }}) </button> <button type="button" [class]="filterClass('redemptions')" (click)="filterType.set('redemptions')"> Redemptions </button> <button type="button" [class]="filterClass('loads')" (click)="filterType.set('loads')">Loads</button> </div> </div> </div> <div ui-card-content class="p-0"> <div class="overflow-x-auto"> <div ui-table> <thead ui-table-header> <tr ui-table-row> <th ui-table-head class="w-[110px]">Date</th> <th ui-table-head>Activity & Reference</th> <th ui-table-head class="w-[90px] text-center">Type</th> <th ui-table-head class="w-[100px] text-right">Amount</th> <th ui-table-head class="w-[110px] text-right">Resulting Balance</th> </tr> </thead> <tbody ui-table-body> @for (record of filteredTransactions(); track record.id) { <tr ui-table-row> <td ui-table-cell class="text-muted-foreground text-xs font-medium">{{ record.date }}</td> <td ui-table-cell> <div class="space-y-0.5"> <p class="text-foreground text-sm leading-none font-medium">{{ record.title }}</p> @if (record.subtitle) { <p class="text-muted-foreground text-xs">{{ record.subtitle }}</p> } </div> </td> <td ui-table-cell class="text-center"> @if (record.type === 'activation') { <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success px-2 py-0.5 text-xs font-normal" > Activation </span> } @if (record.type === 'reload') { <span ui-badge variant="outline" class="border-info/30 bg-info/10 text-info px-2 py-0.5 text-xs font-normal" > Reload </span> } @if (record.type === 'redemption') { <span ui-badge variant="secondary" class="px-2 py-0.5 text-xs font-normal">Redemption</span> } </td> <td ui-table-cell [class]="amountClass(record)">{{ amountLabel(record) }}</td> <td ui-table-cell class="text-muted-foreground text-right font-mono text-sm tabular-nums"> \${{ record.balanceAfter.toFixed(2) }} </td> </tr> } </tbody> </div> </div> </div> <div ui-card-footer class="border-border text-muted-foreground flex flex-col gap-3 border-t p-4 text-xs sm:flex-row sm:items-center sm:justify-between" > <div class="flex items-center gap-1.5"> <lucide-icon [img]="ShieldCheck" [class]="'text-primary size-4 shrink-0'" /> <span>Protected by 256-bit encryption · Zero liability policy</span> </div> <div class="flex items-center gap-3"> <button type="button" class="hover:text-foreground flex min-h-6 items-center gap-1 underline-offset-4 hover:underline" (click)="checkBalance()" > <lucide-icon [img]="RefreshCw" [class]="'size-3'" /> Refresh Ledger </button> </div> </div> </div> <!-- Digital Passbook Barcode Card --> <div ui-card class="border-border bg-muted/20 shadow-xs"> <div ui-card-content class="flex flex-col items-center justify-between gap-4 p-4 sm:flex-row sm:p-5"> <div class="space-y-1 text-center sm:text-left"> <h4 class="text-foreground flex items-center justify-center gap-2 text-sm font-semibold sm:justify-start"> <lucide-icon [img]="Wallet" [class]="'text-primary size-4'" /> In-Store Digital Pass </h4> <p class="text-muted-foreground max-w-sm text-xs"> Present this barcode at checkout in any physical store location to scan and redeem card funds. </p> </div> <!-- Barcode Simulation --> <div class="border-border bg-card flex flex-col items-center gap-1.5 rounded-lg border p-3 shadow-xs"> <div class="flex h-10 items-end gap-[3px] px-2" aria-hidden="true"> <div class="bg-foreground h-full w-[2px]"></div> <div class="bg-foreground h-full w-[4px]"></div> <div class="bg-foreground h-full w-[1px]"></div> <div class="bg-foreground h-full w-[3px]"></div> <div class="bg-foreground h-full w-[2px]"></div> <div class="bg-foreground h-full w-[5px]"></div> <div class="bg-foreground h-full w-[1px]"></div> <div class="bg-foreground h-full w-[3px]"></div> <div class="bg-foreground h-full w-[2px]"></div> <div class="bg-foreground h-full w-[4px]"></div> <div class="bg-foreground h-full w-[2px]"></div> <div class="bg-foreground h-full w-[1px]"></div> <div class="bg-foreground h-full w-[4px]"></div> <div class="bg-foreground h-full w-[2px]"></div> <div class="bg-foreground h-full w-[3px]"></div> </div> <span class="text-muted-foreground font-mono text-xs tracking-widest">{{ cardNumber() }}</span> </div> </div> </div> </div> </div> `, }) export class UiGiftCardBalanceCheckerComponent { @Input('class') className?: string private readonly destroyRef = inject(DestroyRef) private readonly timers = new Set<ReturnType<typeof setTimeout>>() protected readonly ArrowDownLeft = ArrowDownLeft protected readonly ArrowUpRight = ArrowUpRight protected readonly Check = Check protected readonly CheckCircle2 = CheckCircle2 protected readonly Copy = Copy protected readonly CreditCard = CreditCard protected readonly Gift = Gift protected readonly HelpCircle = HelpCircle protected readonly Lock = Lock protected readonly Plus = Plus protected readonly RefreshCw = RefreshCw protected readonly ShieldCheck = ShieldCheck protected readonly ShoppingBag = ShoppingBag protected readonly Wallet = Wallet readonly cardNumber = signal('7482 - 9104 - 6382 - 8492') readonly securityPin = signal('4829') readonly isChecking = signal(false) readonly isVerified = signal(true) readonly copied = signal(false) readonly applied = signal(false) readonly feedbackMessage = signal<string | null>(null) readonly filterType = signal<GiftCardFilterType>('all') readonly transactions = signal<GiftCardTransactionRecord[]>(initialTransactions) readonly currentBalance = computed(() => { const list = this.transactions() return list.length === 0 ? 0 : list[0]!.balanceAfter }) readonly totalLoaded = computed(() => this.transactions() .filter((t) => t.amount > 0) .reduce((sum, t) => sum + t.amount, 0), ) readonly totalSpent = computed(() => Math.abs( this.transactions() .filter((t) => t.amount < 0) .reduce((sum, t) => sum + t.amount, 0), ), ) readonly lastFourDigits = computed(() => { const digits = this.cardNumber().replace(/\D/g, '') return digits.length >= 4 ? digits.slice(-4) : '8492' }) readonly filteredTransactions = computed(() => { const filter = this.filterType() const list = this.transactions() if (filter === 'redemptions') return list.filter((t) => t.type === 'redemption') if (filter === 'loads') return list.filter((t) => t.type === 'activation' || t.type === 'reload') return list }) constructor() { this.destroyRef.onDestroy(() => { for (const t of this.timers) clearTimeout(t) this.timers.clear() }) } get hostClass(): string { return cn('block w-full space-y-6', this.className) } protected filterClass(type: GiftCardFilterType): string { return cn( 'rounded px-2.5 py-1 font-medium transition-colors', this.filterType() === type ? 'bg-background text-foreground shadow-xs' : 'text-muted-foreground hover:text-foreground', ) } protected amountClass(record: GiftCardTransactionRecord): string { return cn('text-right text-sm font-medium tabular-nums', record.amount > 0 ? 'text-success' : 'text-foreground') } protected amountLabel(record: GiftCardTransactionRecord): string { return record.amount > 0 ? `+$${record.amount.toFixed(2)}` : `-$${Math.abs(record.amount).toFixed(2)}` } private later(fn: () => void, ms: number): void { const t = setTimeout(() => { this.timers.delete(t) fn() }, ms) this.timers.add(t) } onCardNumberChange(value: string): void { this.cardNumber.set(formatGiftCardInput(value)) } onPinChange(value: string): void { this.securityPin.set(value.replace(/\D/g, '').slice(0, 4)) } checkBalance(): void { this.isChecking.set(true) this.feedbackMessage.set(null) this.later(() => { this.isChecking.set(false) this.isVerified.set(true) this.feedbackMessage.set(`Gift card verified. Current active balance is $${this.currentBalance().toFixed(2)}.`) this.later(() => this.feedbackMessage.set(null), 4000) }, 500) } copyCardNumber(): void { const raw = this.cardNumber().replace(/\s+/g, '') if (typeof navigator !== 'undefined' && navigator.clipboard) { navigator.clipboard.writeText(raw).catch(() => {}) } this.copied.set(true) this.later(() => this.copied.set(false), 2000) } applyToOrder(): void { this.copyCardNumber() this.applied.set(true) this.feedbackMessage.set(`Card ending in ${this.lastFourDigits()} copied and applied to your checkout session!`) this.later(() => { this.applied.set(false) this.feedbackMessage.set(null) }, 4000) } reloadFunds(amount: number): void { const newBalance = this.currentBalance() + amount const newRecord: GiftCardTransactionRecord = { id: `tx-${Date.now()}`, date: 'Today', reference: `#RLD-${Math.floor(10000 + Math.random() * 90000)}`, title: 'Manual Online Fund Reload', subtitle: `Instant Online Top-Up (+ $${amount.toFixed(2)})`, type: 'reload', amount, balanceAfter: newBalance, status: 'Completed', } this.transactions.update((prev) => [newRecord, ...prev]) this.feedbackMessage.set( `Successfully reloaded +$${amount.toFixed(2)}! New card balance: $${newBalance.toFixed(2)}.`, ) this.later(() => this.feedbackMessage.set(null), 4000) } }src/app/components/blocks/gift-card-balance-checker/index.ts0.2 kB
export { UiGiftCardBalanceCheckerComponent, formatGiftCardInput, type GiftCardFilterType, type GiftCardTransactionRecord, } from './gift-card-balance-checker.component'
Raw manifest:https://uipkge.dev/r/angular/gift-card-balance-checker.json