Lease Agreement Sign
blockreal-estateDocuSign-style digital residential lease agreement signature and review workflow with terms summary, interactive initial checkpoints, sticky signature action sidebar, and cryptographic audit execution.
Installation
$pnpm dlx uipkge-ng@latest add lease-agreement-sign$npx uipkge-ng@latest add lease-agreement-sign$yarn dlx uipkge-ng@latest add lease-agreement-sign$bunx uipkge-ng@latest add lease-agreement-signManifest:
https://uipkge.dev/r/angular/lease-agreement-sign.jsonInstalls to:src/app/components/blocks/lease-agreement-sign/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
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 & 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 & 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 & 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@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@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 & 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 & 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 & 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 & 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 & 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 & 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@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 & Fees)</p> <p class="text-muted-foreground text-xs">Acknowledges $3,450 rent & 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 & 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 & 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
export { UiLeaseAgreementSignComponent, type SignatureFont } from './lease-agreement-sign.component'
Raw manifest:https://uipkge.dev/r/angular/lease-agreement-sign.json