Menu

Change language

GitHub repo

Lease Agreement Sign

blockreal-estate

DocuSign-style digital residential lease agreement signature and review workflow with terms summary, interactive initial checkpoints, sticky signature action sidebar, and cryptographic audit execution.

Installation

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

Variants

Loading interactive previews…

Files installed (2)

  • src/app/components/blocks/lease-agreement-sign/lease-agreement-sign.component.ts42.7 kB
    // DocuSign-style digital residential lease agreement signature and review workflow with
    // terms summary, interactive initial checkpoints, sticky signature action sidebar, and
    // cryptographic audit execution. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import {
      AlertCircle,
      BadgeCheck,
      Building2,
      Check,
      CheckCircle2,
      Download,
      Lock,
      LucideAngularModule,
      Pen,
      PenTool,
      Printer,
      RotateCcw,
      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 { UiSeparatorComponent } from '@/ui/separator/separator.component'
    
    export type SignatureFont = 'serif' | 'script' | 'sans'
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-lease-agreement-sign, [ui-lease-agreement-sign]',
      standalone: true,
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiSeparatorComponent,
      ],
      template: `
        <div data-slot="lease-agreement-sign" [class]="rootClass">
          <div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 sm:py-8 lg:px-8">
            <!-- Top Header -->
            <header class="border-border bg-card mb-6 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">
                <div class="space-y-1.5">
                  <div class="flex flex-wrap items-center gap-2">
                    <span class="text-muted-foreground text-xs font-medium">Agreement Ref:</span>
                    <span class="text-foreground font-mono text-xs font-semibold tabular-nums">#LSA-2026-44B</span>
                    <span class="text-muted-foreground text-xs">•</span>
                    @if (isSigned()) {
                      <span ui-badge wrap class="border-success/30 bg-success/10 text-success gap-1.5 text-xs font-medium">
                        <lucide-icon [img]="BadgeCheck" class="size-3.5" />
                        <span>Fully Executed &amp; Legally Binding</span>
                      </span>
                    } @else if (initial1Completed() && initial2Completed()) {
                      <span ui-badge wrap class="border-info/30 bg-info/10 text-info gap-1.5 text-xs font-medium">
                        <lucide-icon [img]="PenTool" class="size-3.5" />
                        <span>Ready for Tenant Signature</span>
                      </span>
                    } @else {
                      <span ui-badge wrap class="border-warning/30 bg-warning/10 text-warning gap-1.5 text-xs font-medium">
                        <lucide-icon [img]="AlertCircle" class="size-3.5" />
                        <span>Action Required • {{ pendingCount() }} Signatures Needed</span>
                      </span>
                    }
                  </div>
                  <h1 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl lg:text-3xl">
                    Residential Lease Agreement • 12-Month Term
                  </h1>
                  <p class="text-muted-foreground flex flex-wrap items-center gap-2 text-xs sm:text-sm">
                    <lucide-icon [img]="Building2" class="size-4 shrink-0" />
                    <span>Unit 4B • 742 Evergreen Terrace, Springfield, OR 97477</span>
                  </p>
                </div>
    
                <div class="flex flex-wrap items-center gap-2.5">
                  <button ui-button variant="outline" size="sm" class="gap-1.5 text-xs" (click)="handlePrint()">
                    <lucide-icon [img]="Printer" class="size-4" />
                    <span>Print</span>
                  </button>
                  <button
                    ui-button
                    aria-label="Download attachment"
                    variant="outline"
                    size="sm"
                    class="gap-1.5 text-xs"
                    (click)="handleDownload()"
                  >
                    <lucide-icon [img]="Download" class="size-4" />
                    <span>{{ downloadSuccess() ? 'Downloading PDF...' : 'Download Draft PDF' }}</span>
                  </button>
                  @if (isSigned()) {
                    <button
                      ui-button
                      variant="outline"
                      size="sm"
                      class="border-muted-foreground/30 gap-1.5 text-xs"
                      (click)="resetWorkflow()"
                    >
                      <lucide-icon [img]="RotateCcw" class="size-3.5" />
                      <span>Reset Demo</span>
                    </button>
                  }
                </div>
              </div>
            </header>
    
            <!-- 2-Column Layout -->
            <div class="grid grid-cols-1 gap-8 lg:grid-cols-12">
              <!-- Left Column: Legal Lease Document -->
              <main class="space-y-6 lg:col-span-8">
                <div class="border-border bg-card rounded-xl border p-6 shadow-xs sm:p-8">
                  <!-- Document Title Bar -->
                  <div class="border-border border-b pb-6 text-center">
                    <span ui-badge variant="outline" class="font-mono text-xs tracking-widest uppercase">
                      Official Real Estate Contract
                    </span>
                    <h2 class="text-foreground mt-3 text-lg font-bold tracking-tight uppercase sm:text-xl">
                      Standard Residential Lease Agreement
                    </h2>
                    <p class="text-muted-foreground mt-1 text-xs">
                      State of Oregon • Multnomah County • Governing Statute: ORS Chapter 90
                    </p>
                  </div>
    
                  <!-- Lease Summary Terms Box -->
                  <div class="my-6">
                    <div class="bg-muted/40 border-border rounded-lg border p-4 sm:p-5">
                      <div class="mb-3 flex items-center justify-between">
                        <h3 class="text-foreground text-xs font-semibold tracking-wider uppercase">
                          Key Financial &amp; Term Summary
                        </h3>
                        <span ui-badge variant="secondary" class="font-mono text-xs">Fixed-Term</span>
                      </div>
                      <div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
                        <div class="border-border/60 bg-card rounded-md border p-3">
                          <p class="text-muted-foreground text-xs font-medium">Monthly Rent</p>
                          <p class="text-foreground mt-1 font-mono text-lg font-bold tabular-nums">$3,450.00</p>
                          <p class="text-muted-foreground mt-0.5 text-xs">Due 1st of month</p>
                        </div>
                        <div class="border-border/60 bg-card rounded-md border p-3">
                          <p class="text-muted-foreground text-xs font-medium">Lease Duration</p>
                          <p class="text-foreground mt-1 text-xs font-bold sm:text-sm">12 Months</p>
                          <p class="text-muted-foreground mt-0.5 font-mono text-xs tabular-nums">
                            Sep 01, 2026 – Aug 31, 2027
                          </p>
                        </div>
                        <div class="border-border/60 bg-card rounded-md border p-3">
                          <p class="text-muted-foreground text-xs font-medium">Security Deposit</p>
                          <p class="text-foreground mt-1 font-mono text-lg font-bold tabular-nums">$3,450.00</p>
                          <p class="text-muted-foreground mt-0.5 text-xs">Escrow Trust Acct</p>
                        </div>
                        <div class="border-border/60 bg-card rounded-md border p-3">
                          <p class="text-muted-foreground text-xs font-medium">Pet Policy</p>
                          <p class="text-foreground mt-1 text-xs font-bold sm:text-sm">Approved - 1 Dog</p>
                          <p class="text-muted-foreground mt-0.5 text-xs">$50/mo pet rent</p>
                        </div>
                      </div>
                    </div>
                  </div>
    
                  <!-- Legal Sections -->
                  <div class="text-foreground/90 space-y-8 text-xs leading-relaxed sm:text-sm">
                    <!-- Section 1 -->
                    <section id="section-1" class="space-y-3">
                      <div class="flex items-center gap-2">
                        <span class="bg-primary/10 text-primary rounded px-2 py-0.5 font-mono text-xs font-bold">01</span>
                        <h3 class="text-foreground text-sm font-bold sm:text-base">Parties, Premises &amp; Occupancy</h3>
                      </div>
                      <p class="text-muted-foreground">
                        This Residential Lease Agreement (the “Agreement”) is entered into on this 21st day of August, 2026,
                        by and between:
                      </p>
                      <div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
                        <div class="border-border bg-muted/20 rounded-lg border p-3.5">
                          <p class="text-foreground text-xs font-semibold tracking-wider uppercase">Landlord / Lessor</p>
                          <p class="text-foreground mt-1 font-medium">Evergreen Heritage Holdings LLC</p>
                          <p class="text-muted-foreground text-xs">Managing Agent: Marcus Vance, CPM</p>
                          <p class="text-muted-foreground font-mono text-xs">Lic #OR-994201 • info&#64;evergreenhh.com</p>
                        </div>
                        <div class="border-border bg-muted/20 rounded-lg border p-3.5">
                          <p class="text-foreground text-xs font-semibold tracking-wider uppercase">Tenant / Lessee</p>
                          <p class="text-foreground mt-1 font-medium">Elena Rostova</p>
                          <p class="text-muted-foreground text-xs">Primary Occupant</p>
                          <p class="text-muted-foreground text-xs">elena.rostova&#64;example.com • (555) 234-5678</p>
                        </div>
                      </div>
                      <p class="text-muted-foreground">
                        <strong>Premises Description:</strong> Landlord hereby leases to Tenant the residential real
                        property situated at <strong>Unit 4B, 742 Evergreen Terrace, Springfield, OR 97477</strong>,
                        comprising 2 Bedrooms, 2 Full Bathrooms (approx. 1,120 sq ft), together with designated covered
                        parking stall #42 and storage locker #S-14.
                      </p>
                    </section>
    
                    <div ui-separator></div>
    
                    <!-- Section 2 -->
                    <section id="section-2" class="space-y-3">
                      <div class="flex items-center gap-2">
                        <span class="bg-primary/10 text-primary rounded px-2 py-0.5 font-mono text-xs font-bold">02</span>
                        <h3 class="text-foreground text-sm font-bold sm:text-base">
                          Rent Payment Schedule &amp; Late Fees
                        </h3>
                      </div>
                      <p class="text-muted-foreground">
                        Tenant agrees to pay Landlord the base monthly rent of
                        <strong class="text-foreground font-mono tabular-nums">$3,450.00 USD</strong> plus
                        <strong class="text-foreground font-mono tabular-nums">$50.00 USD</strong> monthly pet rent, for an
                        aggregate monthly payment of
                        <strong class="text-foreground font-mono tabular-nums">$3,500.00 USD</strong>, due on or before the
                        first (1st) day of each calendar month.
                      </p>
                      <p class="text-muted-foreground">
                        <strong>Grace Period &amp; Penalties:</strong> A grace period is provided through 11:59 PM PST on
                        the fifth (5th) day of the month. If rent is not received in full by 12:00 AM on the sixth (6th)
                        calendar day, a statutory late fee of
                        <strong class="text-foreground font-mono tabular-nums">$75.00 USD</strong> shall immediately apply,
                        plus an additional charge of
                        <strong class="text-foreground font-mono tabular-nums">$10.00 USD per day</strong> until the
                        delinquent balance is satisfied in full.
                      </p>
    
                      <div id="initial-marker-1" [class]="initial1BoxClass">
                        <div class="flex flex-col justify-between gap-3 sm:flex-row sm:items-center">
                          <div class="space-y-1">
                            <div class="flex items-center gap-2">
                              <span class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
                                Tenant Initial Requirement • Section 2
                              </span>
                              @if (initial1Completed()) {
                                <span
                                  ui-badge
                                  wrap
                                  class="border-success/40 bg-success/10 text-success text-xs font-medium"
                                >
                                  <lucide-icon [img]="Check" class="mr-1 size-3" /> Initialed
                                </span>
                              } @else {
                                <span
                                  ui-badge
                                  wrap
                                  class="border-warning/40 bg-warning/10 text-warning text-xs font-medium"
                                >
                                  Action Required
                                </span>
                              }
                            </div>
                            <p class="text-muted-foreground text-xs">
                              Acknowledge and agree to the $3,450.00 rent schedule, ACH delivery, and $75.00 late assessment
                              clauses.
                            </p>
                          </div>
                          <button
                            ui-button
                            type="button"
                            size="sm"
                            [disabled]="isSigned()"
                            [variant]="initial1Completed() ? 'outline' : 'default'"
                            [class]="initial1BtnClass"
                            (click)="toggleInitial1()"
                          >
                            @if (initial1Completed()) {
                              <span class="text-sm font-semibold italic">ER</span>
                              <span class="font-mono text-xs tabular-nums">• Elena Rostova [Initialed]</span>
                            } @else {
                              <lucide-icon [img]="Pen" class="size-3.5" />
                              <span>Click to Initial [ER]</span>
                            }
                          </button>
                        </div>
                      </div>
                    </section>
    
                    <div ui-separator></div>
    
                    <!-- Section 3 -->
                    <section id="section-3" class="space-y-3">
                      <div class="flex items-center gap-2">
                        <span class="bg-primary/10 text-primary rounded px-2 py-0.5 font-mono text-xs font-bold">03</span>
                        <h3 class="text-foreground text-sm font-bold sm:text-base">
                          Maintenance, Utilities &amp; Occupancy Rules
                        </h3>
                      </div>
                      <ul class="text-muted-foreground space-y-2">
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="CheckCircle2" class="text-primary mt-0.5 size-4 shrink-0" />
                          <span>
                            <strong class="text-foreground">Quiet Hours:</strong> Community quiet hours are strictly
                            enforced between 10:00 PM and 7:00 AM daily.
                          </span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="CheckCircle2" class="text-primary mt-0.5 size-4 shrink-0" />
                          <span>
                            <strong class="text-foreground">HVAC Maintenance:</strong> Tenant shall replace HVAC air filters
                            every 90 calendar days. Replacement filters are provided free of charge at property management.
                          </span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="CheckCircle2" class="text-primary mt-0.5 size-4 shrink-0" />
                          <span>
                            <strong class="text-foreground">Guest Policy:</strong> Guests staying exceeding 14 consecutive
                            calendar days require written authorization from property management.
                          </span>
                        </li>
                        <li class="flex items-start gap-2">
                          <lucide-icon [img]="CheckCircle2" class="text-primary mt-0.5 size-4 shrink-0" />
                          <span>
                            <strong class="text-foreground">Utilities Allocation:</strong> Landlord pays municipal water,
                            sewer, and storm drainage. Tenant is responsible for electricity (PGE), gas (NW Natural), and
                            high-speed internet.
                          </span>
                        </li>
                      </ul>
                    </section>
    
                    <div ui-separator></div>
    
                    <!-- Section 4 -->
                    <section id="section-4" class="space-y-3">
                      <div class="flex items-center gap-2">
                        <span class="bg-primary/10 text-primary rounded px-2 py-0.5 font-mono text-xs font-bold">04</span>
                        <h3 class="text-foreground text-sm font-bold sm:text-base">
                          Termination, Renewal &amp; Move-Out Notice
                        </h3>
                      </div>
                      <p class="text-muted-foreground">
                        Either party may terminate or request non-renewal of this lease by delivering a formal written
                        notice at least <strong>sixty (60) calendar days</strong> prior to the lease expiration date of
                        August 31, 2027.
                      </p>
                      <p class="text-muted-foreground">
                        <strong>Move-Out &amp; Carpet Certification:</strong> Upon vacating the premises, Tenant shall
                        return all building keys (2 unit keys, 1 mailbox key, 1 garage fob) and furnish a paid receipt from
                        a licensed professional carpet cleaning service.
                      </p>
    
                      <div id="initial-marker-2" [class]="initial2BoxClass">
                        <div class="flex flex-col justify-between gap-3 sm:flex-row sm:items-center">
                          <div class="space-y-1">
                            <div class="flex items-center gap-2">
                              <span class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
                                Tenant Initial Requirement • Section 4
                              </span>
                              @if (initial2Completed()) {
                                <span
                                  ui-badge
                                  wrap
                                  class="border-success/40 bg-success/10 text-success text-xs font-medium"
                                >
                                  <lucide-icon [img]="Check" class="mr-1 size-3" /> Initialed
                                </span>
                              } @else {
                                <span
                                  ui-badge
                                  wrap
                                  class="border-warning/40 bg-warning/10 text-warning text-xs font-medium"
                                >
                                  Action Required
                                </span>
                              }
                            </div>
                            <p class="text-muted-foreground text-xs">
                              Acknowledge the 60-day written move-out notice requirement and professional carpet cleaning
                              obligation.
                            </p>
                          </div>
                          <button
                            ui-button
                            type="button"
                            size="sm"
                            [disabled]="isSigned()"
                            [variant]="initial2Completed() ? 'outline' : 'default'"
                            [class]="initial2BtnClass"
                            (click)="toggleInitial2()"
                          >
                            @if (initial2Completed()) {
                              <span class="text-sm font-semibold italic">ER</span>
                              <span class="font-mono text-xs tabular-nums">• Elena Rostova [Initialed]</span>
                            } @else {
                              <lucide-icon [img]="Pen" class="size-3.5" />
                              <span>Click to Initial [ER]</span>
                            }
                          </button>
                        </div>
                      </div>
                    </section>
    
                    <div ui-separator></div>
    
                    <!-- Execution -->
                    <section class="space-y-4 pt-2">
                      <div class="flex items-center justify-between">
                        <h3 class="text-foreground text-sm font-bold sm:text-base">Execution &amp; Legal Signatures</h3>
                        <span class="text-muted-foreground font-mono text-xs">2 of 2 Parties</span>
                      </div>
    
                      <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
                        <div class="border-border bg-muted/20 rounded-lg border p-4">
                          <div class="flex items-center justify-between">
                            <p class="text-foreground text-xs font-semibold tracking-wider uppercase">Landlord Signature</p>
                            <span
                              ui-badge
                              wrap
                              variant="outline"
                              class="border-success/40 bg-success/10 text-success font-mono text-xs"
                            >
                              <lucide-icon [img]="Check" class="mr-1 size-3" /> Signed
                            </span>
                          </div>
                          <div class="border-border/60 bg-card my-3 rounded-md border p-3 text-center">
                            <p class="text-success text-xl font-medium tracking-wide italic">Marcus Vance</p>
                            <p class="text-muted-foreground mt-0.5 font-mono text-xs">Agent for Evergreen Holdings LLC</p>
                          </div>
                          <div class="text-muted-foreground space-y-1 font-mono text-xs">
                            <p class="flex justify-between">
                              <span>Date Signed:</span>
                              <span class="text-foreground font-semibold tabular-nums">Aug 20, 2026 • 09:15 PDT</span>
                            </p>
                            <p class="flex justify-between">
                              <span>Certificate ID:</span>
                              <span class="text-foreground font-semibold">DS-EHG-8842-MV</span>
                            </p>
                          </div>
                        </div>
    
                        <div [class]="tenantBoxClass">
                          <div class="flex items-center justify-between">
                            <p class="text-foreground text-xs font-semibold tracking-wider uppercase">Tenant Signature</p>
                            @if (isSigned()) {
                              <span
                                ui-badge
                                wrap
                                class="border-success/40 bg-success/10 text-success font-mono text-xs font-medium"
                              >
                                <lucide-icon [img]="Check" class="mr-1 size-3" /> Signed
                              </span>
                            } @else {
                              <span
                                ui-badge
                                wrap
                                class="border-warning/40 bg-warning/10 text-warning font-mono text-xs font-medium"
                              >
                                Pending Signature
                              </span>
                            }
                          </div>
                          <div [class]="tenantPreviewClass">
                            @if (isSigned()) {
                              <p [class]="signaturePreviewClass">{{ signerName() }}</p>
                              <p class="text-muted-foreground mt-0.5 font-mono text-xs">
                                e-Signed via DocuSign / UIPKGE Registry
                              </p>
                            } @else {
                              <p class="text-muted-foreground text-xs italic">
                                Awaiting signature execution via right sidebar
                              </p>
                              <p class="text-muted-foreground/80 mt-1 text-xs">
                                Complete 2 initials above to enable signing
                              </p>
                            }
                          </div>
                          <div class="text-muted-foreground space-y-1 font-mono text-xs">
                            <p class="flex justify-between">
                              <span>Date Signed:</span>
                              <span class="text-foreground font-semibold tabular-nums">
                                {{ isSigned() ? 'Aug 21, 2026 • 10:44 PDT' : 'Pending' }}
                              </span>
                            </p>
                            <p class="flex justify-between">
                              <span>Digital Audit Hash:</span>
                              <span class="text-foreground font-semibold">
                                {{ isSigned() ? 'SHA256:7f83b1...9069' : 'Unsigned' }}
                              </span>
                            </p>
                          </div>
                        </div>
                      </div>
                    </section>
                  </div>
                </div>
              </main>
    
              <!-- Right Column: Sticky Signature Action Sidebar -->
              <aside class="space-y-5 lg:sticky lg:top-6 lg:col-span-4 lg:self-start">
                <div ui-card class="border-border bg-card shadow-xs">
                  <div ui-card-header class="p-4 pb-2">
                    <div class="flex items-center justify-between">
                      <div class="flex items-center gap-2">
                        <div
                          class="bg-primary/10 text-primary flex size-8 items-center justify-center rounded-full font-bold"
                        >
                          ER
                        </div>
                        <div>
                          <h3 ui-card-title class="text-sm font-semibold">{{ signerName() }}</h3>
                          <p ui-card-description class="text-xs">Primary Lessee • Tenant</p>
                        </div>
                      </div>
                      <span ui-badge variant="outline" class="border-primary/30 text-primary font-mono text-xs"
                        >Signer 1/1</span
                      >
                    </div>
                  </div>
                  <div ui-card-content class="text-muted-foreground space-y-2 p-4 pt-2 text-xs">
                    <div class="flex justify-between">
                      <span class="text-muted-foreground">Email:</span>
                      <span class="text-foreground font-medium">elena.rostova&#64;example.com</span>
                    </div>
                    <div class="flex justify-between">
                      <span class="text-muted-foreground">Auth Level:</span>
                      <span class="text-foreground font-medium">SMS 2FA Verified</span>
                    </div>
                    <div class="flex justify-between">
                      <span class="text-muted-foreground">IP Session:</span>
                      <span class="text-foreground font-mono tabular-nums">198.51.100.42 (TLS 1.3)</span>
                    </div>
                  </div>
                </div>
    
                <div ui-card class="border-border bg-card shadow-xs">
                  <div ui-card-header class="p-4 pb-2">
                    <div class="flex items-center justify-between">
                      <h3 ui-card-title class="text-sm font-semibold">Required Action Checklist</h3>
                      <span
                        ui-badge
                        [variant]="completedCount() === 3 ? 'default' : 'secondary'"
                        class="font-mono text-xs whitespace-normal tabular-nums"
                      >
                        {{ completedCount() }} / 3 Done
                      </span>
                    </div>
                    <p ui-card-description class="text-xs">All initial markers must be completed prior to signing</p>
                  </div>
                  <div ui-card-content class="space-y-2.5 p-4 pt-2">
                    <button
                      type="button"
                      class="border-border/80 hover:bg-muted/40 flex w-full items-center justify-between rounded-lg border p-2.5 text-left text-xs transition-colors"
                      (click)="scrollToInitial1()"
                    >
                      <div class="flex items-center gap-2.5">
                        <div [class]="checkDotClass(initial1Completed())">
                          @if (initial1Completed()) {
                            <lucide-icon [img]="Check" class="size-3" />
                          } @else {
                            <span>1</span>
                          }
                        </div>
                        <div>
                          <p class="text-foreground font-medium">Initial Section 2 (Rent &amp; Fees)</p>
                          <p class="text-muted-foreground text-xs">Acknowledges $3,450 rent &amp; late charges</p>
                        </div>
                      </div>
                      <span
                        ui-badge
                        wrap
                        [variant]="initial1Completed() ? 'outline' : 'secondary'"
                        [class]="checkBadgeClass(initial1Completed())"
                      >
                        {{ initial1Completed() ? 'Done' : 'Click to jump' }}
                      </span>
                    </button>
    
                    <button
                      type="button"
                      class="border-border/80 hover:bg-muted/40 flex w-full items-center justify-between rounded-lg border p-2.5 text-left text-xs transition-colors"
                      (click)="scrollToInitial2()"
                    >
                      <div class="flex items-center gap-2.5">
                        <div [class]="checkDotClass(initial2Completed())">
                          @if (initial2Completed()) {
                            <lucide-icon [img]="Check" class="size-3" />
                          } @else {
                            <span>2</span>
                          }
                        </div>
                        <div>
                          <p class="text-foreground font-medium">Initial Section 4 (Move-Out)</p>
                          <p class="text-muted-foreground text-xs">Acknowledges 60-day notice &amp; cleaning</p>
                        </div>
                      </div>
                      <span
                        ui-badge
                        wrap
                        [variant]="initial2Completed() ? 'outline' : 'secondary'"
                        [class]="checkBadgeClass(initial2Completed())"
                      >
                        {{ initial2Completed() ? 'Done' : 'Click to jump' }}
                      </span>
                    </button>
    
                    <div [class]="signRowClass">
                      <div class="flex items-center gap-2.5">
                        <div [class]="checkDotClass(isSigned(), true)">
                          @if (isSigned()) {
                            <lucide-icon [img]="Check" class="size-3" />
                          } @else {
                            <span>3</span>
                          }
                        </div>
                        <div>
                          <p class="text-foreground font-medium">Sign Full Agreement</p>
                          <p class="text-muted-foreground text-xs">Execute legal lease contract</p>
                        </div>
                      </div>
                      <span ui-badge wrap [variant]="isSigned() ? 'outline' : 'secondary'" [class]="signBadgeClass">
                        {{ isSigned() ? 'Executed' : 'Pending' }}
                      </span>
                    </div>
                  </div>
                </div>
    
                <div ui-card class="border-border bg-card shadow-xs">
                  <div ui-card-header class="p-4 pb-2">
                    <h3 ui-card-title class="text-sm font-semibold">Electronic Signature Preview</h3>
                    <p ui-card-description class="text-xs">Adopt your legal signature typography</p>
                  </div>
                  <div ui-card-content class="space-y-4 p-4 pt-2">
                    <div class="bg-muted/60 grid grid-cols-3 gap-1.5 rounded-lg p-1">
                      <button type="button" [class]="fontTabClass('serif')" (click)="signatureFont.set('serif')">
                        Formal Serif
                      </button>
                      <button type="button" [class]="fontTabClass('script')" (click)="signatureFont.set('script')">
                        Script Elegance
                      </button>
                      <button type="button" [class]="fontTabClass('sans')" (click)="signatureFont.set('sans')">
                        Modern Sans
                      </button>
                    </div>
    
                    <div class="border-border bg-muted/20 relative rounded-lg border p-4 text-center">
                      <p class="text-muted-foreground text-xs font-medium">Adopted Signature</p>
                      <div class="my-2 flex min-h-[48px] items-center justify-center">
                        <span [class]="signaturePreviewClass">{{ signerName() }}</span>
                      </div>
                      <div class="text-muted-foreground flex items-center justify-center gap-1.5 font-mono text-xs">
                        <lucide-icon [img]="ShieldCheck" class="text-success size-3.5" />
                        <span>256-Bit eIDAS / ESIGN Certified</span>
                      </div>
                    </div>
    
                    <label class="text-muted-foreground flex cursor-pointer items-start gap-2.5 text-xs">
                      <input
                        type="checkbox"
                        [checked]="eConsentAgreed()"
                        [disabled]="isSigned()"
                        (change)="eConsentAgreed.set(!eConsentAgreed())"
                        class="text-primary focus:ring-ring border-border mt-0.5 size-4 rounded"
                      />
                      <span>
                        I agree to transact electronically and confirm this electronic signature legally binds me under U.S.
                        ESIGN Act and ORS Chapter 90.
                      </span>
                    </label>
    
                    <div class="space-y-2 pt-2">
                      @if (!isSigned()) {
                        <button
                          ui-button
                          type="button"
                          class="w-full gap-2 text-sm font-semibold shadow-xs"
                          [disabled]="!canSign()"
                          (click)="signAgreement()"
                        >
                          <lucide-icon [img]="PenTool" class="size-4" />
                          <span>Sign &amp; Finalize Agreement</span>
                        </button>
                      } @else {
                        <div class="border-success/30 bg-success/10 space-y-2 rounded-lg border p-3.5 text-center">
                          <div class="text-success flex items-center justify-center gap-1.5 text-xs font-semibold">
                            <lucide-icon [img]="CheckCircle2" class="size-4" />
                            <span>Lease Executed Successfully!</span>
                          </div>
                          <p class="text-muted-foreground text-xs">
                            A copy of the countersigned PDF has been securely dispatched to your verified email.
                          </p>
                          <button
                            ui-button
                            aria-label="Download attachment"
                            size="sm"
                            class="mt-2 w-full gap-1.5 text-xs"
                            (click)="handleDownload()"
                          >
                            <lucide-icon [img]="Download" class="size-3.5" />
                            <span>Download Signed PDF</span>
                          </button>
                        </div>
                      }
                      @if (!isSigned() && !canSign()) {
                        <p class="text-muted-foreground text-center text-xs">{{ signHint() }}</p>
                      }
                    </div>
                  </div>
                </div>
    
                <div class="border-border bg-card/60 text-muted-foreground space-y-1.5 rounded-lg border p-3 text-xs">
                  <div class="text-foreground flex items-center gap-1.5 font-medium">
                    <lucide-icon [img]="Lock" class="text-primary size-3.5" />
                    <span>Cryptographic Tamper-Proof Audit Trail</span>
                  </div>
                  <p class="leading-relaxed">
                    Every initial and signature event is stamped with SHA-256 integrity hash, RFC 3161 trusted timestamp,
                    and logged in the immutable lease registry ledger.
                  </p>
                </div>
              </aside>
            </div>
          </div>
        </div>
      `,
    })
    export class UiLeaseAgreementSignComponent {
      protected readonly AlertCircle = AlertCircle
      protected readonly BadgeCheck = BadgeCheck
      protected readonly Building2 = Building2
      protected readonly Check = Check
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly Download = Download
      protected readonly Lock = Lock
      protected readonly Pen = Pen
      protected readonly PenTool = PenTool
      protected readonly Printer = Printer
      protected readonly RotateCcw = RotateCcw
      protected readonly ShieldCheck = ShieldCheck
    
      @Input() set initialSigned(v: boolean) {
        if (v) {
          this.isSigned.set(true)
          this.initial1Completed.set(true)
          this.initial2Completed.set(true)
        }
      }
      @Input() set initialInitialsCompleted(v: boolean) {
        if (v) {
          this.initial1Completed.set(true)
          this.initial2Completed.set(true)
        }
      }
      @Input('class') className?: string
    
      readonly initial1Completed = signal(false)
      readonly initial2Completed = signal(false)
      readonly isSigned = signal(false)
      readonly signerName = signal('Elena Rostova')
      readonly signatureFont = signal<SignatureFont>('serif')
      readonly eConsentAgreed = signal(true)
      readonly downloadSuccess = signal(false)
      private downloadTimer: ReturnType<typeof setTimeout> | undefined
    
      readonly completedCount = computed(
        () => (this.initial1Completed() ? 1 : 0) + (this.initial2Completed() ? 1 : 0) + (this.isSigned() ? 1 : 0),
      )
      readonly pendingCount = computed(() => (this.initial1Completed() ? 0 : 1) + (this.initial2Completed() ? 0 : 1))
      readonly canSign = computed(
        () => this.initial1Completed() && this.initial2Completed() && this.eConsentAgreed() && !this.isSigned(),
      )
      readonly signHint = computed(() =>
        !this.initial1Completed() || !this.initial2Completed()
          ? 'Please complete all initial checkpoints above before signing.'
          : 'Please check the electronic consent box.',
      )
    
      get rootClass(): string {
        return cn('bg-background text-foreground w-full', this.className)
      }
    
      get initial1BoxClass(): string {
        return cn(
          'rounded-lg border p-4 transition-colors duration-200',
          this.initial1Completed()
            ? 'border-success/40 bg-success/5'
            : 'border-warning/50 bg-warning/5 ring-warning/20 dark:bg-warning/10 ring-1',
        )
      }
    
      get initial2BoxClass(): string {
        return cn(
          'rounded-lg border p-4 transition-colors duration-200',
          this.initial2Completed()
            ? 'border-success/40 bg-success/5'
            : 'border-warning/50 bg-warning/5 ring-warning/20 dark:bg-warning/10 ring-1',
        )
      }
    
      get initial1BtnClass(): string {
        return cn(
          'shrink-0 gap-2 text-xs font-medium',
          this.initial1Completed()
            ? 'border-success/40 text-success hover:bg-success/10 text-success'
            : 'bg-warning hover:bg-warning/90 bg-warning dark:hover:bg-warning text-white',
        )
      }
    
      get initial2BtnClass(): string {
        return cn(
          'shrink-0 gap-2 text-xs font-medium',
          this.initial2Completed()
            ? 'border-success/40 text-success hover:bg-success/10 text-success'
            : 'bg-warning hover:bg-warning/90 bg-warning dark:hover:bg-warning text-white',
        )
      }
    
      get tenantBoxClass(): string {
        return cn(
          'rounded-lg border p-4 transition-colors duration-200',
          this.isSigned() ? 'border-success/40 bg-success/5' : 'border-border bg-muted/10 border-dashed',
        )
      }
    
      get tenantPreviewClass(): string {
        return cn(
          'my-3 rounded-md border p-3 text-center transition-colors',
          this.isSigned() ? 'bg-card border-success/40' : 'border-muted-foreground/30 bg-muted/20 border-dashed',
        )
      }
    
      get signaturePreviewClass(): string {
        return cn(
          'text-foreground text-2xl select-none',
          this.signatureFont() === 'serif' && 'font-medium tracking-wide italic',
          this.signatureFont() === 'script' && 'font-medium tracking-widest italic',
          this.signatureFont() === 'sans' && 'font-semibold tracking-tight',
        )
      }
    
      get signRowClass(): string {
        return cn(
          'flex items-center justify-between rounded-lg border p-2.5 text-xs',
          this.isSigned() ? 'border-success/40 bg-success/5' : 'border-border/80 bg-card',
        )
      }
    
      get signBadgeClass(): string {
        return cn('text-xs', this.isSigned() ? 'border-success/30 text-success' : 'text-muted-foreground')
      }
    
      checkDotClass(done: boolean, muted = false): string {
        return cn(
          'flex size-5 items-center justify-center rounded-full text-xs font-semibold',
          done
            ? 'bg-success bg-success text-white'
            : muted
              ? 'bg-muted text-muted-foreground'
              : 'bg-warning/20 text-warning',
        )
      }
    
      checkBadgeClass(done: boolean): string {
        return cn('text-xs', done ? 'border-success/30 text-success' : 'text-warning')
      }
    
      fontTabClass(font: SignatureFont): string {
        return cn(
          'rounded-md py-1 text-center text-xs font-medium transition-colors',
          this.signatureFont() === font
            ? 'bg-card text-foreground shadow-xs'
            : 'text-muted-foreground hover:text-foreground',
        )
      }
    
      toggleInitial1(): void {
        if (this.isSigned()) return
        this.initial1Completed.update((v) => !v)
      }
    
      toggleInitial2(): void {
        if (this.isSigned()) return
        this.initial2Completed.update((v) => !v)
      }
    
      signAgreement(): void {
        if (!this.canSign()) return
        this.isSigned.set(true)
      }
    
      resetWorkflow(): void {
        this.initial1Completed.set(false)
        this.initial2Completed.set(false)
        this.isSigned.set(false)
        this.downloadSuccess.set(false)
      }
    
      handleDownload(): void {
        this.downloadSuccess.set(true)
        clearTimeout(this.downloadTimer)
        this.downloadTimer = setTimeout(() => this.downloadSuccess.set(false), 2500)
      }
    
      handlePrint(): void {
        if (typeof window !== 'undefined') window.print()
      }
    
      scrollToInitial1(): void {
        const el = document.getElementById('initial-marker-1') as HTMLElement | null
        el?.scrollIntoView?.({ behavior: 'smooth', block: 'center' })
      }
    
      scrollToInitial2(): void {
        const el = document.getElementById('initial-marker-2') as HTMLElement | null
        el?.scrollIntoView?.({ behavior: 'smooth', block: 'center' })
      }
    }
    
  • src/app/components/blocks/lease-agreement-sign/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/lease-agreement-sign.json