{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "loyalty-rewards-tier-hub",
  "title": "Loyalty Rewards Tier Hub",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/loyalty-rewards-tier-hub/loyalty-rewards-tier-hub.component.ts",
      "content": "import { Component, Input, signal, ChangeDetectionStrategy } from '@angular/core'\nimport {\n  Award,\n  Check,\n  Crown,\n  Gift,\n  Headphones,\n  History,\n  Lock,\n  LucideAngularModule,\n  ShoppingBag,\n  Tag,\n  Ticket,\n  Truck,\n  Zap,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardFooterComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiProgressComponent } from '@/ui/progress/progress.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport {\n  UiTableBodyComponent,\n  UiTableCellComponent,\n  UiTableComponent,\n  UiTableHeadComponent,\n  UiTableHeaderComponent,\n  UiTableRowComponent,\n} from '@/ui/table/table.component'\n\nexport interface LoyaltyPerk {\n  id: string\n  title: string\n  subtitle: string\n  description: string\n  status: 'active' | 'locked'\n  tierBadge: string\n  unlockPoints?: number\n}\n\nexport interface LoyaltyReward {\n  id: string\n  title: string\n  category: string\n  pointsCost: number\n  description: string\n  unlocked: boolean\n  pointsNeeded?: number\n  code?: string\n}\n\nexport interface LoyaltyLedgerEntry {\n  id: string\n  title: string\n  subtitle: string\n  type: string\n  date: string\n  pointsChange: number\n  isPositive: boolean\n}\n\nconst perks: LoyaltyPerk[] = [\n  {\n    id: 'perk-shipping',\n    title: 'Free Express Shipping on All Orders',\n    subtitle: 'All Orders',\n    description: 'Complimentary 2-day express shipping on every online order with zero minimum spend.',\n    status: 'active',\n    tierBadge: 'Active',\n  },\n  {\n    id: 'perk-archive',\n    title: 'Early Access to Private Archive Sales',\n    subtitle: '48h Early Access',\n    description: 'Shop seasonal private archives and limited capsule releases 48 hours before public launch.',\n    status: 'active',\n    tierBadge: 'Active',\n  },\n  {\n    id: 'perk-stylist',\n    title: 'Dedicated VIP Concierge & Personal Stylist',\n    subtitle: '1-on-1 Stylist Line',\n    description: 'Direct priority chat with our senior stylists for bespoke fashion curation and concierge care.',\n    status: 'active',\n    tierBadge: 'Active',\n  },\n  {\n    id: 'perk-annual-gift',\n    title: 'Diamond Exclusive Annual Gift',\n    subtitle: 'Bespoke Curated Box',\n    description: 'Curated luxury anniversary gift box ($120+ retail value) delivered automatically each winter.',\n    status: 'locked',\n    tierBadge: 'Unlocks at 3,000 pts',\n    unlockPoints: 3000,\n  },\n]\n\nconst rewards: LoyaltyReward[] = [\n  {\n    id: 'reward-15-off',\n    title: '$15 Off Any Order',\n    category: 'Store Credit Voucher',\n    pointsCost: 1500,\n    description: 'Instant digital coupon applied at checkout on any order above $50.',\n    unlocked: true,\n    code: 'VIP-SAVE15',\n  },\n  {\n    id: 'reward-leather-pouch',\n    title: 'Free Travel Leather Pouch',\n    category: 'Exclusive Merchandise',\n    pointsCost: 2000,\n    description: 'Handcrafted Italian pebbled leather travel accessory pouch in signature Midnight Noir.',\n    unlocked: true,\n    code: 'CLAIM-POUCH24',\n  },\n  {\n    id: 'reward-audio-30',\n    title: '$30 Off Premium Audio',\n    category: 'Partner Exclusive',\n    pointsCost: 3000,\n    description: 'Exclusive member markdown on noise-cancelling headphones and studio sound gear.',\n    unlocked: false,\n    pointsNeeded: 550,\n  },\n]\n\nconst ledgerTransactions: LoyaltyLedgerEntry[] = [\n  {\n    id: 'tx-1',\n    title: 'Order #ORD-9428 - Studio Silk Blouse',\n    subtitle: 'Online Purchase · $190.00 subtotal',\n    type: 'Earned · 2x Platinum Multiplier',\n    date: 'Oct 18, 2024',\n    pointsChange: 380,\n    isPositive: true,\n  },\n  {\n    id: 'tx-2',\n    title: 'Redeemed: $10 Voucher Code #VIP-10OFF',\n    subtitle: 'Rewards Catalog · Checkout Redemption',\n    type: 'Reward Redemption',\n    date: 'Oct 10, 2024',\n    pointsChange: -1000,\n    isPositive: false,\n  },\n  {\n    id: 'tx-3',\n    title: 'Order #ORD-9104 - Cashmere Wrap',\n    subtitle: 'Online Purchase · $320.00 subtotal',\n    type: 'Earned · 2x Platinum Multiplier',\n    date: 'Sep 29, 2024',\n    pointsChange: 640,\n    isPositive: true,\n  },\n  {\n    id: 'tx-4',\n    title: 'Tier Upgrade Bonus - Platinum Milestone',\n    subtitle: 'Milestone Threshold · 2,000 pts Achieved',\n    type: 'Milestone Tier Reward',\n    date: 'Sep 15, 2024',\n    pointsChange: 500,\n    isPositive: true,\n  },\n]\n\n/**\n * VIP loyalty tier dashboard: customer hero card with points balance, multiplier and\n * next-tier progress, the four tier privilege perks, a three-item rewards catalog with\n * redeemable vouchers, and the points history ledger. Angular port of the React\n * `LoyaltyRewardsTierHub` block (same markup, classes, copy and sample data).\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-loyalty-rewards-tier-hub, [ui-loyalty-rewards-tier-hub]',\n  standalone: true,\n  imports: [\n    LucideAngularModule,\n    UiAvatarComponent,\n    UiAvatarFallbackComponent,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardFooterComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiProgressComponent,\n    UiSeparatorComponent,\n    UiTableBodyComponent,\n    UiTableCellComponent,\n    UiTableComponent,\n    UiTableHeadComponent,\n    UiTableHeaderComponent,\n    UiTableRowComponent,\n  ],\n  host: {\n    '[attr.data-slot]': '\"loyalty-rewards-tier-hub\"',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <!-- Header Section -->\n    <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n      <div>\n        <div class=\"flex items-center gap-2\">\n          <span ui-badge wrap variant=\"secondary\" class=\"gap-1.5 font-medium\">\n            <lucide-icon [img]=\"Award\" class=\"text-primary size-3.5\" aria-hidden=\"true\" />\n            VIP Rewards Club\n          </span>\n        </div>\n        <h1 class=\"text-foreground mt-2 text-2xl font-bold tracking-tight sm:text-3xl\">Loyalty &amp; Rewards Hub</h1>\n        <p class=\"text-muted-foreground mt-1 max-w-2xl text-sm sm:text-base\">\n          Track VIP tier progress, redeem points for luxury perks, and review your member transaction ledger.\n        </p>\n      </div>\n    </div>\n\n    <!-- Customer Loyalty Hero Card -->\n    <div ui-card class=\"border-border/80 shadow-xs\">\n      <div ui-card-content class=\"space-y-6 p-6\">\n        <!-- Profile Header & Tier Status -->\n        <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n          <div class=\"flex items-center gap-4\">\n            <span ui-avatar class=\"border-primary/20 size-14 border-2 shadow-xs\">\n              <span ui-avatar-fallback class=\"bg-primary/10 text-primary text-base font-semibold\">ER</span>\n            </span>\n            <div>\n              <h2 class=\"text-foreground text-lg font-bold tracking-tight sm:text-xl\">{{ customerName }}</h2>\n              <p class=\"text-muted-foreground text-xs font-medium sm:text-sm\">{{ tierName }} · Member since 2022</p>\n            </div>\n          </div>\n\n          <span\n            ui-badge\n            wrap\n            variant=\"outline\"\n            class=\"border-primary/20 bg-primary/10 text-primary gap-1.5 px-3 py-1.5 text-xs font-semibold\"\n          >\n            <lucide-icon [img]=\"Crown\" class=\"text-primary size-3.5 shrink-0\" aria-hidden=\"true\" />\n            <span>Platinum Tier · 2x Points Multiplier</span>\n          </span>\n        </div>\n\n        <div ui-separator></div>\n\n        <!-- Points Balance & Multiplier Display -->\n        <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n          <!-- Balance Stat -->\n          <div class=\"border-border/60 bg-muted/30 flex flex-col justify-between rounded-xl border p-4\">\n            <div>\n              <p class=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n                Available Points Balance\n              </p>\n              <div class=\"mt-1 flex items-baseline gap-2\">\n                <span class=\"text-foreground text-3xl font-bold tracking-tight tabular-nums sm:text-4xl\">2,450</span>\n                <span class=\"text-muted-foreground text-sm font-medium\">Points</span>\n              </div>\n            </div>\n            <p class=\"text-muted-foreground mt-3 text-xs font-medium\">\n              Estimated reward cash value:\n              <span class=\"text-success text-success font-semibold tabular-nums\">$24.50 reward value</span>\n            </p>\n          </div>\n\n          <!-- Tier Multiplier Stat -->\n          <div class=\"border-border/60 bg-muted/30 flex flex-col justify-between rounded-xl border p-4\">\n            <div>\n              <p class=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">Current Multiplier</p>\n              <div class=\"mt-1 flex items-center gap-2\">\n                <span class=\"text-foreground text-3xl font-bold tracking-tight tabular-nums sm:text-4xl\">2x</span>\n                <span ui-badge wrap variant=\"success\" class=\"gap-1 text-xs\">\n                  <lucide-icon [img]=\"Zap\" class=\"size-3\" aria-hidden=\"true\" />\n                  Active\n                </span>\n              </div>\n            </div>\n            <p class=\"text-muted-foreground mt-3 text-xs font-medium\">\n              Earn 2 points per $1 spent on all boutique catalog orders.\n            </p>\n          </div>\n\n          <!-- Next Reward Threshold -->\n          <div\n            class=\"border-border/60 bg-muted/30 flex flex-col justify-between rounded-xl border p-4 sm:col-span-2 lg:col-span-1\"\n          >\n            <div>\n              <p class=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">Next Tier Goal</p>\n              <div class=\"mt-1 flex items-baseline gap-2\">\n                <span class=\"text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl\">550 pts</span>\n                <span class=\"text-muted-foreground text-xs font-medium\">to Diamond VIP</span>\n              </div>\n            </div>\n            <p class=\"text-muted-foreground mt-3 text-xs font-medium\">\n              Unlocks 3x points multiplier and annual exclusive gift.\n            </p>\n          </div>\n        </div>\n\n        <!-- Next Tier Progress Bar Section -->\n        <div class=\"border-border/80 bg-muted/40 space-y-2.5 rounded-xl border p-4\">\n          <div class=\"flex flex-wrap items-center justify-between gap-2 text-xs sm:text-sm\">\n            <div class=\"flex items-center gap-2\">\n              <lucide-icon [img]=\"Award\" class=\"text-primary size-4 shrink-0\" aria-hidden=\"true\" />\n              <span class=\"text-foreground font-semibold\">Next Tier Progress: Diamond VIP</span>\n            </div>\n            <span class=\"text-foreground font-semibold tabular-nums\">2,450 / 3,000 Points</span>\n          </div>\n\n          <div ui-progress [value]=\"81.6\" class=\"h-2.5\"></div>\n\n          <div class=\"flex flex-wrap items-center justify-between gap-2 text-xs\">\n            <span class=\"text-muted-foreground font-medium tabular-nums\">\n              81.6% to Diamond VIP · 550 points to upgrade\n            </span>\n            <span class=\"text-muted-foreground\">Target qualification date: Dec 31, 2024</span>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <!-- 4 VIP Tier Privilege Perks Summary -->\n    <div class=\"space-y-3\">\n      <div class=\"flex items-center justify-between gap-2\">\n        <div>\n          <h3 class=\"text-foreground text-base font-semibold\">VIP Tier Privilege Perks</h3>\n          <p class=\"text-muted-foreground text-xs\">\n            Active benefits unlocked with your Platinum status and next-tier Diamond previews.\n          </p>\n        </div>\n        <span ui-badge wrap variant=\"outline\" class=\"shrink-0 text-xs font-medium tabular-nums\">\n          3 of 4 Perks Active\n        </span>\n      </div>\n\n      <div class=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n        @for (perk of perks; track perk.id) {\n          <div\n            [class]=\"\n              cn(\n                'flex flex-col justify-between rounded-xl border p-4 transition-colors',\n                perk.status === 'active'\n                  ? 'border-border/80 bg-card shadow-xs'\n                  : 'border-border/60 bg-muted/20 opacity-85'\n              )\n            \"\n          >\n            <div class=\"space-y-3\">\n              <div class=\"flex items-center justify-between gap-2\">\n                <div [class]=\"perkIconClass(perk)\">\n                  @if (perk.id === 'perk-shipping') {\n                    <lucide-icon [img]=\"Truck\" class=\"size-4\" aria-hidden=\"true\" />\n                  }\n                  @if (perk.id === 'perk-archive') {\n                    <lucide-icon [img]=\"Tag\" class=\"size-4\" aria-hidden=\"true\" />\n                  }\n                  @if (perk.id === 'perk-stylist') {\n                    <lucide-icon [img]=\"Headphones\" class=\"size-4\" aria-hidden=\"true\" />\n                  }\n                  @if (perk.id === 'perk-annual-gift') {\n                    <lucide-icon [img]=\"Gift\" class=\"size-4\" aria-hidden=\"true\" />\n                  }\n                </div>\n\n                @if (perk.status === 'active') {\n                  <span ui-badge wrap variant=\"success\" class=\"gap-1 text-xs\">\n                    <lucide-icon [img]=\"Check\" class=\"size-3\" aria-hidden=\"true\" />\n                    Active\n                  </span>\n                } @else {\n                  <span ui-badge wrap variant=\"secondary\" class=\"text-muted-foreground gap-1 text-xs\">\n                    <lucide-icon [img]=\"Lock\" class=\"size-3\" aria-hidden=\"true\" />\n                    Unlocks at 3,000 pts\n                  </span>\n                }\n              </div>\n\n              <div>\n                <p class=\"text-foreground text-sm font-semibold\">{{ perk.title }}</p>\n                <p class=\"text-muted-foreground mt-0.5 text-xs font-medium\">{{ perk.subtitle }}</p>\n                <p class=\"text-muted-foreground mt-2 text-xs leading-relaxed\">{{ perk.description }}</p>\n              </div>\n            </div>\n\n            <div class=\"border-border/50 mt-4 border-t pt-3\">\n              <span class=\"text-muted-foreground text-xs\">\n                {{ perk.status === 'active' ? 'Included in Platinum VIP' : 'Requires Diamond Tier' }}\n              </span>\n            </div>\n          </div>\n        }\n      </div>\n    </div>\n\n    <!-- Rewards Catalog Grid (3 unlockable rewards) -->\n    <div class=\"space-y-3\">\n      <div class=\"flex items-center justify-between gap-2\">\n        <div>\n          <h3 class=\"text-foreground text-base font-semibold\">Rewards Catalog</h3>\n          <p class=\"text-muted-foreground text-xs\">\n            Redeem your points balance for instant discount vouchers and complimentary luxury items.\n          </p>\n        </div>\n        <span ui-badge wrap variant=\"outline\" class=\"shrink-0 text-xs font-medium tabular-nums\">\n          2 Available to Redeem\n        </span>\n      </div>\n\n      <div class=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n        @for (reward of rewards; track reward.id) {\n          <div\n            ui-card\n            [class]=\"\n              cn(\n                'border-border/80 flex flex-col justify-between shadow-xs transition-colors',\n                !reward.unlocked && 'bg-muted/20 opacity-90'\n              )\n            \"\n          >\n            <div ui-card-header class=\"pb-3\">\n              <div class=\"flex items-center justify-between gap-2\">\n                <span ui-badge wrap variant=\"secondary\" class=\"min-w-0 shrink text-xs font-medium\">\n                  {{ reward.category }}\n                </span>\n                <div class=\"shrink-0 text-right\">\n                  <span class=\"text-foreground text-sm font-bold tabular-nums\">\n                    {{ formatPoints(reward.pointsCost) }} pts\n                  </span>\n                </div>\n              </div>\n              <h3 ui-card-title class=\"mt-2 text-base font-semibold\">{{ reward.title }}</h3>\n              <p ui-card-description class=\"text-xs leading-relaxed\">{{ reward.description }}</p>\n            </div>\n\n            <div ui-card-content class=\"pb-3\">\n              <!-- When locked, show points shortfall -->\n              @if (!reward.unlocked) {\n                <div class=\"space-y-1.5\">\n                  <div class=\"flex items-center justify-between text-xs\">\n                    <span class=\"text-muted-foreground\">Progress to unlock</span>\n                    <span class=\"text-foreground font-semibold tabular-nums\">2,450 / 3,000 pts</span>\n                  </div>\n                  <div ui-progress [value]=\"81.6\" class=\"h-1.5\"></div>\n                </div>\n              } @else if (isRedeemed(reward.id)) {\n                <div\n                  class=\"border-success/30 bg-success/10 text-success rounded-md border p-2.5 text-center text-xs font-medium\"\n                >\n                  <div class=\"flex items-center justify-center gap-1\">\n                    <lucide-icon [img]=\"Check\" class=\"size-3.5\" aria-hidden=\"true\" />\n                    <span>Redeemed! Code:</span>\n                    <span class=\"font-mono font-bold\">{{ reward.code }}</span>\n                  </div>\n                </div>\n              }\n            </div>\n\n            <div ui-card-footer class=\"pt-0\">\n              @if (reward.unlocked && !isRedeemed(reward.id)) {\n                <button ui-button type=\"button\" class=\"w-full gap-1.5 text-xs font-medium\" (click)=\"redeem(reward.id)\">\n                  <lucide-icon [img]=\"Tag\" class=\"size-3.5\" aria-hidden=\"true\" />\n                  <span>Redeem Reward</span>\n                </button>\n              }\n\n              @if (reward.unlocked && isRedeemed(reward.id)) {\n                <button\n                  ui-button\n                  type=\"button\"\n                  variant=\"outline\"\n                  class=\"border-success/40 text-success w-full gap-1.5 text-xs font-medium\"\n                  disabled\n                >\n                  <lucide-icon [img]=\"Check\" class=\"size-3.5\" aria-hidden=\"true\" />\n                  <span>Voucher Claimed</span>\n                </button>\n              }\n\n              @if (!reward.unlocked) {\n                <button ui-button type=\"button\" variant=\"outline\" disabled class=\"w-full gap-1.5 text-xs font-medium\">\n                  <lucide-icon [img]=\"Lock\" class=\"size-3.5\" aria-hidden=\"true\" />\n                  <span>Locked · Need {{ reward.pointsNeeded }} pts</span>\n                </button>\n              }\n            </div>\n          </div>\n        }\n      </div>\n    </div>\n\n    <!-- Points History Ledger -->\n    <div ui-card class=\"border-border/80 shadow-xs\">\n      <div ui-card-header class=\"pb-4\">\n        <div class=\"flex items-center justify-between gap-2\">\n          <div>\n            <div class=\"flex items-center gap-2\">\n              <lucide-icon [img]=\"History\" class=\"text-muted-foreground size-4\" aria-hidden=\"true\" />\n              <h3 ui-card-title class=\"text-base font-semibold\">Points History Ledger</h3>\n            </div>\n            <p ui-card-description>Recent earnings and redemption transactions on your VIP account.</p>\n          </div>\n          <span ui-badge wrap variant=\"outline\" class=\"shrink-0 text-xs font-medium tabular-nums\">\n            Recent 4 Transactions\n          </span>\n        </div>\n      </div>\n\n      <div ui-card-content class=\"p-0 sm:p-6 sm:pt-0\">\n        <div class=\"overflow-x-auto\">\n          <div ui-table density=\"cozy\">\n            <thead ui-table-header>\n              <tr ui-table-row>\n                <th ui-table-head>Transaction Details</th>\n                <th ui-table-head>Type &amp; Multiplier</th>\n                <th ui-table-head>Date</th>\n                <th ui-table-head class=\"text-right\">Points Change</th>\n              </tr>\n            </thead>\n            <tbody ui-table-body>\n              @for (tx of ledgerTransactions; track tx.id) {\n                <tr ui-table-row>\n                  <td ui-table-cell>\n                    <div class=\"flex items-center gap-3\">\n                      <div\n                        [class]=\"\n                          cn(\n                            'flex size-8 shrink-0 items-center justify-center rounded-lg border',\n                            tx.isPositive\n                              ? 'border-success/20 bg-success/10 text-success'\n                              : 'border-border/60 bg-muted text-muted-foreground'\n                          )\n                        \"\n                      >\n                        @if (tx.isPositive && tx.pointsChange < 500) {\n                          <lucide-icon [img]=\"ShoppingBag\" class=\"size-4\" aria-hidden=\"true\" />\n                        }\n                        @if (tx.isPositive && tx.pointsChange >= 500) {\n                          <lucide-icon [img]=\"Crown\" class=\"size-4\" aria-hidden=\"true\" />\n                        }\n                        @if (!tx.isPositive) {\n                          <lucide-icon [img]=\"Ticket\" class=\"size-4\" aria-hidden=\"true\" />\n                        }\n                      </div>\n                      <div class=\"min-w-0\">\n                        <p class=\"text-foreground truncate text-sm font-medium\">{{ tx.title }}</p>\n                        <p class=\"text-muted-foreground truncate text-xs\">{{ tx.subtitle }}</p>\n                      </div>\n                    </div>\n                  </td>\n                  <td ui-table-cell>\n                    @if (tx.type.includes('Multiplier')) {\n                      <span ui-badge wrap variant=\"outline\" class=\"gap-1 text-xs font-medium\">\n                        <lucide-icon [img]=\"Zap\" class=\"text-warning size-3\" aria-hidden=\"true\" />\n                        {{ tx.type }}\n                      </span>\n                    } @else if (tx.type.includes('Milestone')) {\n                      <span\n                        ui-badge\n                        wrap\n                        variant=\"outline\"\n                        class=\"border-primary/20 bg-primary/10 text-primary gap-1 text-xs font-medium\"\n                      >\n                        <lucide-icon [img]=\"Crown\" class=\"text-primary size-3\" aria-hidden=\"true\" />\n                        {{ tx.type }}\n                      </span>\n                    } @else {\n                      <span ui-badge wrap variant=\"secondary\" class=\"text-muted-foreground gap-1 text-xs font-medium\">\n                        <lucide-icon [img]=\"Ticket\" class=\"size-3\" aria-hidden=\"true\" />\n                        {{ tx.type }}\n                      </span>\n                    }\n                  </td>\n                  <td ui-table-cell class=\"text-muted-foreground text-xs whitespace-nowrap sm:text-sm\">\n                    {{ tx.date }}\n                  </td>\n                  <td ui-table-cell class=\"text-right text-xs font-medium tabular-nums sm:text-sm\">\n                    <span [class]=\"tx.isPositive ? 'text-success font-bold' : 'text-muted-foreground font-semibold'\">\n                      {{\n                        tx.isPositive\n                          ? '+' + formatPoints(tx.pointsChange) + ' pts'\n                          : formatPoints(tx.pointsChange) + ' pts'\n                      }}\n                    </span>\n                  </td>\n                </tr>\n              }\n            </tbody>\n          </div>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiLoyaltyRewardsTierHubComponent {\n  @Input() customerName = 'Elena Rostova'\n  @Input() tierName = 'VIP Platinum Member'\n  @Input() currentPoints = 2450\n  @Input() targetPoints = 3000\n  @Input() pointValueDollars = 24.5\n  @Input() multiplier = '2x Points Multiplier'\n  @Input('class') className?: string\n\n  protected readonly cn = cn\n  protected readonly perks = perks\n  protected readonly rewards = rewards\n  protected readonly ledgerTransactions = ledgerTransactions\n\n  protected readonly Award = Award\n  protected readonly Check = Check\n  protected readonly Crown = Crown\n  protected readonly Gift = Gift\n  protected readonly Headphones = Headphones\n  protected readonly History = History\n  protected readonly Lock = Lock\n  protected readonly ShoppingBag = ShoppingBag\n  protected readonly Tag = Tag\n  protected readonly Ticket = Ticket\n  protected readonly Truck = Truck\n  protected readonly Zap = Zap\n\n  /** Reward ids the member has redeemed in this session. */\n  readonly redeemedMap = signal<Record<string, boolean>>({})\n\n  get hostClass(): string {\n    return cn('mx-auto block w-full max-w-5xl space-y-6', this.className)\n  }\n\n  isRedeemed(rewardId: string): boolean {\n    return this.redeemedMap()[rewardId] === true\n  }\n\n  redeem(rewardId: string): void {\n    this.redeemedMap.update((prev) => ({ ...prev, [rewardId]: true }))\n  }\n\n  protected formatPoints(value: number): string {\n    return value.toLocaleString()\n  }\n\n  protected perkIconClass(perk: LoyaltyPerk): string {\n    return cn(\n      'flex size-9 shrink-0 items-center justify-center rounded-lg border',\n      perk.id === 'perk-shipping' && 'border-success/20 bg-success/10 text-success',\n      perk.id === 'perk-archive' && 'border-chart-1/30 bg-chart-1/10 text-chart-1',\n      perk.id === 'perk-stylist' && 'border-info/20 bg-info/10 text-info',\n      perk.id === 'perk-annual-gift' && 'border-border/60 bg-muted text-muted-foreground',\n    )\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/loyalty-rewards-tier-hub/loyalty-rewards-tier-hub.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/loyalty-rewards-tier-hub/index.ts",
      "content": "export {\n  UiLoyaltyRewardsTierHubComponent,\n  type LoyaltyLedgerEntry,\n  type LoyaltyPerk,\n  type LoyaltyReward,\n} from './loyalty-rewards-tier-hub.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/loyalty-rewards-tier-hub/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/avatar.json",
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/progress.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/table.json"
  ],
  "description": "Sephora and Starbucks style VIP customer loyalty tier dashboard, points wallet, and rewards catalog. Features a customer hero card with points balance, reward valuation, and tier progress tracker, 4 VIP privilege perks with unlock statuses, a 3-item redeemable rewards catalog grid, and a recent points earnings & redemption history ledger.",
  "categories": [
    "commerce",
    "ecommerce",
    "dashboard",
    "marketing"
  ]
}