Menu

Change language

GitHub repo

Legal Policy Page

blocklegal

Privacy Policy and legal documentation layout with top metadata header, version switcher, sticky table of contents navigation, and structured compliance sections for GDPR and CCPA.

Installation

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

Variants

Loading interactive previews…

Schema

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

PolicyTocItem
interface PolicyTocItem {
  id: string
  label: string
}

Files installed (2)

  • src/app/components/blocks/legal-policy-page/legal-policy-page.component.ts25.6 kB
    // Privacy Policy and legal documentation layout with top metadata header, version switcher,
    // sticky table of contents navigation, and structured compliance sections for GDPR and CCPA.
    // Port of the React block 1:1.
    import { AfterViewInit, ChangeDetectionStrategy, Component, Input, OnDestroy, signal } from '@angular/core'
    import {
      CheckCircle2,
      FileText,
      LucideAngularModule,
      Mail,
      MapPin,
      Pencil,
      Printer,
      ShieldCheck,
      Sliders,
      Trash2,
    } 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 {
      UiSelectComponent,
      UiSelectContentComponent,
      UiSelectItemComponent,
      UiSelectTriggerComponent,
      UiSelectValueComponent,
    } from '@/ui/select/select.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    
    export interface PolicyTocItem {
      id: string
      label: string
    }
    
    export const tableOfContents: PolicyTocItem[] = [
      { id: 'introduction', label: '1. Introduction & Scope' },
      { id: 'information-collected', label: '2. Information We Collect' },
      { id: 'data-usage', label: '3. How We Use Your Data' },
      { id: 'third-parties', label: '4. Data Sharing & Third Parties' },
      { id: 'your-rights', label: '5. Your Rights & Choices' },
      { id: 'contact-dpo', label: '6. Contact & Data Protection Officer' },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-legal-policy-page, [ui-legal-policy-page]',
      standalone: true,
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiSelectComponent,
        UiSelectContentComponent,
        UiSelectItemComponent,
        UiSelectTriggerComponent,
        UiSelectValueComponent,
        UiSeparatorComponent,
      ],
      template: `
        <div data-slot="legal-policy-page" [class]="rootClass">
          <div class="mx-auto max-w-6xl px-4 py-8 sm:px-6 sm:py-12 lg:px-8">
            <!-- Top Header -->
            <div class="flex flex-col gap-6 sm:flex-row sm:items-end sm:justify-between">
              <div class="space-y-2">
                <span ui-badge variant="outline" class="font-mono text-xs">Legal &amp; Compliance</span>
                <h1 class="text-3xl font-bold tracking-tight sm:text-4xl">Privacy Policy</h1>
                <p class="text-muted-foreground text-sm">
                  Effective date: August 15, 2026 • Current version: {{ selectedVersion() }}
                </p>
              </div>
    
              <div class="flex flex-wrap items-center gap-3">
                <ui-select [value]="selectedVersion()" (valueChange)="selectedVersion.set($event)">
                  <button ui-select-trigger class="w-40 text-xs" aria-label="Select policy version">
                    <ui-select-value placeholder="Select version" />
                  </button>
                  <ui-select-content>
                    <ui-select-item value="v2.4">v2.4 (Current)</ui-select-item>
                    <ui-select-item value="v2.3">v2.3 (July 2025)</ui-select-item>
                    <ui-select-item value="v2.0">v2.0 (Jan 2025)</ui-select-item>
                  </ui-select-content>
                </ui-select>
    
                <button ui-button variant="outline" size="sm" class="gap-1.5" (click)="handlePrint()">
                  <lucide-icon [img]="Printer" class="size-4" />
                  <span>Print / PDF</span>
                </button>
              </div>
            </div>
    
            <div ui-separator class="my-8"></div>
    
            <!-- 2-Column Documentation Layout -->
            <div class="grid grid-cols-1 gap-10 lg:grid-cols-12 lg:gap-12">
              <!-- Sticky Sidebar Navigation -->
              <aside class="space-y-6 lg:sticky lg:top-8 lg:col-span-4 lg:self-start xl:col-span-3">
                <div class="border-border bg-card rounded-lg border p-4 shadow-xs">
                  <p class="text-muted-foreground px-2 pb-2 text-xs font-semibold tracking-wider uppercase">
                    Table of Contents
                  </p>
                  <nav aria-label="Table of contents">
                    <ul class="space-y-1">
                      @for (item of toc; track item.id) {
                        <li>
                          <button type="button" [class]="tocBtnClass(item.id)" (click)="scrollToSection(item.id)">
                            {{ item.label }}
                          </button>
                        </li>
                      }
                    </ul>
                  </nav>
                </div>
    
                <div ui-card class="bg-muted/30 border-border">
                  <div ui-card-header class="p-4 pb-2">
                    <div class="flex items-center gap-2">
                      <lucide-icon [img]="ShieldCheck" class="text-primary size-4" />
                      <h3 ui-card-title class="text-xs font-semibold">Privacy Questions?</h3>
                    </div>
                  </div>
                  <div ui-card-content class="text-muted-foreground space-y-2 p-4 pt-0 text-xs">
                    <p>Need clarification on our data handling practices or DPAs?</p>
                    <a
                      href="mailto:[email protected]"
                      class="text-primary flex min-h-6 items-center font-medium hover:underline"
                    >
                      privacy&#64;acmecorp.com
                    </a>
                  </div>
                </div>
              </aside>
    
              <!-- Right Main Content -->
              <main class="space-y-12 lg:col-span-8 xl:col-span-9">
                <section id="introduction" class="scroll-mt-8 space-y-4">
                  <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">1. Introduction &amp; Scope</h2>
                  <p class="text-muted-foreground text-sm leading-relaxed">
                    At Acme Corp (“we”, “our”, or “us”), safeguarding your privacy and maintaining customer trust is core to
                    everything we build. This Privacy Policy sets forth our policies regarding the collection, processing,
                    storage, and disclosure of personal data when you access or use our developer platform, APIs, web
                    interfaces, and documentation (collectively, the “Services”).
                  </p>
                  <p class="text-muted-foreground text-sm leading-relaxed">
                    This policy applies to all registered workspace members, API consumers, and website visitors. By
                    accessing our Services, you acknowledge that you have read and understood this Privacy Policy.
                  </p>
    
                  <div class="border-primary/20 bg-primary/5 rounded-lg border p-4 sm:p-5">
                    <div class="flex items-start gap-3">
                      <div class="bg-primary/10 text-primary mt-0.5 rounded-md p-2">
                        <lucide-icon [img]="ShieldCheck" class="size-5 shrink-0" />
                      </div>
                      <div class="space-y-2">
                        <h3 class="text-foreground text-sm font-semibold">Our Core Privacy Commitments</h3>
                        <ul class="text-muted-foreground space-y-1.5 text-xs sm:text-sm">
                          <li class="flex items-center gap-2">
                            <lucide-icon [img]="CheckCircle2" class="text-primary size-4 shrink-0" />
                            <span>
                              <strong>Zero Data Monetization:</strong> We never sell, rent, or trade your personal data to
                              third-party brokers or advertisers.
                            </span>
                          </li>
                          <li class="flex items-center gap-2">
                            <lucide-icon [img]="CheckCircle2" class="text-primary size-4 shrink-0" />
                            <span>
                              <strong>End-to-End Encryption:</strong> Customer databases and authentication keys are
                              protected with AES-256 at rest and TLS 1.3 in transit.
                            </span>
                          </li>
                          <li class="flex items-center gap-2">
                            <lucide-icon [img]="CheckCircle2" class="text-primary size-4 shrink-0" />
                            <span>
                              <strong>Portability &amp; Erasure:</strong> Export your data archive in standard JSON or
                              trigger permanent account deletion at any time.
                            </span>
                          </li>
                        </ul>
                      </div>
                    </div>
                  </div>
                </section>
    
                <section id="information-collected" class="scroll-mt-8 space-y-4">
                  <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">2. Information We Collect</h2>
                  <p class="text-muted-foreground text-sm leading-relaxed">
                    We collect information strictly necessary to fulfill our service commitments, secure account access, and
                    meet statutory compliance obligations.
                  </p>
    
                  <div class="border-border overflow-hidden rounded-lg border">
                    <div class="overflow-x-auto">
                      <table class="w-full text-left text-xs sm:text-sm">
                        <thead class="bg-muted/50 border-border border-b font-semibold">
                          <tr>
                            <th scope="col" class="px-4 py-3">Data Category</th>
                            <th scope="col" class="px-4 py-3">Collected Information</th>
                            <th scope="col" class="px-4 py-3">Purpose &amp; Legal Basis</th>
                          </tr>
                        </thead>
                        <tbody class="divide-border divide-y">
                          <tr class="hover:bg-muted/30 transition-colors">
                            <td class="text-foreground px-4 py-3 align-top font-medium whitespace-nowrap">
                              Account Identity
                            </td>
                            <td class="text-muted-foreground px-4 py-3 align-top">
                              Full name, email address, password hash, organization name, role, API keys.
                            </td>
                            <td class="text-muted-foreground px-4 py-3 align-top">
                              Authentication and contractual service delivery (GDPR Art. 6(1)(b)).
                            </td>
                          </tr>
                          <tr class="hover:bg-muted/30 transition-colors">
                            <td class="text-foreground px-4 py-3 align-top font-medium whitespace-nowrap">
                              Billing &amp; Payments
                            </td>
                            <td class="text-muted-foreground px-4 py-3 align-top">
                              Billing address, tax ID, payment transaction tokens (card numbers tokenized by Stripe).
                            </td>
                            <td class="text-muted-foreground px-4 py-3 align-top">
                              Payment processing, invoice generation, tax compliance (GDPR Art. 6(1)(c)).
                            </td>
                          </tr>
                          <tr class="hover:bg-muted/30 transition-colors">
                            <td class="text-foreground px-4 py-3 align-top font-medium whitespace-nowrap">
                              Usage &amp; Telemetry
                            </td>
                            <td class="text-muted-foreground px-4 py-3 align-top">
                              IP address, client user agent, API endpoint invocations, response latencies, error logs.
                            </td>
                            <td class="text-muted-foreground px-4 py-3 align-top">
                              Security monitoring, rate limiting, and system stability (GDPR Art. 6(1)(f)).
                            </td>
                          </tr>
                          <tr class="hover:bg-muted/30 transition-colors">
                            <td class="text-foreground px-4 py-3 align-top font-medium whitespace-nowrap">
                              Cookies &amp; Storage
                            </td>
                            <td class="text-muted-foreground px-4 py-3 align-top">
                              Session authentication tokens, theme preference state, consent audit records.
                            </td>
                            <td class="text-muted-foreground px-4 py-3 align-top">
                              Session persistence, interface customization, and consent tracking.
                            </td>
                          </tr>
                        </tbody>
                      </table>
                    </div>
                  </div>
                </section>
    
                <section id="data-usage" class="scroll-mt-8 space-y-4">
                  <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">3. How We Use Your Data</h2>
                  <p class="text-muted-foreground text-sm leading-relaxed">
                    We process personal data only when an explicit legal basis exists under applicable regulations.
                    Specifically, your information is used for:
                  </p>
                  <ul class="grid grid-cols-1 gap-3 sm:grid-cols-2">
                    @for (use of dataUses; track use.title) {
                      <li class="border-border bg-card flex items-start gap-3 rounded-lg border p-3.5 shadow-xs">
                        <lucide-icon [img]="CheckCircle2" class="text-primary mt-0.5 size-4 shrink-0" />
                        <div class="text-xs sm:text-sm">
                          <strong class="text-foreground font-semibold">{{ use.title }}:</strong>
                          <p class="text-muted-foreground mt-0.5">{{ use.body }}</p>
                        </div>
                      </li>
                    }
                  </ul>
                </section>
    
                <section id="third-parties" class="scroll-mt-8 space-y-4">
                  <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">
                    4. Data Sharing &amp; Third Parties (Sub-Processors)
                  </h2>
                  <p class="text-muted-foreground text-sm leading-relaxed">
                    We do not sell user data. To maintain high-availability infrastructure, we engage vetted sub-processors
                    bound by Data Processing Agreements (DPAs) incorporating standard contractual clauses (SCCs).
                  </p>
    
                  <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
                    @for (sub of subProcessors; track sub.name) {
                      <div ui-card class="border-border bg-card">
                        <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">{{ sub.name }}</h3>
                            <span ui-badge variant="secondary" class="text-xs font-normal">{{ sub.tag }}</span>
                          </div>
                          <p ui-card-description class="text-xs">{{ sub.blurb }}</p>
                        </div>
                        <div ui-card-content class="text-muted-foreground space-y-1 p-4 pt-1 text-xs">
                          <div class="flex justify-between">
                            <span class="text-foreground font-medium">Location:</span>
                            <span>{{ sub.location }}</span>
                          </div>
                          <div class="flex justify-between">
                            <span class="text-foreground font-medium">Data Shared:</span>
                            <span>{{ sub.shared }}</span>
                          </div>
                        </div>
                      </div>
                    }
                  </div>
                </section>
    
                <section id="your-rights" class="scroll-mt-8 space-y-4">
                  <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">5. Your Rights &amp; Choices</h2>
                  <p class="text-muted-foreground text-sm leading-relaxed">
                    Under GDPR (Articles 15–22), CCPA/CPRA, and related global regulations, you possess specific enforceable
                    rights regarding your personal records.
                  </p>
    
                  <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
                    <div ui-card class="border-border bg-card">
                      <div ui-card-header class="p-4 pb-2">
                        <div class="flex items-center gap-2">
                          <lucide-icon [img]="FileText" class="text-primary size-4" />
                          <h3 ui-card-title class="text-sm font-semibold">Right to Access &amp; Portability</h3>
                        </div>
                      </div>
                      <div ui-card-content class="text-muted-foreground p-4 pt-1 text-xs sm:text-sm">
                        Request a complete machine-readable copy (JSON) of your account data, logs, and billing history.
                      </div>
                    </div>
    
                    <div ui-card class="border-border bg-card">
                      <div ui-card-header class="p-4 pb-2">
                        <div class="flex items-center gap-2">
                          <lucide-icon [img]="Pencil" class="text-primary size-4" />
                          <h3 ui-card-title class="text-sm font-semibold">Right to Rectification</h3>
                        </div>
                      </div>
                      <div ui-card-content class="text-muted-foreground p-4 pt-1 text-xs sm:text-sm">
                        Update or correct inaccurate profile details, billing addresses, and organization contacts in
                        settings.
                      </div>
                    </div>
    
                    <div ui-card class="border-border bg-card">
                      <div ui-card-header class="p-4 pb-2">
                        <div class="flex items-center gap-2">
                          <lucide-icon [img]="Trash2" class="text-destructive size-4" />
                          <h3 ui-card-title class="text-sm font-semibold">Right to Erasure (To Be Forgotten)</h3>
                        </div>
                      </div>
                      <div ui-card-content class="text-muted-foreground p-4 pt-1 text-xs sm:text-sm">
                        Request permanent deletion of your account and personal identifiers within 30 calendar days.
                      </div>
                    </div>
    
                    <div ui-card class="border-border bg-card">
                      <div ui-card-header class="p-4 pb-2">
                        <div class="flex items-center gap-2">
                          <lucide-icon [img]="Sliders" class="text-primary size-4" />
                          <h3 ui-card-title class="text-sm font-semibold">Right to Restrict &amp; Opt-Out</h3>
                        </div>
                      </div>
                      <div ui-card-content class="text-muted-foreground p-4 pt-1 text-xs sm:text-sm">
                        Opt out of non-essential telemetry, unsubscribe from product updates, and configure cookie
                        preferences.
                      </div>
                    </div>
                  </div>
                </section>
    
                <section id="contact-dpo" class="scroll-mt-8 space-y-4">
                  <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">
                    6. Contact &amp; Data Protection Officer
                  </h2>
                  <p class="text-muted-foreground text-sm leading-relaxed">
                    If you have inquiries, complaints, or formal requests regarding this policy, contact our designated Data
                    Protection Officer.
                  </p>
    
                  <div ui-card class="border-border bg-card">
                    <div ui-card-header class="p-5 pb-3">
                      <div class="flex flex-wrap items-center justify-between gap-2">
                        <h3 ui-card-title class="text-base font-semibold">Acme Data Protection Officer</h3>
                        <span ui-badge variant="outline" class="font-mono text-xs">30-Day Response SLA</span>
                      </div>
                      <p ui-card-description class="text-xs sm:text-sm">
                        Designated compliance contact under GDPR Article 37 and international privacy statutes.
                      </p>
                    </div>
                    <div ui-card-content class="text-muted-foreground space-y-4 p-5 pt-0 text-xs sm:text-sm">
                      <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
                        <div class="flex items-start gap-2.5">
                          <lucide-icon [img]="Mail" class="text-muted-foreground mt-0.5 size-4 shrink-0" />
                          <div>
                            <p class="text-foreground font-medium">Email Inquiries</p>
                            <a
                              href="mailto:[email protected]"
                              class="text-primary inline-flex min-h-6 items-center font-medium hover:underline"
                            >
                              dpo&#64;acmecorp.com
                            </a>
                            <p class="text-muted-foreground mt-0.5 text-xs">PGP key available upon request</p>
                          </div>
                        </div>
                        <div class="flex items-start gap-2.5">
                          <lucide-icon [img]="MapPin" class="text-muted-foreground mt-0.5 size-4 shrink-0" />
                          <div>
                            <p class="text-foreground font-medium">Physical Address</p>
                            <p>Acme Corporation</p>
                            <p>Attn: Legal &amp; Compliance</p>
                            <p>500 Market St, Suite 300</p>
                            <p>San Francisco, CA 94105, USA</p>
                          </div>
                        </div>
                      </div>
                      <div ui-separator></div>
                      <p class="text-muted-foreground text-xs leading-relaxed">
                        If you reside in the European Economic Area (EEA) and believe that our data processing infringes
                        applicable data protection law, you have the statutory right to lodge a complaint with your local
                        supervisory authority.
                      </p>
                    </div>
                  </div>
                </section>
              </main>
            </div>
          </div>
        </div>
      `,
    })
    export class UiLegalPolicyPageComponent implements AfterViewInit, OnDestroy {
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly FileText = FileText
      protected readonly Mail = Mail
      protected readonly MapPin = MapPin
      protected readonly Pencil = Pencil
      protected readonly Printer = Printer
      protected readonly ShieldCheck = ShieldCheck
      protected readonly Sliders = Sliders
      protected readonly Trash2 = Trash2
    
      protected readonly toc = tableOfContents
    
      protected readonly dataUses = [
        {
          title: 'Service Operation',
          body: 'Managing user authentication, executing API workloads, and delivering developer tooling.',
        },
        {
          title: 'Security & Fraud Prevention',
          body: 'Detecting suspicious sign-ins, preventing credential abuse, and enforcing rate limits.',
        },
        {
          title: 'Billing & Invoicing',
          body: 'Processing subscription payments, issuing VAT invoices, and maintaining financial records.',
        },
        {
          title: 'Transactional Communications',
          body: 'Sending password reset links, security audit notices, and critical maintenance alerts.',
        },
        {
          title: 'Performance Analytics',
          body: 'Analyzing aggregated telemetry to reduce edge query latency and improve uptime.',
        },
        {
          title: 'Statutory Compliance',
          body: 'Fulfilling mandatory tax disclosures, court orders, and regulatory audit standards.',
        },
      ]
    
      protected readonly subProcessors = [
        {
          name: 'Amazon Web Services',
          tag: 'Cloud Hosting',
          blurb: 'Database storage and edge computation',
          location: 'US & EU (Frankfurt)',
          shared: 'Encrypted databases, backups',
        },
        {
          name: 'Stripe, Inc.',
          tag: 'Payments',
          blurb: 'PCI-DSS Level 1 payment gateway',
          location: 'United States / Global',
          shared: 'Billing info, payment tokens',
        },
        {
          name: 'Postmark',
          tag: 'Transactional Email',
          blurb: 'System emails & security alerts',
          location: 'United States',
          shared: 'Recipient email, delivery logs',
        },
        {
          name: 'Sentry / Datadog',
          tag: 'Observability',
          blurb: 'Application monitoring & crash tracing',
          location: 'US & European Union',
          shared: 'Anonymized crash traces, IP hash',
        },
      ]
    
      @Input('class') className?: string
    
      readonly activeSection = signal('introduction')
      readonly selectedVersion = signal('v2.4')
      private observer: IntersectionObserver | null = null
    
      get rootClass(): string {
        return cn('bg-background text-foreground w-full', this.className)
      }
    
      tocBtnClass(id: string): string {
        return cn(
          'focus-visible:ring-ring w-full rounded-md px-2.5 py-1.5 text-left text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',
          this.activeSection() === id
            ? 'bg-primary/10 text-primary font-semibold'
            : 'text-muted-foreground hover:bg-muted/60 hover:text-foreground',
        )
      }
    
      scrollToSection(id: string): void {
        this.activeSection.set(id)
        const el = document.getElementById(id) as HTMLElement | null
        el?.scrollIntoView?.({ behavior: 'smooth' })
      }
    
      handlePrint(): void {
        if (typeof window !== 'undefined') window.print()
      }
    
      ngAfterViewInit(): void {
        if (typeof window === 'undefined' || !('IntersectionObserver' in window)) return
        this.observer = new IntersectionObserver(
          (entries) => {
            for (const entry of entries) {
              if (entry.isIntersecting) this.activeSection.set(entry.target.id)
            }
          },
          { rootMargin: '-10% 0px -70% 0px' },
        )
        for (const item of tableOfContents) {
          const el = document.getElementById(item.id)
          if (el) this.observer.observe(el)
        }
      }
    
      ngOnDestroy(): void {
        this.observer?.disconnect()
        this.observer = null
      }
    }
    
  • src/app/components/blocks/legal-policy-page/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/legal-policy-page.json