Onboarding Equipment Provisioning
blockhrEnterprise IT hardware, peripherals, and software license provisioning portal for new hires with primary workstation selection, display and peripheral bundles, security keys, cloud SaaS seats, and courier tracking.
Installation
$pnpm dlx uipkge-ng@latest add onboarding-equipment-provisioning$npx uipkge-ng@latest add onboarding-equipment-provisioning$yarn dlx uipkge-ng@latest add onboarding-equipment-provisioning$bunx uipkge-ng@latest add onboarding-equipment-provisioningManifest:
https://uipkge.dev/r/angular/onboarding-equipment-provisioning.jsonInstalls to:src/app/components/blocks/onboarding-equipment-provisioning/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
Files installed (2)
src/app/components/blocks/onboarding-equipment-provisioning/onboarding-equipment-provisioning.component.ts41.5 kB
// Enterprise IT hardware, peripherals, and software license provisioning portal for // new hires: workstation selection, display/peripheral bundles, security keys, SaaS // seats, courier tracking, and a custom hardware order dialog. Port of the Vue/React // block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, computed, signal } from '@angular/core' import { Boxes, Calendar, Check, CheckCircle2, Circle, Copy, DollarSign, Download, Laptop, Layers, Lock, LucideAngularModule, Monitor, Plus, Send, ShieldCheck, Truck, X, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiCheckboxComponent } from '@/ui/checkbox/checkbox.component' import { UiRadioGroupComponent, UiRadioGroupItemComponent } from '@/ui/radio-group/radio-group.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' export type WorkstationChoice = 'macbook' | 'thinkpad' const WORKSTATION_PRICES: Record<WorkstationChoice, number> = { macbook: 3499, thinkpad: 2850, } export const softwareLicenses = [ { name: 'GitHub Enterprise', tier: 'Copilot Enterprise + CodeQL', seatId: 'GH-ENG-8402', category: 'Engineering', status: 'Provisioned', sso: 'Okta SCIM', }, { name: 'Figma Professional', tier: 'Design System Editor + Dev Mode', seatId: 'FG-DS-1940', category: 'Design & UI', status: 'Provisioned', sso: 'Okta SCIM', }, { name: '1Password Business', tier: 'Enterprise Vault + Infra Keys', seatId: '1P-CORP-9921', category: 'Security', status: 'Provisioned', sso: 'MDM Enrolled', }, { name: 'Slack Pro', tier: 'Enterprise Grid (acme-eng)', seatId: 'SLK-88301', category: 'Comms', status: 'Provisioned', sso: 'SAML 2.0', }, { name: 'Datadog APM', tier: 'Staff Observability & Traces', seatId: 'DD-APM-4109', category: 'DevOps', status: 'Provisioned', sso: 'Okta SCIM', }, { name: 'JetBrains All Products', tier: 'Ultimate Suite (IntelliJ/CLion/GoLand)', seatId: 'JB-ALL-7740', category: 'Engineering', status: 'Provisioned', sso: 'Corp Pool', }, ] export const trackingSteps = [ { title: 'Order Approved', date: 'Aug 24, 2026', time: '09:00 AM', completed: true, current: false }, { title: 'IT Staged & Imaged', date: 'Aug 25, 2026', time: '02:30 PM', completed: true, current: false }, { title: 'Shipped via FedEx', date: 'Aug 26, 2026', time: '06:15 PM', completed: true, current: true }, { title: 'Out for Delivery', date: 'Aug 28, 2026', time: 'Est. 08:00 AM', completed: false, current: false }, { title: 'Delivered to Home', date: 'Aug 28, 2026', time: 'Est. 10:30 AM', completed: false, current: false }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-onboarding-equipment-provisioning, [ui-onboarding-equipment-provisioning]', standalone: true, // The block renders its own root <div> (and the dialog portals visually): keep the host out of layout. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, UiCheckboxComponent, UiRadioGroupComponent, UiRadioGroupItemComponent, UiSeparatorComponent, ], template: ` <div data-slot="onboarding-equipment-provisioning" [class]="rootClass"> <!-- Header Section --> <div class="border-border bg-card flex flex-col gap-4 rounded-xl border p-5 shadow-xs sm:p-6"> <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between"> <!-- Title & Hire Info --> <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2"> <span ui-badge variant="outline" class="gap-1.5 font-mono text-xs"> <lucide-icon [img]="Boxes" class="text-primary size-3.5" aria-hidden="true" /> IT Asset Provisioning </span> <div class="border-success/20 bg-success/10 text-success inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium" > <span class="relative flex size-1.5"> <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span> <span class="bg-success relative inline-flex size-1.5 rounded-full"></span> </span> Hardware Assigned · Shipped via FedEx </div> </div> <h1 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl"> New Hire Hardware & IT Provisioning </h1> <!-- New Hire Metadata Line --> <div class="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs sm:text-sm"> <span class="text-foreground font-semibold">Elena Rostova</span> <span class="text-muted-foreground hidden sm:inline">•</span> <span class="text-muted-foreground">Senior Staff Engineer</span> <span class="text-muted-foreground hidden sm:inline">•</span> <span class="text-muted-foreground flex items-center gap-1"> <lucide-icon [img]="Calendar" class="size-3.5" aria-hidden="true" /> Start Date: <strong class="text-foreground font-medium">Sep 01, 2026</strong> </span> <span class="text-muted-foreground hidden sm:inline">•</span> <button type="button" class="hover:bg-muted focus-visible:ring-ring border-border bg-muted/40 text-foreground inline-flex min-h-6 items-center gap-1 rounded border px-1.5 py-0.5 font-mono text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none" title="Click to copy employee ID" (click)="copyEmployeeId()" > EMP-88291 @if (copiedEmpId()) { <lucide-icon [img]="Check" class="text-success size-3" aria-hidden="true" /> } @else { <lucide-icon [img]="Copy" class="text-muted-foreground size-3" aria-hidden="true" /> } </button> </div> </div> <!-- Action Buttons --> <div class="flex flex-wrap items-center gap-2 pt-1 lg:pt-0"> <button ui-button aria-label="Download attachment" variant="outline" size="sm" class="gap-1.5 text-xs shadow-xs" (click)="handleExportManifest()" > <lucide-icon [img]="Download" class="size-3.5" aria-hidden="true" /> <span>Export Manifest</span> </button> <button ui-button size="sm" class="gap-1.5 text-xs shadow-xs" (click)="handleOpenCustomOrder()"> <lucide-icon [img]="Plus" class="size-3.5" aria-hidden="true" /> <span>Order Custom Hardware</span> </button> </div> </div> <!-- Financial & Overview Summary Band --> <ui-separator /> <div class="grid grid-cols-2 gap-4 pt-1 sm:grid-cols-4"> <div class="border-border/60 bg-muted/20 rounded-lg border p-3"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs"> <lucide-icon [img]="DollarSign" class="text-primary size-3.5" aria-hidden="true" /> <span>Total IT Investment</span> </div> <p class="text-foreground mt-1 font-mono text-lg font-bold tabular-nums sm:text-xl"> ${{ formatTotal(totalInvestment()) }} </p> <p class="text-muted-foreground text-xs">Total Assigned Value</p> </div> <div class="border-border/60 bg-muted/20 rounded-lg border p-3"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs"> <lucide-icon [img]="Laptop" class="text-primary size-3.5" aria-hidden="true" /> <span>Primary Workstation</span> </div> <p class="text-foreground mt-1 truncate text-xs font-semibold sm:text-sm"> {{ selectedWorkstation() === 'macbook' ? 'MacBook Pro 16" M3 Max' : 'ThinkPad P1 Gen 6 i9' }} </p> <p class="text-muted-foreground font-mono text-xs tabular-nums">${{ workstationCost().toFixed(2) }}</p> </div> <div class="border-border/60 bg-muted/20 rounded-lg border p-3"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs"> <lucide-icon [img]="Layers" class="text-primary size-3.5" aria-hidden="true" /> <span>Cloud SaaS Seats</span> </div> <p class="text-foreground mt-1 text-xs font-semibold sm:text-sm">6 Enterprise Licenses</p> <p class="text-success text-xs font-medium">All Provisioned</p> </div> <div class="border-border/60 bg-muted/20 rounded-lg border p-3"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs"> <lucide-icon [img]="Truck" class="text-primary size-3.5" aria-hidden="true" /> <span>FedEx Priority</span> </div> <p class="text-foreground mt-1 text-xs font-semibold sm:text-sm">Est. Aug 28, 2026</p> <p class="text-muted-foreground font-mono text-xs">#TRK-8492019</p> </div> </div> </div> <!-- 4 IT Provisioning Pillar Cards Grid --> <div class="grid grid-cols-1 gap-6 lg:grid-cols-2"> <!-- Pillar 1: Primary Workstation Selection --> <div ui-card class="border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2"> <div class="bg-primary/10 text-primary rounded-lg p-2"> <lucide-icon [img]="Laptop" class="size-4" aria-hidden="true" /> </div> <div> <h3 ui-card-title class="text-base font-semibold">1. Primary Workstation</h3> <p ui-card-description>Select standard issue macOS or Linux developer laptop</p> </div> </div> <span ui-badge variant="outline" class="font-mono text-xs">Pillar 01</span> </div> </div> <div ui-card-content class="space-y-3"> <ui-radio-group [value]="selectedWorkstation()" (valueChange)="selectedWorkstation.set($any($event))" class="grid grid-cols-1 gap-3" > <!-- Choice A: MacBook Pro 16" --> <label [class]="workstationLabelClass('macbook')"> <div class="flex flex-wrap items-start justify-between gap-3"> <div class="flex min-w-0 items-start gap-3"> <ui-radio-group-item id="ws-macbook" value="macbook" class="mt-1" /> <div class="min-w-0 space-y-1"> <div class="flex flex-wrap items-center gap-2"> <span class="text-foreground text-sm font-bold"> MacBook Pro 16" (M3 Max · 64GB RAM · 1TB SSD) </span> <span ui-badge variant="default" class="text-xs">Selected Spec</span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> Apple M3 Max (16-core CPU, 40-core GPU) • 64GB Unified Memory • 1TB Fast NVMe • Liquid Retina XDR (Space Black) </p> <div class="flex flex-wrap items-center gap-2 pt-1"> <span ui-badge variant="secondary" class="font-mono text-xs"> <lucide-icon [img]="ShieldCheck" class="text-success mr-1 size-3" aria-hidden="true" /> AppleCare+ Enterprise (3-Year Full Accidental Damage) </span> </div> </div> </div> <div class="text-right"> <span class="text-foreground font-mono text-sm font-bold tabular-nums">$3,499.00</span> </div> </div> </label> <!-- Choice B: ThinkPad P1 Gen 6 --> <label [class]="workstationLabelClass('thinkpad')"> <div class="flex flex-wrap items-start justify-between gap-3"> <div class="flex min-w-0 items-start gap-3"> <ui-radio-group-item id="ws-thinkpad" value="thinkpad" class="mt-1" /> <div class="min-w-0 space-y-1"> <div class="flex flex-wrap items-center gap-2"> <span class="text-foreground text-sm font-bold"> ThinkPad P1 Gen 6 (Intel i9 · 64GB RAM · Ubuntu Linux) </span> <span ui-badge variant="outline" class="text-xs">Linux Spec</span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> Intel Core i9-13900H • NVIDIA RTX 4080 (12GB) • 64GB DDR5 • 1TB NVMe • 16" WQXGA OLED • Ubuntu 24.04 LTS </p> <div class="flex flex-wrap items-center gap-2 pt-1"> <span ui-badge variant="secondary" class="font-mono text-xs"> <lucide-icon [img]="ShieldCheck" class="text-success mr-1 size-3" aria-hidden="true" /> Lenovo Premier Support (3-Year Next-Day Onsite) </span> </div> </div> </div> <div class="text-right"> <span class="text-foreground font-mono text-sm font-bold tabular-nums">$2,850.00</span> </div> </div> </label> </ui-radio-group> </div> <div ui-card-footer class="border-border/60 bg-muted/20 flex items-center justify-between border-t py-2.5 text-xs" > <span class="text-muted-foreground">Workstation Subtotal:</span> <span class="text-foreground font-mono font-bold tabular-nums" >${{ workstationCost().toFixed(2) }}</span > </div> </div> <!-- Pillar 2: Display & Peripherals --> <div ui-card class="border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2"> <div class="bg-primary/10 text-primary rounded-lg p-2"> <lucide-icon [img]="Monitor" class="size-4" aria-hidden="true" /> </div> <div> <h3 ui-card-title class="text-base font-semibold">2. Display & Peripherals</h3> <p ui-card-description>High-resolution retina display and ergonomic peripherals</p> </div> </div> <span ui-badge variant="outline" class="font-mono text-xs">Pillar 02</span> </div> </div> <div ui-card-content class="space-y-3"> <div [class]="peripheralRowClass(includeDisplay())"> <div class="flex items-start gap-3"> <ui-checkbox id="periph-display" [checked]="includeDisplay()" (checkedChange)="includeDisplay.set(!!$event)" class="mt-0.5" /> <div class="space-y-0.5"> <label for="periph-display" class="text-foreground cursor-pointer text-sm font-semibold"> Studio Display 27" 5K Retina </label> <p class="text-muted-foreground text-xs"> 5120×2880 Retina • 600 nits • 12MP Center Stage Camera • Studio Mics • Tilt-adjustable Stand </p> <div class="pt-1"> <span ui-badge variant="secondary" class="text-xs">Thunderbolt 3 Host Pass-Through</span> </div> </div> </div> <span class="text-foreground font-mono text-sm font-bold tabular-nums">$1,499.00</span> </div> <div [class]="peripheralRowClass(includeInputBundle())"> <div class="flex items-start gap-3"> <ui-checkbox id="periph-inputs" [checked]="includeInputBundle()" (checkedChange)="includeInputBundle.set(!!$event)" class="mt-0.5" /> <div class="space-y-0.5"> <label for="periph-inputs" class="text-foreground cursor-pointer text-sm font-semibold"> Magic Keyboard & Trackpad Bundle </label> <p class="text-muted-foreground text-xs"> Touch ID Wireless Keyboard • Force Touch Multi-Touch Glass Trackpad • Space Black finish </p> <div class="pt-1"> <span ui-badge variant="secondary" class="text-xs">USB-C Braided Woven Cable</span> </div> </div> </div> <span class="text-foreground font-mono text-sm font-bold tabular-nums">$249.00</span> </div> <div [class]="peripheralRowClass(includeDeskMat())"> <div class="flex items-start gap-3"> <ui-checkbox id="periph-mat" [checked]="includeDeskMat()" (checkedChange)="includeDeskMat.set(!!$event)" class="mt-0.5" /> <div class="space-y-0.5"> <label for="periph-mat" class="text-foreground cursor-pointer text-sm font-semibold"> Ergonomic Standing Desk Mat </label> <p class="text-muted-foreground text-xs"> 24" × 36" Anti-fatigue high-density polyurethane core • Contoured active standing terrain </p> </div> </div> <span class="text-foreground font-mono text-sm font-bold tabular-nums">$49.00</span> </div> </div> <div ui-card-footer class="border-border/60 bg-muted/20 flex items-center justify-between border-t py-2.5 text-xs" > <span class="text-muted-foreground">Peripherals Subtotal:</span> <span class="text-foreground font-mono font-bold tabular-nums" >${{ peripheralsTotal().toFixed(2) }}</span > </div> </div> <!-- Pillar 3: Security & Authentication Hardware --> <div ui-card class="border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2"> <div class="bg-primary/10 text-primary rounded-lg p-2"> <lucide-icon [img]="ShieldCheck" class="size-4" aria-hidden="true" /> </div> <div> <h3 ui-card-title class="text-base font-semibold">3. Security & Authentication Hardware</h3> <p ui-card-description>Zero-trust physical tokens and cryptographic hardware keys</p> </div> </div> <span ui-badge variant="outline" class="font-mono text-xs">Pillar 03</span> </div> </div> <div ui-card-content class="space-y-3"> <div [class]="peripheralRowClass(includeYubikey())"> <div class="flex items-start gap-3"> <ui-checkbox id="sec-yubikey" [checked]="includeYubikey()" (checkedChange)="includeYubikey.set(!!$event)" class="mt-0.5" /> <div class="space-y-0.5"> <div class="flex flex-wrap items-center gap-2"> <label for="sec-yubikey" class="text-foreground cursor-pointer text-sm font-semibold"> YubiKey 5C NFC (2-Pack · Primary & Backup) </label> <span ui-badge variant="outline" class="font-mono text-xs">FIDO2 / WebAuthn</span> </div> <p class="text-muted-foreground text-xs"> Dual-key pair for primary keychain and secure home backup • USB-C • Contactless NFC • IP68 water resistant </p> <p class="text-muted-foreground pt-0.5 font-mono text-xs"> Pre-enrolled Serials: <span class="text-foreground font-medium">#YB-90281-A / #YB-90281-B</span> </p> </div> </div> <span class="text-foreground font-mono text-sm font-bold tabular-nums">$75.00</span> </div> <div [class]="peripheralRowClass(includeHardwareToken())"> <div class="flex items-start gap-3"> <ui-checkbox id="sec-token" [checked]="includeHardwareToken()" (checkedChange)="includeHardwareToken.set(!!$event)" class="mt-0.5" /> <div class="space-y-0.5"> <div class="flex flex-wrap items-center gap-2"> <label for="sec-token" class="text-foreground cursor-pointer text-sm font-semibold"> Encrypted Hardware Token </label> <span ui-badge variant="outline" class="font-mono text-xs">FIPS 140-3 Level 3</span> </div> <p class="text-muted-foreground text-xs"> AES-256 hardware cryptographic security token with PIN pad • Corporate Root CA Certificate pre-loaded </p> <p class="text-muted-foreground pt-0.5 font-mono text-xs"> Device Serial: <span class="text-foreground font-medium">#ET-78392-X</span> </p> </div> </div> <span class="text-foreground font-mono text-sm font-bold tabular-nums">$49.00</span> </div> <div class="border-border/60 bg-muted/20 flex items-center justify-between rounded-lg border px-3 py-2 text-xs" > <div class="text-muted-foreground flex items-center gap-1.5"> <lucide-icon [img]="Lock" class="text-success size-3.5" aria-hidden="true" /> <span>SOC 2 Type II & FedRAMP High compliant hardware assignment</span> </div> <span class="text-success font-medium">Enforced</span> </div> </div> <div ui-card-footer class="border-border/60 bg-muted/20 flex items-center justify-between border-t py-2.5 text-xs" > <span class="text-muted-foreground">Security Hardware Subtotal:</span> <span class="text-foreground font-mono font-bold tabular-nums">${{ securityTotal().toFixed(2) }}</span> </div> </div> <!-- Pillar 4: Cloud Software & License Seats --> <div ui-card class="border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2"> <div class="bg-primary/10 text-primary rounded-lg p-2"> <lucide-icon [img]="Layers" class="size-4" aria-hidden="true" /> </div> <div> <h3 ui-card-title class="text-base font-semibold">4. Cloud Software & License Seats</h3> <p ui-card-description>6 assigned SaaS enterprise licenses auto-synced via Okta</p> </div> </div> <span ui-badge variant="outline" class="font-mono text-xs">Pillar 04</span> </div> </div> <div ui-card-content> <div class="grid grid-cols-1 gap-2.5 sm:grid-cols-2"> @for (license of licenses; track license.name) { <div class="border-border bg-card flex flex-col justify-between rounded-lg border p-3 shadow-xs"> <div class="space-y-1"> <div class="flex items-start justify-between gap-1.5"> <span class="text-foreground text-xs font-bold">{{ license.name }}</span> <div class="border-success/20 bg-success/10 text-success inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-xs font-medium" > <lucide-icon [img]="Check" class="size-3" aria-hidden="true" /> <span>{{ license.status }}</span> </div> </div> <p class="text-muted-foreground text-xs leading-tight">{{ license.tier }}</p> </div> <div class="border-border/60 mt-2 flex items-center justify-between border-t pt-1.5 text-xs"> <span class="text-muted-foreground font-mono text-xs">{{ license.seatId }}</span> <span ui-badge variant="secondary" class="font-mono text-xs">{{ license.sso }}</span> </div> </div> } </div> </div> <div ui-card-footer class="border-border/60 bg-muted/20 flex items-center justify-between border-t py-2.5 text-xs" > <span class="text-muted-foreground">Software Licensing Cost:</span> <span class="text-foreground font-mono font-bold tabular-nums">Enterprise Corp Pool ($0.00 Direct)</span> </div> </div> </div> <!-- Shipping & Delivery Tracking Card --> <div ui-card class="border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div class="flex items-center gap-2"> <div class="bg-primary/10 text-primary rounded-lg p-2"> <lucide-icon [img]="Truck" class="size-4" aria-hidden="true" /> </div> <div> <h3 ui-card-title class="text-base font-semibold">Shipping & Delivery Tracking</h3> <p ui-card-description>Direct-to-home courier transit with signature required on delivery</p> </div> </div> <div class="flex items-center gap-2"> <span ui-badge variant="outline" class="font-mono text-xs">Carrier: FedEx Priority</span> <button type="button" class="hover:bg-muted focus-visible:ring-ring border-border bg-muted/40 text-foreground inline-flex min-h-6 items-center gap-1.5 rounded border px-2 py-1 font-mono text-xs font-semibold transition-colors focus-visible:ring-2 focus-visible:outline-none" title="Click to copy tracking number" (click)="copyTrackingNumber()" > #TRK-8492019 @if (copiedTracking()) { <lucide-icon [img]="Check" class="text-success size-3" aria-hidden="true" /> } @else { <lucide-icon [img]="Copy" class="text-muted-foreground size-3" aria-hidden="true" /> } </button> </div> </div> </div> <div ui-card-content class="space-y-6"> <!-- Address & Courier Info Banner --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"> <div class="border-border bg-muted/20 space-y-1 rounded-lg border p-3.5"> <span class="text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase"> Shipment Recipient </span> <p class="text-foreground text-sm font-bold">Elena Rostova</p> <p class="text-muted-foreground text-xs">Senior Staff Engineer • Distributed Systems</p> <p class="text-muted-foreground text-xs">elena.rostova@acme.corp</p> </div> <div class="border-border bg-muted/20 space-y-1 rounded-lg border p-3.5"> <span class="text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase"> Destination Address </span> <p class="text-foreground text-sm font-bold">4820 Grand Avenue, Apt 14B</p> <p class="text-muted-foreground text-xs">Seattle, WA 98101-2294</p> <p class="text-muted-foreground text-xs">Direct In-Person Signature Required</p> </div> <div class="border-border bg-muted/20 space-y-1 rounded-lg border p-3.5 sm:col-span-2 lg:col-span-1"> <span class="text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase"> Estimated Delivery </span> <p class="text-foreground text-sm font-bold">Thursday, Aug 28, 2026</p> <p class="text-success text-xs font-medium">Estimated by 10:30 AM (Morning Delivery)</p> <p class="text-muted-foreground text-xs">FedEx Express Priority Overnight</p> </div> </div> <!-- 5-Step Progress Stepper --> <div class="space-y-2 pt-2"> <span class="text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase"> Logistics Timeline Status </span> <div class="grid grid-cols-1 gap-3 sm:grid-cols-5"> @for (step of steps; track step.title; let idx = $index) { <div [class]="stepCardClass(step)"> <div class="flex items-center justify-between gap-2"> <span class="text-muted-foreground font-mono text-xs">0{{ idx + 1 }}</span> @if (step.completed && !step.current) { <lucide-icon [img]="CheckCircle2" class="text-success size-4" aria-hidden="true" /> } @else if (step.current) { <span class="relative flex size-2"> <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span> <span class="bg-success relative inline-flex size-2 rounded-full"></span> </span> } @else { <lucide-icon [img]="Circle" class="text-muted-foreground/50 size-3.5" aria-hidden="true" /> } </div> <div class="mt-2 space-y-0.5"> <p [class]="stepTitleClass(step)">{{ step.title }}</p> <p class="text-muted-foreground font-mono text-xs">{{ step.date }}</p> <p class="text-muted-foreground text-xs">{{ step.time }}</p> </div> </div> } </div> </div> </div> </div> <!-- Custom Hardware Order Modal Dialog --> @if (showCustomOrderModal()) { <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-xs" role="dialog" aria-modal="true" aria-labelledby="custom-hardware-title" > <div class="border-border bg-card animate-in fade-in zoom-in-95 w-full max-w-lg space-y-4 rounded-xl border p-6 shadow-xl" > <div class="flex items-center justify-between"> <div class="flex items-center gap-2"> <div class="bg-primary/10 text-primary rounded-lg p-2"> <lucide-icon [img]="Laptop" class="size-4" aria-hidden="true" /> </div> <div> <h2 id="custom-hardware-title" class="text-foreground text-base font-bold">Order Custom Hardware</h2> <p class="text-muted-foreground text-xs"> Request specialized hardware or accessories for Elena Rostova </p> </div> </div> <button ui-button variant="ghost" size="icon" class="size-8" aria-label="Close dialog" (click)="handleCloseCustomOrder()" > <lucide-icon [img]="X" class="size-4" aria-hidden="true" /> </button> </div> <ui-separator /> @if (!customRequestSubmitted()) { <div class="space-y-4"> <div class="space-y-1.5"> <label class="text-foreground text-xs font-semibold">Equipment / Item Name</label> <input [value]="customItemName()" (input)="customItemName.set($any($event.target).value)" type="text" placeholder="e.g. Ergonomic Split Mechanical Keyboard, 4K Webcam, eGPU" class="border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 w-full rounded-md border px-3 py-2 text-xs shadow-xs outline-none focus-visible:ring-[3px]" /> </div> <div class="space-y-1.5"> <label class="text-foreground text-xs font-semibold">Business Justification</label> <textarea [value]="customItemJustification()" (input)="customItemJustification.set($any($event.target).value)" rows="3" placeholder="Provide engineering justification or project requirements..." class="border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 w-full resize-none rounded-md border px-3 py-2 text-xs shadow-xs outline-none focus-visible:ring-[3px]" ></textarea> </div> <div class="border-border bg-muted/20 text-muted-foreground rounded-lg border p-3 text-xs"> <p> Custom requests are automatically routed to Engineering Management & IT Procurement for budget sign-off. </p> </div> <div class="flex items-center justify-end gap-2 pt-2"> <button ui-button aria-label="Close dialog" variant="outline" size="sm" class="text-xs" (click)="handleCloseCustomOrder()" > Cancel </button> <button ui-button size="sm" class="gap-1.5 text-xs" [disabled]="!customItemName().trim()" (click)="submitCustomRequest()" > <lucide-icon [img]="Send" class="size-3.5" aria-hidden="true" /> <span>Submit Request</span> </button> </div> </div> } @else { <div class="space-y-3 py-4 text-center"> <div class="bg-success/10 text-success mx-auto flex size-12 items-center justify-center rounded-full"> <lucide-icon [img]="CheckCircle2" class="size-6" aria-hidden="true" /> </div> <h3 class="text-foreground text-sm font-bold">Custom Hardware Request Submitted</h3> <p class="text-muted-foreground text-xs"> Request for <strong class="text-foreground">{{ customItemName() }}</strong> has been forwarded to IT Procurement. </p> </div> } </div> </div> } </div> `, }) export class UiOnboardingEquipmentProvisioningComponent { protected readonly Boxes = Boxes protected readonly Calendar = Calendar protected readonly Check = Check protected readonly CheckCircle2 = CheckCircle2 protected readonly Circle = Circle protected readonly Copy = Copy protected readonly DollarSign = DollarSign protected readonly Download = Download protected readonly Laptop = Laptop protected readonly Layers = Layers protected readonly Lock = Lock protected readonly Monitor = Monitor protected readonly Plus = Plus protected readonly Send = Send protected readonly ShieldCheck = ShieldCheck protected readonly Truck = Truck protected readonly X = X protected readonly licenses = softwareLicenses protected readonly steps = trackingSteps @Input() set initialWorkstation(v: WorkstationChoice) { this.selectedWorkstation.set(v ?? 'macbook') } @Input('class') className?: string /** Fired when the custom-hardware dialog opens (React `onOrderCustom`, Vue `order-custom`). */ @Output() readonly orderCustom = new EventEmitter<void>() /** Fired when Export Manifest is clicked (React `onExportManifest`, Vue `export-manifest`). */ @Output() readonly exportManifest = new EventEmitter<void>() readonly selectedWorkstation = signal<WorkstationChoice>('macbook') readonly includeDisplay = signal(true) readonly includeInputBundle = signal(true) readonly includeDeskMat = signal(true) readonly includeYubikey = signal(true) readonly includeHardwareToken = signal(true) readonly copiedTracking = signal(false) readonly copiedEmpId = signal(false) readonly showCustomOrderModal = signal(false) readonly customRequestSubmitted = signal(false) readonly customItemName = signal('') readonly customItemJustification = signal('') readonly workstationCost = computed(() => WORKSTATION_PRICES[this.selectedWorkstation()]) readonly displayCost = computed(() => (this.includeDisplay() ? 1499 : 0)) readonly inputBundleCost = computed(() => (this.includeInputBundle() ? 249 : 0)) readonly deskMatCost = computed(() => (this.includeDeskMat() ? 49 : 0)) readonly yubikeyCost = computed(() => (this.includeYubikey() ? 75 : 0)) readonly hardwareTokenCost = computed(() => (this.includeHardwareToken() ? 49 : 0)) readonly peripheralsTotal = computed(() => this.displayCost() + this.inputBundleCost() + this.deskMatCost()) readonly securityTotal = computed(() => this.yubikeyCost() + this.hardwareTokenCost()) readonly totalInvestment = computed(() => this.workstationCost() + this.peripheralsTotal() + this.securityTotal()) get rootClass(): string { return cn('w-full space-y-6', this.className) } workstationLabelClass(choice: WorkstationChoice): string { return cn( 'relative flex cursor-pointer flex-col justify-between rounded-lg border p-4 transition-colors', this.selectedWorkstation() === choice ? 'border-primary bg-primary/5 ring-primary shadow-xs ring-1' : 'border-border bg-card hover:bg-muted/40', ) } peripheralRowClass(included: boolean): string { return cn( 'border-border flex flex-wrap items-start justify-between gap-3 rounded-lg border p-3.5 transition-colors', included ? 'bg-card' : 'bg-muted/30 opacity-70', ) } stepCardClass(step: { completed: boolean; current: boolean }): string { return cn( 'border-border flex flex-col justify-between rounded-lg border p-3 transition-colors', step.current ? 'border-success/40 bg-success/10 ring-success/30 ring-1' : step.completed ? 'bg-card' : 'bg-muted/20 opacity-60', ) } stepTitleClass(step: { completed: boolean; current: boolean }): string { return cn( 'text-xs font-bold', step.current ? 'text-success font-bold' : step.completed ? 'text-foreground' : 'text-muted-foreground', ) } formatTotal(val: number): string { return val.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } copyTrackingNumber(): void { const nav = typeof navigator !== 'undefined' ? navigator : undefined if (nav?.clipboard) { void nav.clipboard.writeText('TRK-8492019') this.copiedTracking.set(true) setTimeout(() => this.copiedTracking.set(false), 2000) } } copyEmployeeId(): void { const nav = typeof navigator !== 'undefined' ? navigator : undefined if (nav?.clipboard) { void nav.clipboard.writeText('EMP-88291') this.copiedEmpId.set(true) setTimeout(() => this.copiedEmpId.set(false), 2000) } } handleExportManifest(): void { this.exportManifest.emit() } handleOpenCustomOrder(): void { this.showCustomOrderModal.set(true) this.orderCustom.emit() } handleCloseCustomOrder(): void { this.showCustomOrderModal.set(false) this.customRequestSubmitted.set(false) this.customItemName.set('') this.customItemJustification.set('') } submitCustomRequest(): void { if (this.customItemName().trim()) { this.customRequestSubmitted.set(true) setTimeout(() => this.handleCloseCustomOrder(), 2000) } } }src/app/components/blocks/onboarding-equipment-provisioning/index.ts0.2 kB
export { UiOnboardingEquipmentProvisioningComponent, type WorkstationChoice, softwareLicenses, trackingSteps, } from './onboarding-equipment-provisioning.component'
Raw manifest:https://uipkge.dev/r/angular/onboarding-equipment-provisioning.json