Sales Contact Form
blockmarketingInteractive enterprise solutions architect dispatch workbench with track selection, live hub timezones, SLA badges, and ticket generator.
Installation
$pnpm dlx uipkge-ng@latest add contact-01$npx uipkge-ng@latest add contact-01$yarn dlx uipkge-ng@latest add contact-01$bunx uipkge-ng@latest add contact-01Manifest:
https://uipkge.dev/r/angular/contact-01.jsonInstalls to:src/app/components/blocks/contact-01/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
InquiryTrackinterface InquiryTrack {
id: string
title: string
subtitle: string
sla: string
icon: 'enterprise' | 'support' | 'security'
}ContactSubmitPayloadinterface ContactSubmitPayload {
type: string
name: string
email: string
company: string
teamSize: string
message: string
ticketId: string
}Files installed (2)
src/app/components/blocks/contact-01/contact-01.component.ts17.6 kB
// Interactive enterprise solutions architect dispatch workbench with track selection, // live hub timezones, SLA badges, and ticket generator. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, computed, signal } from '@angular/core' import { Building2, Check, Copy, Headphones, LucideAngularModule, MessageSquare, Send, ShieldCheck, } 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, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiInputComponent } from '@/ui/input/input.component' import { UiLabelComponent } from '@/ui/label/label.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiTextareaComponent } from '@/ui/textarea/textarea.component' export interface InquiryTrack { id: string title: string subtitle: string sla: string icon: 'enterprise' | 'support' | 'security' } export interface ContactSubmitPayload { type: string name: string email: string company: string teamSize: string message: string ticketId: string } const inquiryTypes: InquiryTrack[] = [ { id: 'enterprise', title: 'Enterprise Architecture', subtitle: 'Custom registries, zero-lockin migrations & multi-team governance', sla: '< 2h SLA', icon: 'enterprise', }, { id: 'support', title: 'Dedicated Engineering SLA', subtitle: '24/7 incident response, custom upstream bugfixes & private Slack channel', sla: '< 15m SLA', icon: 'support', }, { id: 'security', title: 'Security & Compliance Review', subtitle: 'SOC2 Type II audits, self-hosted registry mirrors & air-gapped deployments', sla: '< 4h SLA', icon: 'security', }, ] const globalHubs = [ { city: 'San Francisco', tz: 'PST (UTC-8)', status: 'Active (09:00 - 18:00)', email: '[email protected]' }, { city: 'London', tz: 'GMT (UTC+0)', status: 'Active (08:30 - 17:30)', email: '[email protected]' }, { city: 'Singapore', tz: 'SGT (UTC+8)', status: 'Active (09:00 - 18:00)', email: '[email protected]' }, ] const teamSizes = ['1-10', '10-50', '50+'] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-contact-01, [ui-contact-01]', standalone: true, // React renders the root <section> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiInputComponent, UiLabelComponent, UiSeparatorComponent, UiTextareaComponent, ], template: ` <section data-slot="contact-01" [class]="rootClass"> <div class="mx-auto max-w-6xl space-y-12"> <!-- Section Header --> <div class="max-w-3xl space-y-3"> <span ui-badge variant="secondary" class="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs"> <lucide-icon [img]="ShieldCheck" class="text-primary size-3.5" /> Enterprise Engineering & Architecture Solutions </span> <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl"> Talk to a principal design engineer. </h2> <p class="text-muted-foreground text-sm leading-relaxed sm:text-base"> No generic sales reps. Connect directly with the core team that architects our dual-framework registry, token engines, and headless workbenches. </p> </div> <!-- Main Layout --> <div class="grid grid-cols-1 items-start gap-8 lg:grid-cols-12"> <!-- Left Column: Inquiry Routing & Global Hubs (5 Cols) --> <div class="space-y-6 lg:col-span-5"> <div class="space-y-3"> <p class="text-muted-foreground font-mono text-xs tracking-wider uppercase">Select Engagement Track</p> <div class="space-y-2.5"> @for (track of inquiryTypes; track track.id) { <div role="button" tabindex="0" [class]="trackCardClass(track.id)" (click)="selectedType.set(track.id)" (keydown.enter)="selectedType.set(track.id)" (keydown.space)="$event.preventDefault(); selectedType.set(track.id)" > <div class="flex items-start justify-between gap-3"> <div class="flex items-center gap-2.5"> <div [class]="trackIconClass(track.id)"> @switch (track.icon) { @case ('enterprise') { <lucide-icon [img]="Building2" class="size-4" /> } @case ('support') { <lucide-icon [img]="Headphones" class="size-4" /> } @default { <lucide-icon [img]="ShieldCheck" class="size-4" /> } } </div> <span class="text-foreground text-xs font-semibold">{{ track.title }}</span> </div> <span ui-badge variant="outline" class="bg-background font-mono text-xs"> {{ track.sla }} </span> </div> <p class="text-muted-foreground mt-2 pl-9 text-xs"> {{ track.subtitle }} </p> </div> } </div> </div> <div ui-separator></div> <!-- Global Timezone & Architecture Hubs --> <div class="space-y-3"> <div class="flex items-center justify-between"> <p class="text-muted-foreground font-mono text-xs tracking-wider uppercase">Direct Engineering Hubs</p> <div class="text-success flex items-center gap-1.5 font-mono text-xs"> <span class="bg-success size-2 animate-pulse rounded-full"></span> Live Dispatch Active </div> </div> <div class="space-y-2"> @for (hub of hubs; track hub.city) { <div class="border-border bg-card/60 flex items-center justify-between rounded-lg border p-3 text-xs shadow-xs" > <div> <p class="text-foreground font-medium">{{ hub.city }}</p> <p class="text-muted-foreground font-mono text-xs">{{ hub.tz }} • {{ hub.status }}</p> </div> <a [href]="'mailto:' + hub.email" class="text-primary font-mono text-xs hover:underline"> {{ hub.email }} </a> </div> } </div> </div> </div> <!-- Right Column: Interactive Dispatch Form (7 Cols) --> <div class="lg:col-span-7"> <div ui-card class="border-border bg-card overflow-hidden shadow-sm"> <div ui-card-header class="border-border/60 bg-muted/20 border-b pb-4"> <div class="flex items-center justify-between"> <h3 ui-card-title class="flex items-center gap-2 text-base font-semibold"> <lucide-icon [img]="MessageSquare" class="text-primary size-4" /> Dispatch Engineering Request </h3> <span ui-badge variant="secondary" class="font-mono text-xs"> Direct Dispatch: {{ selectedType() }} </span> </div> <p ui-card-description class="text-xs"> Provide architectural specifications or questions. Our engineering team responds within guaranteed SLA. </p> </div> <div ui-card-content class="p-6"> @if (!sent()) { <form class="space-y-4" (submit)="onFormSubmit($event)"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2"> <div class="space-y-1.5"> <label ui-label for="contact-name" class="text-xs font-medium">Your Name</label> <ui-input id="contact-name" [value]="name()" (valueChange)="name.set($event)" placeholder="Jane Doe" class="h-9 text-xs" /> </div> <div class="space-y-1.5"> <label ui-label for="contact-email" class="text-xs font-medium">Work Email</label> <ui-input id="contact-email" [value]="email()" (valueChange)="email.set($event)" type="email" placeholder="[email protected]" class="h-9 font-mono text-xs" /> </div> </div> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2"> <div class="space-y-1.5"> <label ui-label for="contact-company" class="text-xs font-medium">Company / Organization</label> <ui-input id="contact-company" [value]="company()" (valueChange)="company.set($event)" placeholder="Acme Corp" class="h-9 text-xs" /> </div> <div class="space-y-1.5"> <span class="text-xs font-medium">Engineering Team Size</span> <div class="grid grid-cols-3 gap-1.5"> @for (ts of teamSizes; track ts) { <button type="button" [class]="teamSizeClass(ts)" (click)="teamSize.set(ts)"> {{ ts }} </button> } </div> </div> </div> <div class="space-y-1.5"> <label ui-label for="contact-message" class="text-xs font-medium" >Project Scope & Technical Details</label > <ui-textarea id="contact-message" [value]="message()" (valueChange)="message.set($event)" [rows]="4" placeholder="Describe your tech stack (Vue 3, Nuxt 3, React 19, Next.js), design token requirements, and deployment targets..." class="resize-none text-xs" /> </div> <div class="flex flex-col items-center justify-between gap-3 pt-2 sm:flex-row"> <div class="text-muted-foreground flex items-center gap-2 text-xs"> <lucide-icon [img]="ShieldCheck" class="text-success size-4" /> <span>NDA & IP protection pre-guaranteed.</span> </div> <button ui-button type="submit" [disabled]="!canSubmit()" class="h-9 w-full gap-1.5 px-5 text-xs font-semibold sm:w-auto" > <span>Dispatch Inquiry</span> <lucide-icon [img]="Send" class="size-3.5" /> </button> </div> </form> } @else { <!-- Success Feedback View --> <div class="space-y-5 px-4 py-8 text-center"> <div class="bg-success/10 text-success mx-auto flex size-12 items-center justify-center rounded-full" > <lucide-icon [img]="Check" class="size-6" /> </div> <div class="space-y-1"> <p class="text-foreground text-base font-bold">Inquiry Dispatched Successfully</p> <p class="text-muted-foreground mx-auto max-w-sm text-xs"> A principal solutions architect has been assigned. You will receive a technical response at <strong class="text-foreground font-mono">{{ email() }}</strong> within guaranteed SLA. </p> </div> <div class="border-border bg-muted/40 mx-auto flex max-w-xs items-center justify-between rounded-lg border p-3" > <div class="text-left"> <p class="text-muted-foreground font-mono text-xs uppercase">Reference Ticket ID</p> <p class="text-foreground font-mono text-xs font-bold">{{ ticketId() }}</p> </div> <button ui-button variant="outline" size="sm" class="h-7 gap-1 font-mono text-xs" (click)="copyTicket()" > @if (copiedTicket()) { <lucide-icon [img]="Check" class="text-success size-3" /> } @else { <lucide-icon [img]="Copy" class="size-3" /> } <span>{{ copiedTicket() ? 'Copied' : 'Copy' }}</span> </button> </div> <button ui-button variant="ghost" size="sm" class="text-muted-foreground text-xs" (click)="sent.set(false)" > Send Another Inquiry </button> </div> } </div> </div> </div> </div> </div> </section> `, }) export class UiContact01Component { protected readonly Building2 = Building2 protected readonly Check = Check protected readonly Copy = Copy protected readonly Headphones = Headphones protected readonly MessageSquare = MessageSquare protected readonly Send = Send protected readonly ShieldCheck = ShieldCheck protected readonly inquiryTypes = inquiryTypes protected readonly hubs = globalHubs protected readonly teamSizes = teamSizes @Input('class') className?: string @Output() readonly submit = new EventEmitter<ContactSubmitPayload>() readonly selectedType = signal('enterprise') readonly name = signal('') readonly email = signal('') readonly company = signal('') readonly teamSize = signal('10-50') readonly message = signal('') readonly sent = signal(false) readonly ticketId = signal('') readonly copiedTicket = signal(false) private copyTimer: ReturnType<typeof setTimeout> | undefined readonly canSubmit = computed(() => !!this.name().trim() && !!this.email().trim() && !!this.message().trim()) get rootClass(): string { return cn('bg-background relative overflow-hidden px-4 py-16 sm:px-6 lg:px-8', this.className) } trackCardClass(id: string): string { return cn( 'group focus-visible:ring-ring cursor-pointer rounded-xl border p-4 transition-colors focus-visible:ring-2 focus-visible:outline-none', this.selectedType() === id ? 'border-primary bg-primary/5 ring-primary/20 shadow-xs ring-1' : 'border-border bg-card hover:bg-muted/40', ) } trackIconClass(id: string): string { return cn( 'rounded-lg p-2 transition-colors', this.selectedType() === id ? 'bg-primary text-primary-foreground' : 'bg-muted text-foreground group-hover:bg-primary/10 group-hover:text-primary', ) } teamSizeClass(ts: string): string { return cn( 'rounded-md border px-2 py-1.5 text-center font-mono text-xs transition-colors', this.teamSize() === ts ? 'border-primary bg-primary/10 text-primary font-semibold' : 'border-border bg-background text-muted-foreground hover:text-foreground', ) } onFormSubmit(event: Event): void { event.preventDefault() this.dispatch() } dispatch(): ContactSubmitPayload | null { if (!this.canSubmit()) return null const generatedId = `UIP-${Math.floor(1000 + Math.random() * 9000)}` this.ticketId.set(generatedId) this.sent.set(true) const payload: ContactSubmitPayload = { type: this.selectedType(), name: this.name(), email: this.email(), company: this.company(), teamSize: this.teamSize(), message: this.message(), ticketId: generatedId, } this.submit.emit(payload) return payload } copyTicket(): void { const id = this.ticketId() if (!id) return try { void navigator.clipboard?.writeText(id) } catch { // Clipboard unavailable (e.g. jsdom) — still flip the label. } this.copiedTicket.set(true) clearTimeout(this.copyTimer) this.copyTimer = setTimeout(() => this.copiedTicket.set(false), 2000) } }src/app/components/blocks/contact-01/index.ts0.1 kB
export { UiContact01Component, type InquiryTrack, type ContactSubmitPayload } from './contact-01.component'
Raw manifest:https://uipkge.dev/r/angular/contact-01.json