Waitlist
blockmarketingPre-launch signup page. Split layout with product pitch, logo mark, three benefit bullets on the left and an email capture card on the right that flips to a queue-position confirmation with referral hint after submit.
Installation
$pnpm dlx uipkge-ng@latest add waitlist$npx uipkge-ng@latest add waitlist$yarn dlx uipkge-ng@latest add waitlist$bunx uipkge-ng@latest add waitlistManifest:
https://uipkge.dev/r/angular/waitlist.jsonInstalls to:src/app/components/blocks/waitlist/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.
WaitlistBenefitinterface WaitlistBenefit {
icon: typeof Zap
title: string
copy: string
}npm dependencies
Files installed (2)
src/app/components/blocks/waitlist/waitlist.component.ts5.8 kB
// Pre-launch signup page: split layout with product pitch, logo mark and three benefit // bullets on the left, and an email capture card on the right that flips to a // queue-position confirmation with referral hint after submit. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core' import { Check, Layers, Link2, LucideAngularModule, ShieldCheck, Zap } from 'lucide-angular' import { cn } from '@/lib/utils' 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' interface WaitlistBenefit { icon: typeof Zap title: string copy: string } const benefits: WaitlistBenefit[] = [ { icon: Zap, title: 'Ships in minutes', copy: 'Drop-in blocks and primitives, not boilerplate to rewrite.' }, { icon: ShieldCheck, title: 'Own the code', copy: 'Every file is copied into your repo. Edit it freely, no lock-in.', }, { icon: Layers, title: 'Dual framework', copy: 'Vue and React mirrors stay in lockstep across every component.' }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-waitlist, [ui-waitlist]', 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, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiInputComponent, ], template: ` <section data-slot="waitlist" [class]="rootClass"> <div class="bg-muted/40 relative flex items-center px-6 py-16 lg:border-r lg:px-16"> <div class="max-w-lg"> <div class="flex items-center gap-3"> <div class="bg-primary text-primary-foreground grid size-10 place-items-center rounded-lg shadow-sm"> <lucide-icon [img]="Layers" class="size-5" /> </div> <span class="text-sm font-semibold tracking-tight">Northwind</span> </div> <h1 class="mt-8 text-3xl font-semibold tracking-tight sm:text-4xl">Be first in line when we launch</h1> <p class="text-muted-foreground mt-4 text-base sm:text-lg"> We’re onboarding a small cohort of teams each week. Join the waitlist and we’ll save your spot. </p> <ul class="mt-10 space-y-6"> @for (benefit of benefits; track benefit.title) { <li class="flex gap-4"> <div class="bg-primary/10 text-primary grid size-9 shrink-0 place-items-center rounded-lg"> <lucide-icon [img]="benefit.icon" class="size-4.5" /> </div> <div> <p class="text-sm font-medium">{{ benefit.title }}</p> <p class="text-muted-foreground mt-1 text-sm">{{ benefit.copy }}</p> </div> </li> } </ul> </div> </div> <div class="flex items-center justify-center px-6 py-16"> <div ui-card class="w-full max-w-md"> @if (!submitted()) { <div ui-card-header> <h3 ui-card-title>Join the waitlist</h3> <p ui-card-description> Early access rolls out weekly. No spam — one email when it’s your turn. </p> </div> <div ui-card-content> <form class="space-y-3" (submit)="handleSubmit($event)"> <ui-input type="email" placeholder="[email protected]" aria-label="Email address" required [value]="email()" (valueChange)="email.set($event)" /> <button ui-button type="submit" class="w-full">Request access</button> </form> <p class="text-muted-foreground mt-4 text-xs">2,300+ teams already in line</p> </div> } @else { <div ui-card-content class="pt-6 text-center"> <div class="bg-primary/10 text-primary mx-auto grid size-12 place-items-center rounded-full"> <lucide-icon [img]="Check" class="size-6" /> </div> <h2 class="mt-5 text-2xl font-semibold tracking-tight">You’re #{{ position() }} in line</h2> <p class="text-muted-foreground mt-2 text-sm"> We’ll email {{ email() }} as soon as your spot opens up. </p> <div class="bg-muted/50 mt-6 flex items-start gap-3 rounded-lg border p-4 text-left"> <lucide-icon [img]="Link2" class="text-muted-foreground mt-0.5 size-4 shrink-0" /> <p class="text-muted-foreground text-xs"> Want to skip the queue? Share your invite link after launch — every friend who signs up moves you up 5 spots. </p> </div> </div> } </div> </div> </section> `, }) export class UiWaitlistComponent { protected readonly Check = Check protected readonly Layers = Layers protected readonly Link2 = Link2 protected readonly benefits = benefits @Input('class') className?: string @Input() set initialEmail(v: string) { this.email.set(v ?? '') } readonly email = signal('') readonly submitted = signal(false) readonly position = signal(214) get rootClass(): string { return cn('bg-background grid min-h-screen lg:grid-cols-2', this.className) } handleSubmit(event: Event): void { event.preventDefault() if (!this.email().includes('@')) return this.submitted.set(true) } }src/app/components/blocks/waitlist/index.ts0.1 kB
export { UiWaitlistComponent } from './waitlist.component'
Raw manifest:https://uipkge.dev/r/angular/waitlist.json