Menu

Change language

GitHub repo

Waitlist

blockmarketing

Pre-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

$npx uipkge-ng@latest add waitlist
Manifest:https://uipkge.dev/r/angular/waitlist.jsonInstalls to:src/app/components/blocks/waitlist/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

WaitlistBenefit
interface 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&rsquo;re onboarding a small cohort of teams each week. Join the waitlist and we&rsquo;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 &mdash; one email when it&rsquo;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&rsquo;re #{{ position() }} in line</h2>
                  <p class="text-muted-foreground mt-2 text-sm">
                    We&rsquo;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 &mdash; 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

Raw manifest:https://uipkge.dev/r/angular/waitlist.json