{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "privacy-data-export-portal",
  "title": "Privacy Data Export Portal",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/privacy-data-export-portal/privacy-data-export-portal.component.ts",
      "content": "// GDPR Article 15 / CCPA personal data export portal: governance stat cards, granular\n// category selection, format chooser, encrypted archive generation, and archive history.\n// Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  Activity,\n  Archive,\n  Calendar,\n  Check,\n  CheckCircle2,\n  Clock,\n  CreditCard,\n  Download,\n  FileSpreadsheet,\n  FileText,\n  FolderArchive,\n  HardDrive,\n  Info,\n  Loader2,\n  Lock,\n  LucideAngularModule,\n  RefreshCw,\n  ShieldCheck,\n  User,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiCheckboxComponent } from '@/ui/checkbox/checkbox.component'\nimport {\n  UiSelectComponent,\n  UiSelectContentComponent,\n  UiSelectItemComponent,\n  UiSelectTriggerComponent,\n  UiSelectValueComponent,\n} from '@/ui/select/select.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 type ExportFormat = 'zip' | 'json' | 'csv'\nexport type ArchiveStatus = 'ready' | 'processing' | 'expired'\n\nexport interface DataCategory {\n  id: string\n  name: string\n  description: string\n  sizeMb: number\n  recordCount: string\n  icon: unknown\n}\n\nexport interface ArchiveRecord {\n  id: string\n  name: string\n  format: ExportFormat\n  requestedAt: string\n  expiresAt: string\n  expiresInDays: number\n  sizeMb: number\n  categories: string[]\n  status: ArchiveStatus\n}\n\nconst CATEGORIES: DataCategory[] = [\n  {\n    id: 'profile',\n    name: 'Profile & Identity',\n    description: 'Account details, email history, avatar, and authentication security credentials.',\n    sizeMb: 1.4,\n    recordCount: '12 records',\n    icon: User,\n  },\n  {\n    id: 'activity',\n    name: 'Activity & Audit Logs',\n    description: 'Login history, device sessions, security events, and API access traces.',\n    sizeMb: 8.6,\n    recordCount: '1,420 events',\n    icon: Activity,\n  },\n  {\n    id: 'financial',\n    name: 'Financial & Transactions',\n    description: 'Invoices, payment receipts, subscription history, and billing ledger entries.',\n    sizeMb: 3.2,\n    recordCount: '48 entries',\n    icon: CreditCard,\n  },\n  {\n    id: 'workspace',\n    name: 'Workspace Content & Projects',\n    description: 'Created blocks, saved templates, comments, custom presets, and file uploads.',\n    sizeMb: 35.0,\n    recordCount: '312 objects',\n    icon: FolderArchive,\n  },\n]\n\nconst INITIAL_ARCHIVES: ArchiveRecord[] = [\n  {\n    id: 'DPA-2026-0814',\n    name: 'DPA-2026-0814.zip',\n    format: 'zip',\n    requestedAt: 'Aug 14, 2026 · 09:24 UTC',\n    expiresAt: 'Aug 25, 2026',\n    expiresInDays: 4,\n    sizeMb: 42.1,\n    categories: ['Profile', 'Logs', 'Workspace', 'Billing'],\n    status: 'ready',\n  },\n  {\n    id: 'DPA-2026-0820',\n    name: 'DPA-2026-0820.json',\n    format: 'json',\n    requestedAt: 'Aug 20, 2026 · 14:15 UTC',\n    expiresAt: 'Aug 27, 2026',\n    expiresInDays: 7,\n    sizeMb: 8.6,\n    categories: ['Activity & Audit Logs'],\n    status: 'processing',\n  },\n  {\n    id: 'DPA-2026-0201',\n    name: 'DPA-2026-0201.zip',\n    format: 'zip',\n    requestedAt: 'Feb 01, 2026 · 11:02 UTC',\n    expiresAt: 'Feb 08, 2026',\n    expiresInDays: 0,\n    sizeMb: 38.4,\n    categories: ['Full Account Archive'],\n    status: 'expired',\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-privacy-data-export-portal, [ui-privacy-data-export-portal]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiCheckboxComponent,\n    UiSelectComponent,\n    UiSelectContentComponent,\n    UiSelectItemComponent,\n    UiSelectTriggerComponent,\n    UiSelectValueComponent,\n    UiSeparatorComponent,\n    UiTableBodyComponent,\n    UiTableCellComponent,\n    UiTableComponent,\n    UiTableHeadComponent,\n    UiTableHeaderComponent,\n    UiTableRowComponent,\n  ],\n  template: `\n    <div data-slot=\"privacy-data-export-portal\" [class]=\"rootClass\">\n      <!-- Header Bar -->\n      <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n        <div class=\"space-y-1\">\n          <div class=\"flex items-center gap-2\">\n            <h1 class=\"text-foreground text-2xl font-bold tracking-tight sm:text-3xl\">\n              Data Privacy & Personal Data Export\n            </h1>\n            <span\n              ui-badge\n              variant=\"outline\"\n              class=\"border-primary/20 bg-primary/5 text-primary hidden items-center gap-1 text-xs font-medium sm:inline-flex\"\n            >\n              <lucide-icon [img]=\"Lock\" class=\"size-3\" />\n              GDPR Art. 15\n            </span>\n          </div>\n          <p class=\"text-muted-foreground text-sm\">\n            Request, generate, and download your complete personal data archives under GDPR Article 15.\n          </p>\n        </div>\n        <div class=\"flex items-center gap-2.5\">\n          <button\n            ui-button\n            type=\"button\"\n            size=\"default\"\n            class=\"gap-2 shadow-xs\"\n            [disabled]=\"isGenerating()\"\n            (click)=\"requestFullExport()\"\n          >\n            <lucide-icon [img]=\"Download\" class=\"size-4\" />\n            Request Full Data Export\n          </button>\n        </div>\n      </div>\n\n      <!-- Notification Banner -->\n      @if (notification(); as note) {\n        <div [class]=\"notificationClass(note.type)\">\n          @if (note.type === 'success') {\n            <lucide-icon [img]=\"CheckCircle2\" class=\"text-success mt-0.5 size-5 shrink-0\" />\n          } @else {\n            <lucide-icon [img]=\"Info\" class=\"text-info mt-0.5 size-5 shrink-0\" />\n          }\n          <div class=\"flex-1 space-y-0.5\">\n            <p class=\"font-medium\">{{ note.title }}</p>\n            <p class=\"text-xs opacity-90\">{{ note.message }}</p>\n          </div>\n          <button\n            ui-button\n            variant=\"ghost\"\n            size=\"sm\"\n            class=\"h-7 px-2 text-xs hover:bg-transparent\"\n            (click)=\"notification.set(null)\"\n          >\n            Dismiss\n          </button>\n        </div>\n      }\n\n      <!-- 4 Privacy & Governance Stat Cards -->\n      <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n        <div ui-card class=\"border-border shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-sm font-medium\">Account Creation Date</h3>\n            <div\n              class=\"border-border bg-muted/50 text-foreground flex size-8 items-center justify-center rounded-md border\"\n            >\n              <lucide-icon [img]=\"Calendar\" class=\"text-muted-foreground size-4\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-1\">\n            <div class=\"text-foreground text-xl font-bold tracking-tight tabular-nums\">Nov 12, 2024</div>\n            <p class=\"text-muted-foreground text-xs\">\n              <span class=\"text-foreground font-medium tabular-nums\">1.8 years</span> active account age\n            </p>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-sm font-medium\">Total Data Footprint</h3>\n            <div\n              class=\"border-border bg-muted/50 text-foreground flex size-8 items-center justify-center rounded-md border\"\n            >\n              <lucide-icon [img]=\"HardDrive\" class=\"text-muted-foreground size-4\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-1\">\n            <div class=\"text-foreground text-xl font-bold tracking-tight tabular-nums\">48.2 MB</div>\n            <p class=\"text-muted-foreground text-xs\">Across 6 storage domains</p>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-sm font-medium\">Data Retention Policy</h3>\n            <div\n              class=\"border-border bg-muted/50 text-foreground flex size-8 items-center justify-center rounded-md border\"\n            >\n              <lucide-icon [img]=\"Clock\" class=\"text-muted-foreground size-4\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-1\">\n            <div class=\"text-foreground text-xl font-bold tracking-tight tabular-nums\">30-Day Policy</div>\n            <p class=\"text-muted-foreground text-xs\">Automated purge of activity logs</p>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-sm font-medium\">Consent Status</h3>\n            <div\n              class=\"border-success/20 bg-success/10 text-success flex size-8 items-center justify-center rounded-md border\"\n            >\n              <lucide-icon [img]=\"ShieldCheck\" class=\"size-4\" />\n            </div>\n          </div>\n          <div ui-card-content class=\"space-y-1\">\n            <div class=\"text-foreground text-xl font-bold tracking-tight tabular-nums\">3 / 3 Active</div>\n            <p class=\"text-success text-xs font-medium\">Required Consents Active</p>\n          </div>\n        </div>\n      </div>\n\n      <!-- Select Data Categories for Export -->\n      <div ui-card class=\"border-border shadow-xs\">\n        <div ui-card-header class=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n          <div class=\"space-y-1\">\n            <h3 ui-card-title class=\"text-foreground text-lg font-semibold\">Select Data Categories for Export</h3>\n            <p ui-card-description>Choose specific data domains to compile into your encrypted archive package.</p>\n          </div>\n          <button\n            ui-button\n            variant=\"outline\"\n            size=\"sm\"\n            class=\"h-8 self-start text-xs sm:self-auto\"\n            (click)=\"toggleAll()\"\n          >\n            {{ isAllSelected() ? 'Deselect All' : 'Select All Categories' }}\n          </button>\n        </div>\n        <div ui-card-content class=\"space-y-6\">\n          <div class=\"grid grid-cols-1 gap-3.5 md:grid-cols-2\">\n            @for (cat of categories; track cat.id) {\n              <div [class]=\"categoryCardClass(cat.id)\" (click)=\"toggleCategory(cat.id)\">\n                <ui-checkbox\n                  [id]=\"'category-' + cat.id\"\n                  [checked]=\"selectedCategories().includes(cat.id)\"\n                  class=\"mt-1\"\n                  (checkedChange)=\"toggleCategory(cat.id)\"\n                  (click)=\"$event.stopPropagation()\"\n                />\n                <div class=\"min-w-0 flex-1 space-y-1.5\">\n                  <div class=\"flex items-center justify-between gap-2\">\n                    <div class=\"flex items-center gap-2\">\n                      <lucide-icon [img]=\"$any(cat.icon)\" class=\"text-muted-foreground size-4\" />\n                      <label\n                        [for]=\"'category-' + cat.id\"\n                        class=\"text-foreground cursor-pointer text-sm leading-none font-medium\"\n                        (click)=\"$event.stopPropagation(); toggleCategory(cat.id)\"\n                      >\n                        {{ cat.name }}\n                      </label>\n                    </div>\n                    <span ui-badge variant=\"secondary\" class=\"shrink-0 font-mono text-xs tabular-nums\">\n                      {{ cat.sizeMb }} MB\n                    </span>\n                  </div>\n                  <p class=\"text-muted-foreground text-xs leading-relaxed\">{{ cat.description }}</p>\n                  <div class=\"text-muted-foreground/80 text-xs tabular-nums\">\n                    Estimated footprint: <span class=\"text-foreground font-medium\">{{ cat.recordCount }}</span>\n                  </div>\n                </div>\n              </div>\n            }\n          </div>\n\n          <ui-separator />\n\n          <div class=\"flex flex-col gap-4 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between\">\n            <div class=\"flex flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-3\">\n              <label class=\"text-foreground text-sm font-medium whitespace-nowrap\">Export Format:</label>\n              <ui-select [value]=\"selectedFormat()\" (valueChange)=\"setFormat($event)\">\n                <button ui-select-trigger class=\"bg-background w-full sm:w-[260px]\">\n                  <ui-select-value placeholder=\"Select export format\" />\n                </button>\n                <ui-select-content>\n                  <ui-select-item value=\"zip\">\n                    <span class=\"flex items-center gap-2\">\n                      <lucide-icon [img]=\"Archive\" class=\"text-muted-foreground size-3.5\" />\n                      <span>Complete ZIP Bundle (.zip)</span>\n                    </span>\n                  </ui-select-item>\n                  <ui-select-item value=\"json\">\n                    <span class=\"flex items-center gap-2\">\n                      <lucide-icon [img]=\"FileText\" class=\"text-muted-foreground size-3.5\" />\n                      <span>JSON Archive (.json)</span>\n                    </span>\n                  </ui-select-item>\n                  <ui-select-item value=\"csv\">\n                    <span class=\"flex items-center gap-2\">\n                      <lucide-icon [img]=\"FileSpreadsheet\" class=\"text-muted-foreground size-3.5\" />\n                      <span>CSV Spreadsheet (.csv)</span>\n                    </span>\n                  </ui-select-item>\n                </ui-select-content>\n              </ui-select>\n            </div>\n\n            <div class=\"flex flex-col items-stretch gap-3 sm:flex-row sm:items-center\">\n              <div class=\"text-muted-foreground text-xs sm:text-right\">\n                Selected:\n                <span class=\"text-foreground font-semibold tabular-nums\">\n                  {{ selectedCategories().length }} of {{ categories.length }} categories\n                </span>\n                <span class=\"text-foreground font-semibold tabular-nums\">~{{ totalSelectedSize() }} MB</span>\n              </div>\n              <button\n                ui-button\n                type=\"button\"\n                class=\"gap-2 shadow-xs\"\n                [disabled]=\"selectedCategories().length === 0 || isGenerating()\"\n                (click)=\"triggerExport('Custom Data Export')\"\n              >\n                @if (isGenerating()) {\n                  <lucide-icon [img]=\"Loader2\" class=\"size-4 animate-spin\" />\n                } @else {\n                  <lucide-icon [img]=\"Archive\" class=\"size-4\" />\n                }\n                {{ isGenerating() ? 'Compiling Archive...' : 'Generate Data Archive' }}\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Legal Compliance Notice Box -->\n      <div\n        class=\"border-border bg-muted/40 text-muted-foreground flex items-start gap-3 rounded-lg border p-4 text-xs shadow-xs\"\n      >\n        <lucide-icon [img]=\"Lock\" class=\"text-muted-foreground mt-0.5 size-4 shrink-0\" />\n        <p class=\"leading-relaxed\">\n          Under <span class=\"text-foreground font-medium\">GDPR Article 15</span> (Right of Access) and\n          <span class=\"text-foreground font-medium\">CCPA §1798.100</span>, you are entitled to request and receive all\n          personal data processed by this service. Export packages are generated on-demand, encrypted with AES-256, and\n          available for download for 7 days before automated deletion.\n        </p>\n      </div>\n\n      <!-- Export Archive History Table -->\n      <div ui-card class=\"border-border shadow-xs\">\n        <div ui-card-header>\n          <div class=\"flex items-center justify-between\">\n            <div class=\"space-y-1\">\n              <h3 ui-card-title class=\"text-foreground text-lg font-semibold\">Export Archive History</h3>\n              <p ui-card-description>\n                Previous data archive requests and generation status. Downloads remain available for 7 days.\n              </p>\n            </div>\n            <span ui-badge variant=\"outline\" class=\"font-mono text-xs tabular-nums\">\n              {{ archives().length }} archives\n            </span>\n          </div>\n        </div>\n        <div ui-card-content class=\"p-0\">\n          <div class=\"overflow-x-auto\">\n            <table ui-table>\n              <thead ui-table-header>\n                <tr ui-table-row>\n                  <th ui-table-head class=\"min-w-[220px]\">Archive ID & Format</th>\n                  <th ui-table-head class=\"min-w-[170px]\">Request Date</th>\n                  <th ui-table-head class=\"min-w-[140px]\">Expiration</th>\n                  <th ui-table-head class=\"min-w-[100px] text-right\">File Size</th>\n                  <th ui-table-head class=\"min-w-[150px]\">Status</th>\n                  <th ui-table-head class=\"min-w-[140px] text-right\">Action</th>\n                </tr>\n              </thead>\n              <tbody ui-table-body>\n                @for (arc of archives(); track arc.id) {\n                  <tr ui-table-row class=\"hover:bg-muted/40\">\n                    <td ui-table-cell>\n                      <div class=\"space-y-1\">\n                        <div class=\"flex items-center gap-2\">\n                          <span class=\"text-foreground font-mono text-xs font-semibold\">{{ arc.name }}</span>\n                          <span ui-badge variant=\"outline\" class=\"px-1.5 py-0 font-mono text-xs uppercase\">\n                            {{ arc.format }}\n                          </span>\n                        </div>\n                        <p class=\"text-muted-foreground text-xs\">{{ arc.categories.join(', ') }}</p>\n                      </div>\n                    </td>\n                    <td ui-table-cell class=\"text-muted-foreground text-xs tabular-nums\">{{ arc.requestedAt }}</td>\n                    <td ui-table-cell>\n                      <div class=\"space-y-0.5\">\n                        <span [class]=\"expiryClass(arc)\">\n                          {{ arc.status === 'expired' ? 'Expired' : 'Expires in ' + arc.expiresInDays + ' days' }}\n                        </span>\n                        <p class=\"text-muted-foreground/70 text-xs tabular-nums\">{{ arc.expiresAt }}</p>\n                      </div>\n                    </td>\n                    <td ui-table-cell class=\"text-foreground text-right font-mono text-xs font-medium tabular-nums\">\n                      {{ arc.sizeMb.toFixed(1) }} MB\n                    </td>\n                    <td ui-table-cell>\n                      @if (arc.status === 'ready') {\n                        <span\n                          ui-badge\n                          variant=\"outline\"\n                          class=\"border-success/30 bg-success/10 text-success items-center gap-1.5 text-xs font-medium\"\n                        >\n                          <lucide-icon [img]=\"Check\" class=\"size-3\" />\n                          Ready for Download\n                        </span>\n                      } @else if (arc.status === 'processing') {\n                        <span\n                          ui-badge\n                          variant=\"outline\"\n                          class=\"border-warning/30 bg-warning/10 text-warning items-center gap-1.5 text-xs font-medium\"\n                        >\n                          <lucide-icon [img]=\"Loader2\" class=\"size-3 animate-spin\" />\n                          Processing Archive\n                        </span>\n                      } @else {\n                        <span ui-badge variant=\"secondary\" class=\"text-muted-foreground text-xs\">Expired</span>\n                      }\n                    </td>\n                    <td ui-table-cell class=\"text-right\">\n                      @if (arc.status === 'ready') {\n                        <button\n                          ui-button\n                          aria-label=\"Download attachment\"\n                          size=\"sm\"\n                          class=\"h-8 gap-1.5 text-xs shadow-xs\"\n                          (click)=\"handleDownload(arc)\"\n                        >\n                          <lucide-icon [img]=\"Download\" class=\"size-3.5\" />\n                          Download ZIP\n                        </button>\n                      } @else if (arc.status === 'processing') {\n                        <button ui-button size=\"sm\" variant=\"secondary\" disabled class=\"h-8 gap-1.5 text-xs opacity-75\">\n                          <lucide-icon [img]=\"Loader2\" class=\"size-3.5 animate-spin\" />\n                          Processing...\n                        </button>\n                      } @else {\n                        <button\n                          ui-button\n                          size=\"sm\"\n                          variant=\"outline\"\n                          class=\"h-8 gap-1.5 text-xs\"\n                          (click)=\"handleReRequest(arc)\"\n                        >\n                          <lucide-icon [img]=\"RefreshCw\" class=\"size-3.5\" />\n                          Re-request\n                        </button>\n                      }\n                    </td>\n                  </tr>\n                }\n              </tbody>\n            </table>\n          </div>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiPrivacyDataExportPortalComponent {\n  protected readonly Activity = Activity\n  protected readonly Archive = Archive\n  protected readonly Calendar = Calendar\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly Clock = Clock\n  protected readonly CreditCard = CreditCard\n  protected readonly Download = Download\n  protected readonly FileSpreadsheet = FileSpreadsheet\n  protected readonly FileText = FileText\n  protected readonly FolderArchive = FolderArchive\n  protected readonly HardDrive = HardDrive\n  protected readonly Info = Info\n  protected readonly Loader2 = Loader2\n  protected readonly Lock = Lock\n  protected readonly RefreshCw = RefreshCw\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly User = User\n\n  protected readonly categories = CATEGORIES\n\n  @Input() set initialCategories(v: string[] | undefined) {\n    this.selectedCategories.set([...(v ?? CATEGORIES.map((c) => c.id))])\n  }\n  @Input() set initialFormat(v: ExportFormat | undefined) {\n    this.selectedFormat.set(v ?? 'zip')\n  }\n  @Input('class') className?: string\n\n  readonly selectedCategories = signal<string[]>(CATEGORIES.map((c) => c.id))\n  readonly selectedFormat = signal<ExportFormat>('zip')\n  readonly archives = signal<ArchiveRecord[]>(INITIAL_ARCHIVES.map((a) => ({ ...a, categories: [...a.categories] })))\n  readonly isGenerating = signal(false)\n  readonly notification = signal<{ title: string; message: string; type: 'success' | 'info' } | null>(null)\n\n  readonly totalSelectedSize = computed(() => {\n    const sum = CATEGORIES.filter((c) => this.selectedCategories().includes(c.id)).reduce((acc, c) => acc + c.sizeMb, 0)\n    return sum.toFixed(1)\n  })\n\n  readonly isAllSelected = computed(() => this.selectedCategories().length === CATEGORIES.length)\n\n  get rootClass(): string {\n    return cn('w-full space-y-6', this.className)\n  }\n\n  notificationClass(type: 'success' | 'info'): string {\n    return cn(\n      'flex items-start gap-3 rounded-lg border p-4 text-sm transition-colors',\n      type === 'success' ? 'border-success/30 bg-success/10 text-success' : 'border-info/30 bg-info/10 text-info',\n    )\n  }\n\n  categoryCardClass(categoryId: string): string {\n    return cn(\n      'group flex cursor-pointer items-start gap-3.5 rounded-lg border p-4 transition-colors',\n      this.selectedCategories().includes(categoryId)\n        ? 'border-primary/50 bg-primary/5 dark:bg-primary/10 shadow-xs'\n        : 'border-border bg-card hover:bg-muted/40',\n    )\n  }\n\n  expiryClass(arc: ArchiveRecord): string {\n    return cn(\n      'text-xs font-medium tabular-nums',\n      arc.status === 'expired'\n        ? 'text-muted-foreground line-through'\n        : arc.expiresInDays <= 4\n          ? 'text-warning'\n          : 'text-muted-foreground',\n    )\n  }\n\n  setFormat(value: string): void {\n    this.selectedFormat.set(value as ExportFormat)\n  }\n\n  toggleCategory(categoryId: string): void {\n    this.selectedCategories.update((prev) =>\n      prev.includes(categoryId) ? prev.filter((id) => id !== categoryId) : [...prev, categoryId],\n    )\n  }\n\n  toggleAll(): void {\n    this.selectedCategories.set(this.isAllSelected() ? [] : CATEGORIES.map((c) => c.id))\n  }\n\n  requestFullExport(): void {\n    this.selectedCategories.set(CATEGORIES.map((c) => c.id))\n    this.selectedFormat.set('zip')\n    this.triggerExport('Full Data Export')\n  }\n\n  triggerExport(label = 'Custom Data Export'): void {\n    if (this.selectedCategories().length === 0) return\n\n    this.isGenerating.set(true)\n    this.notification.set({\n      title: 'Archive Request Queued',\n      message: `${label} requested. Compiling ${this.selectedCategories().length} categories (${this.totalSelectedSize()} MB) with AES-256 encryption.`,\n      type: 'info',\n    })\n\n    setTimeout(() => {\n      const newId = `DPA-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}-${Math.floor(100 + Math.random() * 900)}`\n      const ext = this.selectedFormat()\n      const catNames = CATEGORIES.filter((c) => this.selectedCategories().includes(c.id)).map(\n        (c) => c.name.split(' ')[0],\n      )\n\n      const newRecord: ArchiveRecord = {\n        id: newId,\n        name: `${newId}.${ext}`,\n        format: this.selectedFormat(),\n        requestedAt: 'Just now',\n        expiresAt: '7 days from now',\n        expiresInDays: 7,\n        sizeMb: parseFloat(this.totalSelectedSize()),\n        categories: catNames,\n        status: 'ready',\n      }\n\n      this.archives.update((prev) => [newRecord, ...prev])\n      this.isGenerating.set(false)\n      this.notification.set({\n        title: 'Archive Ready',\n        message: `Export package ${newRecord.name} (${newRecord.sizeMb} MB) has been generated and is ready for download.`,\n        type: 'success',\n      })\n    }, 1200)\n  }\n\n  handleDownload(archive: ArchiveRecord): void {\n    this.notification.set({\n      title: 'Download Initiated',\n      message: `Securely downloading ${archive.name} (${archive.sizeMb} MB). SHA-256 checksum verified.`,\n      type: 'success',\n    })\n  }\n\n  handleReRequest(archive: ArchiveRecord): void {\n    this.notification.set({\n      title: 'Archive Re-generation Requested',\n      message: `Re-generating expired archive for ${archive.categories.join(', ')}.`,\n      type: 'info',\n    })\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/privacy-data-export-portal/privacy-data-export-portal.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/privacy-data-export-portal/index.ts",
      "content": "export {\n  UiPrivacyDataExportPortalComponent,\n  type ExportFormat,\n  type ArchiveStatus,\n  type DataCategory,\n  type ArchiveRecord,\n} from './privacy-data-export-portal.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/privacy-data-export-portal/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "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/checkbox.json",
    "https://uipkge.dev/r/angular/select.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/table.json"
  ],
  "description": "GDPR Article 15 and CCPA personal data export portal with privacy governance stat cards, granular category export selector, format chooser, and export archive history table.",
  "categories": [
    "legal",
    "security",
    "app"
  ]
}