{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "access-request-workflow",
  "title": "Access Request Workflow",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/access-request-workflow/access-request-workflow.component.ts",
      "content": "// Just-In-Time (JIT) IAM temporary privilege escalation: governance metric cards,\n// elevated session countdown timers, request form dialog, and an approval queue with\n// instant revoke and audit actions. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, DestroyRef, Input, computed, inject, signal } from '@angular/core'\nimport {\n  Check,\n  Clock,\n  Copy,\n  FileText,\n  History,\n  Hourglass,\n  KeyRound,\n  Lock,\n  LucideAngularModule,\n  MoreHorizontal,\n  Plus,\n  Search,\n  Send,\n  ShieldAlert,\n  ShieldCheck,\n  UserCheck,\n  X,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component'\nimport { UiBadgeComponent, type BadgeVariant } 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 {\n  UiDialogComponent,\n  UiDialogContentComponent,\n  UiDialogDescriptionComponent,\n  UiDialogFooterComponent,\n  UiDialogHeaderComponent,\n  UiDialogTitleComponent,\n} from '@/ui/dialog/dialog.component'\nimport {\n  UiDropdownMenuComponent,\n  UiDropdownMenuContentComponent,\n  UiDropdownMenuItemComponent,\n  UiDropdownMenuLabelComponent,\n  UiDropdownMenuSeparatorComponent,\n  UiDropdownMenuTriggerComponent,\n} from '@/ui/dropdown-menu/dropdown-menu.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport {\n  UiSelectComponent,\n  UiSelectContentComponent,\n  UiSelectItemComponent,\n  UiSelectTriggerComponent,\n  UiSelectValueComponent,\n} from '@/ui/select/select.component'\nimport {\n  UiTableBodyComponent,\n  UiTableCellComponent,\n  UiTableComponent,\n  UiTableHeadComponent,\n  UiTableHeaderComponent,\n  UiTableRowComponent,\n} from '@/ui/table/table.component'\nimport { UiTextareaComponent } from '@/ui/textarea/textarea.component'\n\nexport type AccessRequestStatus = 'active' | 'pending' | 'expired' | 'rejected'\nexport type AccessRequestTab = 'all' | 'pending' | 'active' | 'expired'\n\nexport interface AccessRequester {\n  name: string\n  email: string\n  team: string\n  avatar: string\n}\n\nexport interface AccessRequest {\n  id: string\n  requester: AccessRequester\n  targetRole: string\n  system: string\n  duration: string\n  timeLeftLabel: string\n  secondsLeft: number\n  justification: string\n  ticketId: string\n  approver: string\n  status: AccessRequestStatus\n  createdAt: string\n  expiresAt: string\n}\n\nconst initialRequests: AccessRequest[] = [\n  {\n    id: 'req-1',\n    requester: { name: 'Elena Rostova', email: 'elena.rostova@acme.corp', team: 'SecOps Team', avatar: 'ER' },\n    targetRole: 'AWS Production Admin',\n    system: 'AWS Cloud IAM (us-east-1)',\n    duration: '4 Hours',\n    timeLeftLabel: '2h 45m left',\n    secondsLeft: 9918,\n    justification: 'Investigating latency spike on payment gateway for incident #SEC-849',\n    ticketId: 'SEC-849',\n    approver: 'Marcus Vance',\n    status: 'active',\n    createdAt: '1h 15m ago',\n    expiresAt: 'Today at 16:30',\n  },\n  {\n    id: 'req-2',\n    requester: { name: 'Marcus Vance', email: 'marcus.vance@acme.corp', team: 'DevOps & Infra', avatar: 'MV' },\n    targetRole: 'Production DB Read-Only',\n    system: 'PostgreSQL Aurora Primary',\n    duration: '1 Hour (Emergency)',\n    timeLeftLabel: '48m left',\n    secondsLeft: 2892,\n    justification: 'Emergency replica query check during PostgreSQL failover testing',\n    ticketId: 'INC-2044',\n    approver: 'Sarah Chen',\n    status: 'active',\n    createdAt: '12m ago',\n    expiresAt: 'Today at 14:15',\n  },\n  {\n    id: 'req-3',\n    requester: { name: 'Sarah Chen', email: 'sarah.chen@acme.corp', team: 'Core Platform', avatar: 'SC' },\n    targetRole: 'Customer PII Decryption',\n    system: 'Customer Data Vault (KMS)',\n    duration: '8 Hours (1 Shift)',\n    timeLeftLabel: 'Pending Approval',\n    secondsLeft: 28800,\n    justification: 'GDPR user deletion verification request #COMP-1102',\n    ticketId: 'COMP-1102',\n    approver: 'Marcus Vance',\n    status: 'pending',\n    createdAt: '10m ago',\n    expiresAt: 'Awaiting approval',\n  },\n  {\n    id: 'req-4',\n    requester: { name: 'David Kim', email: 'david.kim@acme.corp', team: 'Fintech Systems', avatar: 'DK' },\n    targetRole: 'Stripe Dashboard Full Access',\n    system: 'Stripe Merchant Gateway',\n    duration: '1 Hour (Emergency)',\n    timeLeftLabel: 'Pending Approval',\n    secondsLeft: 3600,\n    justification: 'Refunding disputed charges for compromised merchant accounts',\n    ticketId: 'FIN-582',\n    approver: 'Marcus Vance',\n    status: 'pending',\n    createdAt: '25m ago',\n    expiresAt: 'Awaiting approval',\n  },\n  {\n    id: 'req-5',\n    requester: { name: 'Alex Morgan', email: 'alex.m@acme.corp', team: 'Data Platform', avatar: 'AM' },\n    targetRole: 'Production DB Read-Only',\n    system: 'Snowflake Core Analytics',\n    duration: '4 Hours',\n    timeLeftLabel: 'Expired',\n    secondsLeft: 0,\n    justification: 'Quarterly compliance metrics export and audit schema migration verification',\n    ticketId: 'AUD-309',\n    approver: 'Elena Rostova',\n    status: 'expired',\n    createdAt: 'Yesterday',\n    expiresAt: 'Expired at 18:00',\n  },\n]\n\nfunction formatSecondsValue(seconds: number): string {\n  if (seconds <= 0) return '00:00:00'\n  const h = Math.floor(seconds / 3600)\n  const m = Math.floor((seconds % 3600) / 60)\n  const s = seconds % 60\n  return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`\n}\n\nfunction timeLeftLabelFor(seconds: number): string {\n  const h = Math.floor(seconds / 3600)\n  const m = Math.floor((seconds % 3600) / 60)\n  return h > 0 ? `${h}h ${m}m left` : `${m}m left`\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-access-request-workflow, [ui-access-request-workflow]',\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    UiAvatarComponent,\n    UiAvatarFallbackComponent,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiDialogComponent,\n    UiDialogContentComponent,\n    UiDialogDescriptionComponent,\n    UiDialogFooterComponent,\n    UiDialogHeaderComponent,\n    UiDialogTitleComponent,\n    UiDropdownMenuComponent,\n    UiDropdownMenuContentComponent,\n    UiDropdownMenuItemComponent,\n    UiDropdownMenuLabelComponent,\n    UiDropdownMenuSeparatorComponent,\n    UiDropdownMenuTriggerComponent,\n    UiInputComponent,\n    UiSelectComponent,\n    UiSelectContentComponent,\n    UiSelectItemComponent,\n    UiSelectTriggerComponent,\n    UiSelectValueComponent,\n    UiTableBodyComponent,\n    UiTableCellComponent,\n    UiTableComponent,\n    UiTableHeadComponent,\n    UiTableHeaderComponent,\n    UiTableRowComponent,\n    UiTextareaComponent,\n  ],\n  template: `\n    <div data-slot=\"access-request-workflow\" [class]=\"rootClass\">\n      <!-- Header -->\n      <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n        <div>\n          <div class=\"flex items-center gap-2\">\n            <div class=\"bg-primary/10 text-primary flex size-9 items-center justify-center rounded-lg\">\n              <lucide-icon [img]=\"KeyRound\" class=\"size-5\" />\n            </div>\n            <h1 class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl\">{{ title }}</h1>\n          </div>\n          <p class=\"text-muted-foreground mt-1 text-sm\">{{ subtitle }}</p>\n        </div>\n\n        <button ui-button class=\"shrink-0 gap-2 shadow-xs\" (click)=\"isDialogOpen.set(true)\">\n          <lucide-icon [img]=\"Plus\" class=\"size-4\" />\n          Request New Access\n        </button>\n      </div>\n\n      <!-- Request New Access dialog -->\n      <ui-dialog [open]=\"isDialogOpen()\" (openChange)=\"isDialogOpen.set($event)\">\n        <ui-dialog-content class=\"sm:max-w-lg\">\n          <div ui-dialog-header>\n            <h2 ui-dialog-title class=\"flex items-center gap-2\">\n              <lucide-icon [img]=\"ShieldAlert\" class=\"text-primary size-5\" />\n              Request Temporary Access\n            </h2>\n            <p ui-dialog-description>\n              Submit a Just-In-Time (JIT) access request with duration and business justification.\n            </p>\n          </div>\n\n          <form class=\"space-y-4 py-2\" (submit)=\"handleCreateRequest($event)\">\n            <div class=\"space-y-1.5\">\n              <label class=\"text-foreground text-xs font-medium\">Target System / Role</label>\n              <ui-select [value]=\"formRole()\" (valueChange)=\"formRole.set($event)\">\n                <button ui-select-trigger class=\"w-full\">\n                  <ui-select-value placeholder=\"Select target role\" />\n                </button>\n                <ui-select-content>\n                  <ui-select-item value=\"Production DB Read-Only\">Production DB Read-Only</ui-select-item>\n                  <ui-select-item value=\"AWS Production Admin\">AWS Production Admin</ui-select-item>\n                  <ui-select-item value=\"Stripe Dashboard Full Access\">Stripe Dashboard Full Access</ui-select-item>\n                  <ui-select-item value=\"Customer PII Decryption\">Customer PII Decryption</ui-select-item>\n                </ui-select-content>\n              </ui-select>\n            </div>\n\n            <div class=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n              <div class=\"space-y-1.5\">\n                <label class=\"text-foreground text-xs font-medium\">Access Duration</label>\n                <ui-select [value]=\"formDuration()\" (valueChange)=\"formDuration.set($event)\">\n                  <button ui-select-trigger class=\"w-full\">\n                    <ui-select-value placeholder=\"Select duration\" />\n                  </button>\n                  <ui-select-content>\n                    <ui-select-item value=\"1 Hour (Emergency)\">1 Hour (Emergency)</ui-select-item>\n                    <ui-select-item value=\"4 Hours\">4 Hours</ui-select-item>\n                    <ui-select-item value=\"8 Hours (1 Shift)\">8 Hours (1 Shift)</ui-select-item>\n                    <ui-select-item value=\"24 Hours max\">24 Hours max</ui-select-item>\n                  </ui-select-content>\n                </ui-select>\n              </div>\n\n              <div class=\"space-y-1.5\">\n                <label class=\"text-foreground text-xs font-medium\">Approver / Team</label>\n                <ui-select [value]=\"formApprover()\" (valueChange)=\"formApprover.set($event)\">\n                  <button ui-select-trigger class=\"w-full\">\n                    <ui-select-value placeholder=\"Select approver\" />\n                  </button>\n                  <ui-select-content>\n                    <ui-select-item value=\"Security Lead: Marcus Vance\">Security Lead: Marcus Vance</ui-select-item>\n                    <ui-select-item value=\"Infrastructure Team: Sarah Chen\">\n                      Infrastructure Team: Sarah Chen\n                    </ui-select-item>\n                    <ui-select-item value=\"Compliance Officer: Elena Rostova\">\n                      Compliance Officer: Elena Rostova\n                    </ui-select-item>\n                    <ui-select-item value=\"On-Call Lead: David Kim\">On-Call Lead: David Kim</ui-select-item>\n                  </ui-select-content>\n                </ui-select>\n              </div>\n            </div>\n\n            <div class=\"space-y-1.5\">\n              <label class=\"text-foreground text-xs font-medium\">Business Justification</label>\n              <ui-textarea\n                [value]=\"formJustification()\"\n                (valueChange)=\"formJustification.set($event)\"\n                placeholder=\"Investigating latency spike on payment gateway for incident #SEC-849\"\n                [rows]=\"3\"\n                class=\"resize-none text-xs\"\n              />\n              <p class=\"text-muted-foreground text-xs\">\n                All elevated actions are recorded and signed in the immutable IAM audit stream.\n              </p>\n            </div>\n\n            <div ui-dialog-footer class=\"pt-2\">\n              <button ui-button type=\"button\" variant=\"outline\" (click)=\"isDialogOpen.set(false)\">Cancel</button>\n              <button ui-button type=\"submit\" class=\"gap-2\">\n                <lucide-icon [img]=\"Send\" class=\"size-4\" />\n                Submit Request\n              </button>\n            </div>\n          </form>\n        </ui-dialog-content>\n      </ui-dialog>\n\n      <!-- 4 Access Governance KPI 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/80 relative overflow-hidden shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium\">Active Elevated Sessions</h3>\n            <div class=\"bg-success/10 text-success rounded-md p-1.5\">\n              <lucide-icon [img]=\"ShieldAlert\" class=\"size-4\" />\n            </div>\n          </div>\n          <div ui-card-content>\n            <div class=\"text-foreground text-2xl font-bold tracking-tight tabular-nums\">{{ activeCount() }}</div>\n            <p class=\"text-muted-foreground mt-1 text-xs\">Expiring within 8h</p>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border/80 relative overflow-hidden shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium\">Pending Approvals</h3>\n            <div class=\"bg-warning/10 text-warning rounded-md p-1.5\">\n              <lucide-icon [img]=\"Clock\" class=\"size-4\" />\n            </div>\n          </div>\n          <div ui-card-content>\n            <div class=\"text-foreground text-2xl font-bold tracking-tight tabular-nums\">{{ pendingCount() }}</div>\n            <p class=\"text-muted-foreground mt-1 text-xs\">Requires security lead review</p>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border/80 relative overflow-hidden shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium\">Requests Expiring Today</h3>\n            <div class=\"bg-chart-1/10 text-chart-1 rounded-md p-1.5\">\n              <lucide-icon [img]=\"Hourglass\" class=\"size-4\" />\n            </div>\n          </div>\n          <div ui-card-content>\n            <div class=\"text-foreground text-2xl font-bold tracking-tight tabular-nums\">{{ expiringCount() }}</div>\n            <p class=\"text-muted-foreground mt-1 text-xs\">Auto-revocation armed</p>\n          </div>\n        </div>\n\n        <div ui-card class=\"border-border/80 relative overflow-hidden shadow-xs\">\n          <div ui-card-header class=\"flex flex-row items-center justify-between pb-2\">\n            <h3 ui-card-title class=\"text-muted-foreground text-xs font-medium\">30-Day Total Requests</h3>\n            <div class=\"bg-primary/10 text-primary rounded-md p-1.5\">\n              <lucide-icon [img]=\"History\" class=\"size-4\" />\n            </div>\n          </div>\n          <div ui-card-content>\n            <div class=\"text-foreground text-2xl font-bold tracking-tight tabular-nums\">{{ total30Day() }}</div>\n            <p class=\"text-muted-foreground mt-1 text-xs\">98.2% compliance audit pass</p>\n          </div>\n        </div>\n      </div>\n\n      <!-- Pending & Active Requests Table Card -->\n      <div ui-card class=\"border-border/80 shadow-xs\">\n        <div ui-card-header class=\"border-border/60 border-b pb-4\">\n          <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n            <div>\n              <h3 ui-card-title class=\"text-foreground text-base font-semibold\">Privilege Escalation Queue</h3>\n              <p ui-card-description class=\"text-muted-foreground mt-0.5 text-xs\">\n                Review, approve, or reject active and pending privilege requests.\n              </p>\n            </div>\n\n            <div class=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n              <div class=\"relative w-full sm:w-60\">\n                <lucide-icon\n                  [img]=\"Search\"\n                  class=\"text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2\"\n                />\n                <ui-input\n                  [value]=\"searchQuery()\"\n                  (valueChange)=\"searchQuery.set($event)\"\n                  placeholder=\"Search requester, role, ticket...\"\n                  class=\"h-8 pl-8 text-xs\"\n                />\n              </div>\n\n              <div class=\"border-border bg-muted/40 flex items-center rounded-lg border p-0.5\">\n                <button type=\"button\" [class]=\"tabClass('all')\" (click)=\"setSelectedTab('all')\">\n                  All ({{ requests().length }})\n                </button>\n                <button type=\"button\" [class]=\"tabClass('pending')\" (click)=\"setSelectedTab('pending')\">\n                  Pending ({{ pendingCount() }})\n                </button>\n                <button type=\"button\" [class]=\"tabClass('active')\" (click)=\"setSelectedTab('active')\">\n                  Active ({{ activeCount() }})\n                </button>\n                <button type=\"button\" [class]=\"tabClass('expired')\" (click)=\"setSelectedTab('expired')\">\n                  Expired ({{ expiredTabCount() }})\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n\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 class=\"hover:bg-transparent\">\n                  <th ui-table-head class=\"w-[220px] text-xs font-medium\">Requester</th>\n                  <th ui-table-head class=\"w-[200px] text-xs font-medium\">Target Role & System</th>\n                  <th ui-table-head class=\"w-[170px] text-xs font-medium\">Duration & Timer</th>\n                  <th ui-table-head class=\"min-w-[260px] text-xs font-medium\">Business Justification</th>\n                  <th ui-table-head class=\"w-[180px] text-xs font-medium\">Status</th>\n                  <th ui-table-head class=\"w-[160px] text-right text-xs font-medium\">Actions</th>\n                </tr>\n              </thead>\n              <tbody ui-table-body>\n                @if (filteredRequests().length === 0) {\n                  <tr ui-table-row>\n                    <td ui-table-cell [attr.colspan]=\"6\" class=\"text-muted-foreground h-32 text-center text-xs\">\n                      No access requests match your filter.\n                    </td>\n                  </tr>\n                } @else {\n                  @for (req of filteredRequests(); track req.id) {\n                    <tr ui-table-row class=\"hover:bg-muted/30 transition-colors\">\n                      <td ui-table-cell>\n                        <div class=\"flex items-center gap-3\">\n                          <ui-avatar class=\"border-border/60 size-8 border\">\n                            <ui-avatar-fallback class=\"bg-muted text-foreground text-xs font-semibold\">\n                              {{ req.requester.avatar }}\n                            </ui-avatar-fallback>\n                          </ui-avatar>\n                          <div class=\"min-w-0\">\n                            <div class=\"text-foreground truncate text-xs font-medium\">{{ req.requester.name }}</div>\n                            <div class=\"text-muted-foreground truncate text-xs\">{{ req.requester.email }}</div>\n                          </div>\n                        </div>\n                      </td>\n\n                      <td ui-table-cell>\n                        <div class=\"flex flex-col gap-1\">\n                          <span ui-badge [variant]=\"roleBadgeVariant(req.targetRole)\" class=\"w-fit text-xs font-medium\">\n                            {{ req.targetRole }}\n                          </span>\n                          <span class=\"text-muted-foreground truncate text-xs\">{{ req.system }}</span>\n                        </div>\n                      </td>\n\n                      <td ui-table-cell>\n                        <div class=\"flex flex-col gap-1\">\n                          <span class=\"text-foreground text-xs font-medium\">{{ req.duration }}</span>\n                          @if (req.status === 'active') {\n                            <div class=\"text-success flex items-center gap-1.5 font-mono text-xs tabular-nums\">\n                              <lucide-icon [img]=\"Clock\" class=\"size-3 shrink-0\" />\n                              <span>{{ formatSeconds(req.secondsLeft) }}</span>\n                            </div>\n                          } @else if (req.status === 'pending') {\n                            <div class=\"text-muted-foreground flex items-center gap-1.5 font-mono text-xs tabular-nums\">\n                              <lucide-icon [img]=\"Clock\" class=\"size-3 shrink-0\" />\n                              <span>Awaiting start</span>\n                            </div>\n                          } @else {\n                            <div class=\"text-muted-foreground flex items-center gap-1.5 font-mono text-xs tabular-nums\">\n                              <lucide-icon [img]=\"Clock\" class=\"size-3 shrink-0\" />\n                              <span>Session ended</span>\n                            </div>\n                          }\n                        </div>\n                      </td>\n\n                      <td ui-table-cell>\n                        <div class=\"flex flex-col gap-1\">\n                          <div class=\"flex items-center gap-1.5\">\n                            <span\n                              class=\"border-border bg-muted/60 text-foreground inline-flex items-center rounded border px-1.5 py-0.5 font-mono text-xs font-medium\"\n                            >\n                              #{{ req.ticketId }}\n                            </span>\n                            <span class=\"text-muted-foreground text-xs\">\n                              Approver: {{ shortApprover(req.approver) }}\n                            </span>\n                          </div>\n                          <p class=\"text-muted-foreground line-clamp-2 text-xs\" [attr.title]=\"req.justification\">\n                            {{ req.justification }}\n                          </p>\n                        </div>\n                      </td>\n\n                      <td ui-table-cell>\n                        <div class=\"flex items-center\">\n                          @if (req.status === 'active') {\n                            <span ui-badge variant=\"success\" class=\"gap-1.5 text-xs font-medium\">\n                              <span class=\"relative flex size-2\">\n                                <span\n                                  class=\"bg-success absolute inline-flex h-full w-full rounded-full opacity-75\"\n                                ></span>\n                                <span class=\"bg-success relative inline-flex size-2 rounded-full\"></span>\n                              </span>\n                              <span>Active - {{ req.timeLeftLabel }}</span>\n                            </span>\n                          } @else if (req.status === 'pending') {\n                            <span ui-badge variant=\"warning\" class=\"gap-1.5 text-xs font-medium\">\n                              <span class=\"bg-warning size-2 animate-pulse rounded-full\"></span>\n                              <span>Pending Approval</span>\n                            </span>\n                          } @else if (req.status === 'expired') {\n                            <span\n                              ui-badge\n                              variant=\"secondary\"\n                              class=\"text-muted-foreground gap-1.5 text-xs font-normal\"\n                            >\n                              <span class=\"bg-muted-foreground/50 size-2 rounded-full\"></span>\n                              <span>Expired</span>\n                            </span>\n                          } @else {\n                            <span ui-badge variant=\"destructive\" class=\"gap-1.5 text-xs font-normal\">\n                              <span class=\"bg-destructive size-2 rounded-full\"></span>\n                              <span>Rejected</span>\n                            </span>\n                          }\n                        </div>\n                      </td>\n\n                      <td ui-table-cell class=\"text-right\">\n                        <div class=\"flex items-center justify-end gap-1.5\">\n                          @if (req.status === 'pending') {\n                            <button\n                              ui-button\n                              size=\"sm\"\n                              class=\"h-7 gap-1 px-2.5 text-xs shadow-xs\"\n                              (click)=\"handleApprove(req.id)\"\n                            >\n                              <lucide-icon [img]=\"Check\" class=\"size-3.5\" />\n                              Approve\n                            </button>\n                            <button\n                              ui-button\n                              size=\"sm\"\n                              variant=\"outline\"\n                              class=\"text-destructive hover:bg-destructive/10 hover:text-destructive h-7 gap-1 px-2 text-xs\"\n                              (click)=\"handleReject(req.id)\"\n                            >\n                              <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n                              Reject\n                            </button>\n                          }\n                          @if (req.status === 'active') {\n                            <button\n                              ui-button\n                              size=\"sm\"\n                              variant=\"outline\"\n                              class=\"text-destructive border-destructive/30 hover:bg-destructive/10 hover:text-destructive h-7 gap-1 px-2 text-xs\"\n                              (click)=\"handleRevoke(req.id)\"\n                            >\n                              <lucide-icon [img]=\"Lock\" class=\"size-3.5\" />\n                              Revoke\n                            </button>\n                            <ui-dropdown-menu>\n                              <button ui-button ui-dropdown-menu-trigger variant=\"ghost\" size=\"icon\" class=\"size-7\">\n                                <lucide-icon [img]=\"MoreHorizontal\" class=\"size-3.5\" />\n                                <span class=\"sr-only\">More actions</span>\n                              </button>\n                              <ui-dropdown-menu-content align=\"end\" class=\"w-48\">\n                                <div ui-dropdown-menu-label class=\"text-xs\">Elevated Session</div>\n                                <ui-dropdown-menu-separator />\n                                <ui-dropdown-menu-item (select)=\"handleExtend(req.id)\">\n                                  <lucide-icon [img]=\"Clock\" class=\"mr-2 size-3.5\" />\n                                  <span>Extend 30 Minutes</span>\n                                </ui-dropdown-menu-item>\n                                <ui-dropdown-menu-item (select)=\"handleCopyToken(req.id)\">\n                                  <lucide-icon [img]=\"Copy\" class=\"mr-2 size-3.5\" />\n                                  <span>{{ copiedId() === req.id ? 'Copied Token!' : 'Copy Token' }}</span>\n                                </ui-dropdown-menu-item>\n                                <ui-dropdown-menu-separator />\n                                <ui-dropdown-menu-item\n                                  class=\"text-destructive focus:text-destructive\"\n                                  (select)=\"handleRevoke(req.id)\"\n                                >\n                                  <lucide-icon [img]=\"ShieldAlert\" class=\"mr-2 size-3.5\" />\n                                  <span>Revoke Immediately</span>\n                                </ui-dropdown-menu-item>\n                              </ui-dropdown-menu-content>\n                            </ui-dropdown-menu>\n                          }\n                          @if (req.status !== 'pending' && req.status !== 'active') {\n                            <button\n                              ui-button\n                              size=\"sm\"\n                              variant=\"ghost\"\n                              class=\"text-muted-foreground hover:text-foreground h-7 px-2 text-xs\"\n                              (click)=\"handleReRequest(req.id)\"\n                            >\n                              Re-request\n                            </button>\n                            <ui-dropdown-menu>\n                              <button ui-button ui-dropdown-menu-trigger variant=\"ghost\" size=\"icon\" class=\"size-7\">\n                                <lucide-icon [img]=\"MoreHorizontal\" class=\"size-3.5\" />\n                                <span class=\"sr-only\">More actions</span>\n                              </button>\n                              <ui-dropdown-menu-content align=\"end\" class=\"w-44\">\n                                <ui-dropdown-menu-item (select)=\"handleReRequest(req.id)\">\n                                  <lucide-icon [img]=\"History\" class=\"mr-2 size-3.5\" />\n                                  <span>Request Again</span>\n                                </ui-dropdown-menu-item>\n                                <ui-dropdown-menu-item>\n                                  <lucide-icon [img]=\"FileText\" class=\"mr-2 size-3.5\" />\n                                  <span>View Audit Log</span>\n                                </ui-dropdown-menu-item>\n                              </ui-dropdown-menu-content>\n                            </ui-dropdown-menu>\n                          }\n                        </div>\n                      </td>\n                    </tr>\n                  }\n                }\n              </tbody>\n            </table>\n          </div>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiAccessRequestWorkflowComponent {\n  protected readonly Check = Check\n  protected readonly Clock = Clock\n  protected readonly Copy = Copy\n  protected readonly FileText = FileText\n  protected readonly History = History\n  protected readonly Hourglass = Hourglass\n  protected readonly KeyRound = KeyRound\n  protected readonly Lock = Lock\n  protected readonly MoreHorizontal = MoreHorizontal\n  protected readonly Plus = Plus\n  protected readonly Search = Search\n  protected readonly Send = Send\n  protected readonly ShieldAlert = ShieldAlert\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly UserCheck = UserCheck\n  protected readonly X = X\n\n  @Input() title = 'Access Requests & Privilege Elevation'\n  @Input() subtitle = 'Request temporary Just-In-Time production access with audit approval.'\n  @Input('class') className?: string\n\n  readonly requests = signal<AccessRequest[]>(initialRequests.map((r) => ({ ...r, requester: { ...r.requester } })))\n\n  readonly selectedTab = signal<AccessRequestTab>('all')\n  readonly searchQuery = signal('')\n\n  readonly isDialogOpen = signal(false)\n  readonly formRole = signal('AWS Production Admin')\n  readonly formDuration = signal('4 Hours')\n  readonly formApprover = signal('Security Lead: Marcus Vance')\n  readonly formJustification = signal('Investigating latency spike on payment gateway for incident #SEC-849')\n\n  readonly copiedId = signal<string | null>(null)\n  private copyTimer: ReturnType<typeof setTimeout> | undefined\n\n  constructor() {\n    const destroyRef = inject(DestroyRef)\n    const timer = setInterval(() => this.tick(), 1000)\n    destroyRef.onDestroy(() => {\n      clearInterval(timer)\n      clearTimeout(this.copyTimer)\n    })\n  }\n\n  readonly activeCount = computed(() => this.requests().filter((r) => r.status === 'active').length)\n  readonly pendingCount = computed(() => this.requests().filter((r) => r.status === 'pending').length)\n  readonly expiredTabCount = computed(\n    () => this.requests().filter((r) => r.status === 'expired' || r.status === 'rejected').length,\n  )\n  readonly expiringCount = computed(\n    () => this.requests().filter((r) => r.status === 'active' && r.secondsLeft <= 3600 * 4).length,\n  )\n  readonly total30Day = computed(() => 42 + this.requests().filter((r) => r.id.startsWith('req-custom')).length)\n\n  readonly filteredRequests = computed(() => {\n    const tab = this.selectedTab()\n    const q = this.searchQuery().trim().toLowerCase()\n    return this.requests().filter((req) => {\n      if (tab === 'pending' || tab === 'active') {\n        if (req.status !== tab) return false\n      } else if (tab === 'expired') {\n        if (req.status !== 'expired' && req.status !== 'rejected') return false\n      }\n      if (q) {\n        const hay = [req.requester.name, req.targetRole, req.ticketId, req.justification, req.system]\n          .join(' ')\n          .toLowerCase()\n        if (!hay.includes(q)) return false\n      }\n      return true\n    })\n  })\n\n  get rootClass(): string {\n    return cn('flex flex-col gap-6', this.className)\n  }\n\n  tabClass(tab: AccessRequestTab): string {\n    return cn(\n      'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',\n      this.selectedTab() === tab\n        ? 'bg-background text-foreground shadow-xs'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  setSelectedTab(tab: AccessRequestTab): void {\n    this.selectedTab.set(tab)\n  }\n\n  formatSeconds(seconds: number): string {\n    return formatSecondsValue(seconds)\n  }\n\n  roleBadgeVariant(role: string): BadgeVariant {\n    if (role.includes('AWS') || role.includes('Admin')) return 'destructive'\n    if (role.includes('PII') || role.includes('Decryption')) return 'warning'\n    if (role.includes('DB') || role.includes('Read-Only')) return 'info'\n    if (role.includes('Stripe')) return 'secondary'\n    return 'default'\n  }\n\n  shortApprover(approver: string): string {\n    return approver\n      .replace('Security Lead: ', '')\n      .replace('Infrastructure Team: ', '')\n      .replace('Compliance Officer: ', '')\n      .replace('On-Call Lead: ', '')\n  }\n\n  tick(): void {\n    this.requests.update((prev) =>\n      prev.map((req) => {\n        if (req.status === 'active' && req.secondsLeft > 0) {\n          const nextSec = req.secondsLeft - 1\n          if (nextSec <= 0) {\n            return { ...req, secondsLeft: 0, status: 'expired' as AccessRequestStatus, timeLeftLabel: 'Expired' }\n          }\n          return { ...req, secondsLeft: nextSec, timeLeftLabel: timeLeftLabelFor(nextSec) }\n        }\n        return req\n      }),\n    )\n  }\n\n  handleCreateRequest(event: SubmitEvent): void {\n    event.preventDefault()\n    if (!this.formJustification().trim()) return\n\n    const duration = this.formDuration()\n    let seconds = 3600 * 4\n    if (duration.includes('1 Hour')) seconds = 3600\n    else if (duration.includes('8 Hours')) seconds = 3600 * 8\n    else if (duration.includes('24 Hours')) seconds = 3600 * 24\n\n    const role = this.formRole()\n    let system = 'AWS Cloud IAM (us-east-1)'\n    if (role.includes('DB')) system = 'PostgreSQL Aurora Primary'\n    else if (role.includes('Stripe')) system = 'Stripe Merchant Gateway'\n    else if (role.includes('PII')) system = 'Customer Data Vault (KMS)'\n\n    const newReq: AccessRequest = {\n      id: `req-custom-${Date.now()}`,\n      requester: {\n        name: 'Current Operator (You)',\n        email: 'operator@acme.corp',\n        team: 'Platform Engineering',\n        avatar: 'OP',\n      },\n      targetRole: role,\n      system,\n      duration,\n      timeLeftLabel: 'Pending Approval',\n      secondsLeft: seconds,\n      justification: this.formJustification(),\n      ticketId: `SEC-${Math.floor(100 + Math.random() * 900)}`,\n      approver: this.formApprover(),\n      status: 'pending',\n      createdAt: 'Just now',\n      expiresAt: 'Awaiting approval',\n    }\n\n    this.requests.update((prev) => [newReq, ...prev])\n    this.isDialogOpen.set(false)\n  }\n\n  handleApprove(id: string): void {\n    this.requests.update((prev) =>\n      prev.map((req) =>\n        req.id === id ? { ...req, status: 'active' as const, timeLeftLabel: timeLeftLabelFor(req.secondsLeft) } : req,\n      ),\n    )\n  }\n\n  handleReject(id: string): void {\n    this.requests.update((prev) =>\n      prev.map((req) => (req.id === id ? { ...req, status: 'rejected' as const, timeLeftLabel: 'Rejected' } : req)),\n    )\n  }\n\n  handleRevoke(id: string): void {\n    this.requests.update((prev) =>\n      prev.map((req) =>\n        req.id === id ? { ...req, status: 'expired' as const, secondsLeft: 0, timeLeftLabel: 'Revoked' } : req,\n      ),\n    )\n  }\n\n  handleExtend(id: string): void {\n    this.requests.update((prev) =>\n      prev.map((req) => {\n        if (req.id === id && req.status === 'active') {\n          const nextSec = req.secondsLeft + 1800\n          return { ...req, secondsLeft: nextSec, timeLeftLabel: timeLeftLabelFor(nextSec) }\n        }\n        return req\n      }),\n    )\n  }\n\n  handleReRequest(id: string): void {\n    this.requests.update((prev) =>\n      prev.map((req) =>\n        req.id === id\n          ? { ...req, status: 'pending' as const, timeLeftLabel: 'Pending Approval', secondsLeft: 3600 * 4 }\n          : req,\n      ),\n    )\n  }\n\n  handleCopyToken(id: string): void {\n    this.copiedId.set(id)\n    clearTimeout(this.copyTimer)\n    this.copyTimer = setTimeout(() => {\n      if (this.copiedId() === id) this.copiedId.set(null)\n    }, 2000)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/access-request-workflow/access-request-workflow.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/access-request-workflow/index.ts",
      "content": "export {\n  UiAccessRequestWorkflowComponent,\n  type AccessRequest,\n  type AccessRequester,\n  type AccessRequestStatus,\n  type AccessRequestTab,\n} from './access-request-workflow.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/access-request-workflow/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/dialog.json",
    "https://uipkge.dev/r/angular/dropdown-menu.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/select.json",
    "https://uipkge.dev/r/angular/table.json",
    "https://uipkge.dev/r/angular/textarea.json"
  ],
  "description": "Just-In-Time (JIT) IAM temporary privilege escalation request modal and approval queue: governance metrics cards, elevated session countdown timers, approval form dialog, and active session manager with instant revoke and audit logging.",
  "categories": [
    "security",
    "iam",
    "dashboard"
  ]
}