Patient Intake Form
blockhealthcareHIPAA-compliant new patient registration and medical history questionnaire in a SectionCard. Features a 4-step Stepper (Personal Information → Medical History → Insurance & Billing → Consent & Sign), demographic inputs with biological sex and emergency contact, chronic conditions checklist with allergy details and medication textareas, insurance carrier selection with card photo upload dropzones, regulatory HIPAA disclosure accordion with electronic signature pad, and a post-submission confirmation receipt with patient ID.
Installation
$pnpm dlx uipkge-ng@latest add patient-intake-form$npx uipkge-ng@latest add patient-intake-form$yarn dlx uipkge-ng@latest add patient-intake-form$bunx uipkge-ng@latest add patient-intake-formManifest:
https://uipkge.dev/r/angular/patient-intake-form.jsonInstalls to:src/app/components/blocks/patient-intake-form/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
PatientIntakeDatainterface PatientIntakeData {
firstName: string
middleName: string
lastName: string
preferredName: string
dob: string
biologicalSex: string
maritalStatus: string
phone: string
email: string
streetAddress: string
city: string
state: string
zipCode: string
emergencyName: string
emergencyRelationship: string
emergencyPhone: string
chronicConditions: string[]
hasAllergies: boolean
allergyDetails: string
currentMedications: string
pastSurgeries: string
primaryCarePhysician: string
clinicPhone: string
insuranceCarrier: string
memberId: string
groupNumber: string
policyholderName: string
policyholderRelationship: string
frontCardUploaded: boolean
backCardUploaded: boolean
hipaaConsent: boolean
treatmentConsent: boolean
telehealthConsent: boolean
signatureName: string
signatureDate: string
signerRole: string
}Files installed (2)
src/app/components/blocks/patient-intake-form/patient-intake-form.component.ts49.2 kB
// HIPAA-compliant new patient registration in a SectionCard: a 4-step Stepper (Personal // Information → Medical History → Insurance & Billing → Consent & Sign) with demographic // inputs, chronic-conditions checklist, allergy/medication textareas, carrier selection with // card-photo dropzones, HIPAA disclosures with an electronic signature stamp, and a // post-submission confirmation receipt. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, computed, signal } from '@angular/core' import { Activity, AlertTriangle, Building2, Calendar, Check, ChevronLeft, ChevronRight, CreditCard, Download, FileCheck, FileText, Lock, LucideAngularModule, Phone, RotateCcw, ShieldCheck, Stethoscope, User, UserCheck, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCheckboxComponent } from '@/ui/checkbox/checkbox.component' import { UiInputComponent } from '@/ui/input/input.component' import { UiLabelComponent } from '@/ui/label/label.component' import { UiSectionCardComponent } from '@/ui/section-card/section-card.component' import { UiSelectComponent, UiSelectContentComponent, UiSelectItemComponent, UiSelectTriggerComponent, UiSelectValueComponent, } from '@/ui/select/select.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiStepperComponent, type StepperStep } from '@/ui/stepper/stepper.component' import { UiTextareaComponent } from '@/ui/textarea/textarea.component' export interface PatientIntakeData { firstName: string middleName: string lastName: string preferredName: string dob: string biologicalSex: string maritalStatus: string phone: string email: string streetAddress: string city: string state: string zipCode: string emergencyName: string emergencyRelationship: string emergencyPhone: string chronicConditions: string[] hasAllergies: boolean allergyDetails: string currentMedications: string pastSurgeries: string primaryCarePhysician: string clinicPhone: string insuranceCarrier: string memberId: string groupNumber: string policyholderName: string policyholderRelationship: string frontCardUploaded: boolean backCardUploaded: boolean hipaaConsent: boolean treatmentConsent: boolean telehealthConsent: boolean signatureName: string signatureDate: string signerRole: string } const steps: StepperStep[] = [ { id: 1, title: 'Personal Information', description: 'Demographics & Contact' }, { id: 2, title: 'Medical History', description: 'Conditions & Medications' }, { id: 3, title: 'Insurance & Billing', description: 'Coverage & Card Photos' }, { id: 4, title: 'Consent & Sign', description: 'HIPAA & Authorization' }, ] const CHRONIC_CONDITIONS = [ { id: 'hypertension', label: 'Hypertension (High Blood Pressure)' }, { id: 'diabetes', label: 'Diabetes (Type 1 or Type 2)' }, { id: 'asthma', label: 'Asthma / Respiratory Conditions' }, { id: 'heart_disease', label: 'Coronary Artery / Heart Disease' }, { id: 'arthritis', label: 'Arthritis / Chronic Joint Pain' }, { id: 'cancer', label: 'Cancer / Remission History' }, { id: 'thyroid', label: 'Thyroid / Endocrine Disorder' }, { id: 'anxiety_depression', label: 'Depression / Clinical Anxiety' }, { id: 'kidney_disease', label: 'Chronic Kidney Disease' }, { id: 'gerd', label: 'Acid Reflux / GERD' }, ] const INSURANCE_CARRIERS = [ { id: 'bcbs', label: 'BlueCross BlueShield' }, { id: 'aetna', label: 'Aetna Health' }, { id: 'cigna', label: 'Cigna Healthcare' }, { id: 'united', label: 'UnitedHealthcare' }, { id: 'medicare', label: 'Medicare Part A / B' }, { id: 'medicaid', label: 'Medicaid' }, { id: 'humana', label: 'Humana' }, { id: 'kaiser', label: 'Kaiser Permanente' }, { id: 'self_pay', label: 'Self-Pay (Uninsured)' }, ] const DEFAULT_FORM: PatientIntakeData = { firstName: 'Eleanor', middleName: 'Rose', lastName: 'Vance', preferredName: 'Ellie', dob: '1988-10-14', biologicalSex: 'female', maritalStatus: 'married', phone: '(555) 234-8921', email: 'eleanor.vance@example.com', streetAddress: '742 Evergreen Terrace', city: 'Springfield', state: 'OR', zipCode: '97477', emergencyName: 'Marcus Vance', emergencyRelationship: 'spouse', emergencyPhone: '(555) 987-6543', chronicConditions: ['hypertension', 'asthma'], hasAllergies: true, allergyDetails: 'Penicillin (Anaphylaxis/hives), Sulfa antibiotics (severe skin rash)', currentMedications: 'Lisinopril 10mg PO once daily in morning, Albuterol HFA 90mcg inhaler 1-2 puffs Q4H PRN for bronchospasm, Multivitamin daily', pastSurgeries: 'Appendectomy (Laparoscopic, 2016), Right knee arthroscopy / meniscus debridement (2021)', primaryCarePhysician: 'Dr. Robert Chen, MD', clinicPhone: '(555) 601-2290 — Northwest Health Partners', insuranceCarrier: 'bcbs', memberId: 'XEH-902814891', groupNumber: 'GRP-44028', policyholderName: 'Eleanor Vance', policyholderRelationship: 'self', frontCardUploaded: true, backCardUploaded: true, hipaaConsent: true, treatmentConsent: true, telehealthConsent: true, signatureName: 'Eleanor R. Vance', signatureDate: '2026-08-21', signerRole: 'patient', } @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-patient-intake-form, [ui-patient-intake-form]', standalone: true, // React renders the SectionCard itself: the host stays out of layout and `class` goes to the card. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCheckboxComponent, UiInputComponent, UiLabelComponent, UiSectionCardComponent, UiSelectComponent, UiSelectContentComponent, UiSelectItemComponent, UiSelectTriggerComponent, UiSelectValueComponent, UiSeparatorComponent, UiStepperComponent, UiTextareaComponent, ], template: ` <ui-section-card data-slot="patient-intake-form" title="New Patient Registration" description="HIPAA-compliant digital intake questionnaire & clinical onboarding portal." [class]="cardClass" > <div slot="header-action" class="flex items-center gap-2"> <span ui-badge wrap variant="outline" class="border-success/30 bg-success/10 text-success gap-1 py-0.5 text-xs font-medium" > <lucide-icon [img]="ShieldCheck" class="size-3.5" /> HIPAA Protected </span> <span ui-badge wrap variant="secondary" class="hidden font-mono text-xs sm:inline-flex"> Form #PHI-2026-894 </span> </div> @if (step() <= 4) { <ui-stepper [steps]="steps" [value]="step()" (valueChange)="onStepperInput($event)" class="mb-6" /> } <!-- STEP 1: Personal Information --> @if (step() === 1) { <div class="space-y-6"> <div class="border-border/80 bg-muted/40 flex items-start gap-3 rounded-lg border p-3.5 text-xs"> <lucide-icon [img]="ShieldCheck" class="text-primary mt-0.5 size-4 shrink-0" /> <div class="space-y-0.5"> <p class="text-foreground font-semibold">Protected Health Information (PHI) Notice</p> <p class="text-muted-foreground leading-relaxed"> All legal demographics and medical history submitted through this portal are encrypted in accordance with federal HIPAA Omnibus rules and directly synchronized with the clinic's Electronic Health Record (EHR). </p> </div> </div> <div class="space-y-3"> <h3 class="text-foreground flex items-center gap-2 text-xs font-semibold"> <lucide-icon [img]="User" class="text-primary size-3.5" /> Legal Patient Identification </h3> <div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4"> <div class="space-y-1.5 sm:col-span-1"> <label ui-label for="pi-first-name" class="text-xs">Legal First Name *</label> <ui-input id="pi-first-name" [value]="form().firstName" (valueChange)="setField('firstName', $event)" placeholder="First name" size="small" /> </div> <div class="space-y-1.5 sm:col-span-1"> <label ui-label for="pi-middle-name" class="text-xs">Middle Name</label> <ui-input id="pi-middle-name" [value]="form().middleName" (valueChange)="setField('middleName', $event)" placeholder="Middle name" size="small" /> </div> <div class="space-y-1.5 sm:col-span-1"> <label ui-label for="pi-last-name" class="text-xs">Legal Last Name *</label> <ui-input id="pi-last-name" [value]="form().lastName" (valueChange)="setField('lastName', $event)" placeholder="Last name" size="small" /> </div> <div class="space-y-1.5 sm:col-span-1"> <label ui-label for="pi-preferred-name" class="text-xs">Preferred Name</label> <ui-input id="pi-preferred-name" [value]="form().preferredName" (valueChange)="setField('preferredName', $event)" placeholder="Nickname / Preferred" size="small" /> </div> </div> </div> <ui-separator /> <div class="space-y-3"> <h3 class="text-foreground flex items-center gap-2 text-xs font-semibold"> <lucide-icon [img]="Calendar" class="text-primary size-3.5" /> Birth & Clinical Demographics </h3> <div class="grid grid-cols-1 gap-3 sm:grid-cols-3"> <div class="space-y-1.5"> <label ui-label for="pi-dob" class="text-xs">Date of Birth *</label> <ui-input id="pi-dob" [value]="form().dob" (valueChange)="setField('dob', $event)" type="date" size="small" /> </div> <div class="space-y-1.5"> <label ui-label for="pi-biological-sex" class="text-xs">Biological Sex Assigned at Birth *</label> <ui-select [value]="form().biologicalSex" (valueChange)="setField('biologicalSex', $event)"> <button ui-select-trigger id="pi-biological-sex" size="sm" class="h-8 text-xs"> <ui-select-value placeholder="Select sex" /> </button> <ui-select-content> <ui-select-item value="female">Female</ui-select-item> <ui-select-item value="male">Male</ui-select-item> <ui-select-item value="intersex">Intersex</ui-select-item> <ui-select-item value="undisclosed">Prefer not to disclose</ui-select-item> </ui-select-content> </ui-select> </div> <div class="space-y-1.5"> <label ui-label for="pi-marital-status" class="text-xs">Marital Status</label> <ui-select [value]="form().maritalStatus" (valueChange)="setField('maritalStatus', $event)"> <button ui-select-trigger id="pi-marital-status" size="sm" class="h-8 text-xs"> <ui-select-value placeholder="Select status" /> </button> <ui-select-content> <ui-select-item value="single">Single</ui-select-item> <ui-select-item value="married">Married</ui-select-item> <ui-select-item value="divorced">Divorced</ui-select-item> <ui-select-item value="widowed">Widowed</ui-select-item> <ui-select-item value="other">Other</ui-select-item> </ui-select-content> </ui-select> </div> </div> </div> <ui-separator /> <div class="space-y-3"> <h3 class="text-foreground flex items-center gap-2 text-xs font-semibold"> <lucide-icon [img]="Phone" class="text-primary size-3.5" /> Contact & Residential Address </h3> <div class="grid grid-cols-1 gap-3 sm:grid-cols-2"> <div class="space-y-1.5"> <label ui-label for="pi-phone" class="text-xs">Mobile Phone Number *</label> <ui-input id="pi-phone" [value]="form().phone" (valueChange)="setField('phone', $event)" type="tel" placeholder="(555) 234-8921" size="small" /> </div> <div class="space-y-1.5"> <label ui-label for="pi-email" class="text-xs">Email Address *</label> <ui-input id="pi-email" [value]="form().email" (valueChange)="setField('email', $event)" type="email" placeholder="patient@example.com" size="small" /> </div> <div class="space-y-1.5 sm:col-span-2"> <label ui-label for="pi-address" class="text-xs">Street Address *</label> <ui-input id="pi-address" [value]="form().streetAddress" (valueChange)="setField('streetAddress', $event)" placeholder="Street, Apt / Suite" size="small" /> </div> <div class="grid grid-cols-1 gap-3 sm:col-span-2 sm:grid-cols-3"> <div class="col-span-1 space-y-1.5"> <label ui-label for="pi-city" class="text-xs">City</label> <ui-input id="pi-city" [value]="form().city" (valueChange)="setField('city', $event)" placeholder="City" size="small" /> </div> <div class="col-span-1 space-y-1.5"> <label ui-label for="pi-state" class="text-xs">State</label> <ui-input id="pi-state" [value]="form().state" (valueChange)="setField('state', $event)" placeholder="State / Province" size="small" /> </div> <div class="col-span-1 space-y-1.5"> <label ui-label for="pi-zip" class="text-xs">ZIP Code</label> <ui-input id="pi-zip" [value]="form().zipCode" (valueChange)="setField('zipCode', $event)" placeholder="ZIP code" size="small" /> </div> </div> </div> </div> <ui-separator /> <div class="border-border bg-muted/20 space-y-3 rounded-lg border p-4"> <div class="flex flex-wrap items-center justify-between"> <div class="flex items-center gap-2"> <lucide-icon [img]="UserCheck" class="text-primary size-4" /> <h4 class="text-foreground text-xs font-semibold"> Emergency Contact (Designated Healthcare Surrogate) </h4> </div> <span ui-badge wrap variant="secondary" class="text-xs">Required</span> </div> <div class="grid grid-cols-1 gap-3 sm:grid-cols-3"> <div class="space-y-1.5"> <label ui-label for="pi-emg-name" class="text-xs">Contact Full Name *</label> <ui-input id="pi-emg-name" [value]="form().emergencyName" (valueChange)="setField('emergencyName', $event)" placeholder="Full legal name" size="small" /> </div> <div class="space-y-1.5"> <label ui-label for="pi-emg-rel" class="text-xs">Relationship to Patient *</label> <ui-select [value]="form().emergencyRelationship" (valueChange)="setField('emergencyRelationship', $event)" > <button ui-select-trigger id="pi-emg-rel" size="sm" class="h-8 text-xs"> <ui-select-value placeholder="Relationship" /> </button> <ui-select-content> <ui-select-item value="spouse">Spouse</ui-select-item> <ui-select-item value="parent">Parent / Guardian</ui-select-item> <ui-select-item value="child">Adult Child</ui-select-item> <ui-select-item value="sibling">Sibling</ui-select-item> <ui-select-item value="friend">Friend / Relative</ui-select-item> <ui-select-item value="other">Other Legal Representative</ui-select-item> </ui-select-content> </ui-select> </div> <div class="space-y-1.5"> <label ui-label for="pi-emg-phone" class="text-xs">Emergency Phone *</label> <ui-input id="pi-emg-phone" [value]="form().emergencyPhone" (valueChange)="setField('emergencyPhone', $event)" type="tel" placeholder="(555) 987-6543" size="small" /> </div> </div> </div> </div> } <!-- STEP 2: Medical History --> @if (step() === 2) { <div class="space-y-6"> <div class="space-y-3"> <div class="flex flex-wrap items-center justify-between"> <div class="flex items-center gap-2"> <lucide-icon [img]="Activity" class="text-primary size-4" /> <h3 class="text-foreground text-xs font-semibold">Chronic & Pre-Existing Health Conditions</h3> </div> <span ui-badge wrap variant="secondary" class="font-mono text-xs tabular-nums"> {{ form().chronicConditions.length }} selected </span> </div> <p class="text-muted-foreground text-xs"> Select any conditions diagnosed or treated in the past 5 years. If none apply, leave unchecked. </p> <div class="grid grid-cols-1 gap-2.5 sm:grid-cols-2"> @for (cond of chronicConditions; track cond.id) { <div class="border-border hover:bg-muted/30 flex items-center gap-2.5 rounded-lg border p-2.5 transition-colors" > <ui-checkbox [id]="'condition-' + cond.id" [checked]="form().chronicConditions.includes(cond.id)" (checkedChange)="toggleCondition(cond.id, $event)" /> <label ui-label [for]="'condition-' + cond.id" class="flex-1 cursor-pointer text-xs font-normal select-none" > {{ cond.label }} </label> </div> } </div> </div> <ui-separator /> <div class="border-border bg-muted/20 space-y-3 rounded-lg border p-4"> <div class="flex flex-wrap items-center justify-between"> <div class="flex items-center gap-2"> <lucide-icon [img]="AlertTriangle" class="text-warning size-4" /> <h4 class="text-foreground text-xs font-semibold">Known Drug, Latex, or Food Allergies</h4> </div> <div class="flex items-center gap-2"> <ui-checkbox id="mh-has-allergies" [checked]="form().hasAllergies" (checkedChange)="setField('hasAllergies', !!$event)" label="I have known allergies" /> </div> </div> @if (form().hasAllergies) { <div class="space-y-1.5 pt-1"> <label ui-label for="mh-allergy-details" class="text-xs"> List Specific Allergens & Adverse Reactions * </label> <ui-input id="mh-allergy-details" [value]="form().allergyDetails" (valueChange)="setField('allergyDetails', $event)" placeholder="e.g., Penicillin (Anaphylaxis), Sulfa (Severe Rash), Peanuts (Hives), Latex" size="small" /> </div> } @else { <p class="text-muted-foreground text-xs italic">No known drug, latex, or food allergies reported.</p> } </div> <ui-separator /> <div class="space-y-2"> <div class="flex items-center gap-2"> <lucide-icon [img]="Stethoscope" class="text-primary size-4" /> <label ui-label for="mh-medications" class="text-xs font-semibold"> Current Prescription & OTC Medications </label> </div> <ui-textarea id="mh-medications" [value]="form().currentMedications" (valueChange)="setField('currentMedications', $event)" [rows]="3" placeholder="Include drug name, dosage, and schedule (e.g., Lisinopril 10mg once daily in AM, Metformin 500mg BID with meals)..." class="text-xs" /> <p class="text-muted-foreground text-xs"> Include all daily vitamins, herbal supplements, inhalers, and injections. </p> </div> <ui-separator /> <div class="space-y-2"> <div class="flex items-center gap-2"> <lucide-icon [img]="FileText" class="text-primary size-4" /> <label ui-label for="mh-surgeries" class="text-xs font-semibold"> Past Surgeries, Major Injuries & Hospitalizations </label> </div> <ui-textarea id="mh-surgeries" [value]="form().pastSurgeries" (valueChange)="setField('pastSurgeries', $event)" [rows]="2" placeholder="List approximate year and procedure (e.g., Appendectomy 2016, Knee Arthroscopy 2021)..." class="text-xs" /> </div> <ui-separator /> <div class="space-y-3"> <h4 class="text-foreground text-xs font-semibold">Primary Care Provider / Referring Clinic</h4> <div class="grid grid-cols-1 gap-3 sm:grid-cols-2"> <div class="space-y-1.5"> <label ui-label for="mh-pcp-name" class="text-xs">Physician or Practice Name</label> <ui-input id="mh-pcp-name" [value]="form().primaryCarePhysician" (valueChange)="setField('primaryCarePhysician', $event)" placeholder="Dr. Robert Chen, MD" size="small" /> </div> <div class="space-y-1.5"> <label ui-label for="mh-clinic-phone" class="text-xs">Clinic Contact / Phone</label> <ui-input id="mh-clinic-phone" [value]="form().clinicPhone" (valueChange)="setField('clinicPhone', $event)" placeholder="(555) 601-2290" size="small" /> </div> </div> </div> </div> } <!-- STEP 3: Insurance & Billing --> @if (step() === 3) { <div class="space-y-6"> <div class="space-y-3"> <div class="flex flex-wrap items-center justify-between"> <div class="flex items-center gap-2"> <lucide-icon [img]="Building2" class="text-primary size-4" /> <h3 class="text-foreground text-xs font-semibold">Primary Medical Insurance Carrier</h3> </div> <span ui-badge wrap variant="outline" class="text-xs">Primary Plan</span> </div> <div class="grid grid-cols-1 gap-3 sm:grid-cols-3"> <div class="space-y-1.5"> <label ui-label for="ins-carrier" class="text-xs">Insurance Carrier *</label> <ui-select [value]="form().insuranceCarrier" (valueChange)="setField('insuranceCarrier', $event)"> <button ui-select-trigger id="ins-carrier" size="sm" class="h-8 text-xs"> <ui-select-value placeholder="Select carrier" /> </button> <ui-select-content> @for (carrier of insuranceCarriers; track carrier.id) { <ui-select-item [value]="carrier.id">{{ carrier.label }}</ui-select-item> } </ui-select-content> </ui-select> </div> <div class="space-y-1.5"> <label ui-label for="ins-member-id" class="text-xs">Member / Subscriber ID *</label> <ui-input id="ins-member-id" [value]="form().memberId" (valueChange)="setField('memberId', $event)" placeholder="e.g. XEH-902814891" size="small" /> </div> <div class="space-y-1.5"> <label ui-label for="ins-group-num" class="text-xs">Group Number</label> <ui-input id="ins-group-num" [value]="form().groupNumber" (valueChange)="setField('groupNumber', $event)" placeholder="e.g. GRP-44028" size="small" /> </div> </div> </div> <ui-separator /> <div class="space-y-3"> <h3 class="text-foreground flex items-center gap-2 text-xs font-semibold"> <lucide-icon [img]="User" class="text-primary size-3.5" /> Primary Policyholder Information </h3> <div class="grid grid-cols-1 gap-3 sm:grid-cols-2"> <div class="space-y-1.5"> <label ui-label for="ins-holder-name" class="text-xs">Primary Insured Full Legal Name *</label> <ui-input id="ins-holder-name" [value]="form().policyholderName" (valueChange)="setField('policyholderName', $event)" placeholder="Policyholder name" size="small" /> </div> <div class="space-y-1.5"> <label ui-label for="ins-holder-rel" class="text-xs">Relationship to Patient *</label> <ui-select [value]="form().policyholderRelationship" (valueChange)="setField('policyholderRelationship', $event)" > <button ui-select-trigger id="ins-holder-rel" size="sm" class="h-8 text-xs"> <ui-select-value placeholder="Relationship" /> </button> <ui-select-content> <ui-select-item value="self">Self (Patient is Policyholder)</ui-select-item> <ui-select-item value="spouse">Spouse</ui-select-item> <ui-select-item value="child">Child / Dependent</ui-select-item> <ui-select-item value="other">Other Legal Custodian</ui-select-item> </ui-select-content> </ui-select> </div> </div> </div> <ui-separator /> <div class="space-y-3"> <div class="flex flex-wrap items-center justify-between"> <div class="flex items-center gap-2"> <lucide-icon [img]="CreditCard" class="text-primary size-4" /> <h3 class="text-foreground text-xs font-semibold">Insurance Card Photo Verification</h3> </div> <span class="text-muted-foreground text-xs">PNG, JPG, PDF up to 10MB</span> </div> <p class="text-muted-foreground text-xs"> Provide high-resolution photos of both front and back sides of your insurance card for optical character recognition (OCR) and benefit eligibility verification. </p> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2"> <div class="border-border bg-card hover:bg-muted/20 flex flex-col justify-between rounded-xl border p-4 text-center transition-colors" > <div class="space-y-2 py-3"> <div class="bg-primary/10 text-primary mx-auto flex size-10 items-center justify-center rounded-full"> <lucide-icon [img]="CreditCard" class="size-5" /> </div> <div class="space-y-0.5"> <p class="text-foreground text-xs font-semibold">Front of Insurance Card</p> <p class="text-muted-foreground text-xs">Subscriber ID & carrier logo visible</p> </div> </div> <div class="border-border/60 bg-muted/40 flex flex-wrap items-center justify-between rounded-md border px-3 py-2 text-xs" > @if (form().frontCardUploaded) { <span class="text-success flex items-center gap-1.5 font-medium"> <lucide-icon [img]="Check" class="size-3.5" /> card_front_scan.jpg (2.4 MB) </span> } @else { <span class="text-muted-foreground">No file selected</span> } <button ui-button type="button" variant="outline" size="sm" class="h-7 text-xs font-medium" (click)="toggleUpload('frontCardUploaded')" > {{ form().frontCardUploaded ? 'Replace' : 'Upload Front' }} </button> </div> </div> <div class="border-border bg-card hover:bg-muted/20 flex flex-col justify-between rounded-xl border p-4 text-center transition-colors" > <div class="space-y-2 py-3"> <div class="bg-primary/10 text-primary mx-auto flex size-10 items-center justify-center rounded-full"> <lucide-icon [img]="CreditCard" class="size-5" /> </div> <div class="space-y-0.5"> <p class="text-foreground text-xs font-semibold">Back of Insurance Card</p> <p class="text-muted-foreground text-xs">Claims address & Rx BIN visible</p> </div> </div> <div class="border-border/60 bg-muted/40 flex flex-wrap items-center justify-between rounded-md border px-3 py-2 text-xs" > @if (form().backCardUploaded) { <span class="text-success flex items-center gap-1.5 font-medium"> <lucide-icon [img]="Check" class="size-3.5" /> card_back_scan.jpg (1.9 MB) </span> } @else { <span class="text-muted-foreground">No file selected</span> } <button ui-button type="button" variant="outline" size="sm" class="h-7 text-xs font-medium" (click)="toggleUpload('backCardUploaded')" > {{ form().backCardUploaded ? 'Replace' : 'Upload Back' }} </button> </div> </div> </div> </div> </div> } <!-- STEP 4: Consent & Sign --> @if (step() === 4) { <div class="space-y-6"> <div class="space-y-2"> <div class="flex flex-wrap items-center justify-between"> <div class="flex items-center gap-2"> <lucide-icon [img]="FileText" class="text-primary size-4" /> <h3 class="text-foreground text-xs font-semibold">Regulatory Disclosures & Legal Notice</h3> </div> <span ui-badge wrap variant="secondary" class="text-xs">Mandatory Acknowledgement</span> </div> <div class="border-border bg-muted/20 text-muted-foreground max-h-40 space-y-3 overflow-y-auto rounded-lg border p-3.5 text-xs leading-relaxed" > <div> <span class="text-foreground font-semibold">1. HIPAA Notice of Privacy Practices:</span> I acknowledge receipt and review of the Notice of Privacy Practices, which describes how my Protected Health Information (PHI) may be used and disclosed for clinical treatment, payment processing, and healthcare operations in strict compliance with the HIPAA Omnibus Rule (45 CFR § 164.520). </div> <div> <span class="text-foreground font-semibold">2. Informed Consent for Outpatient Treatment:</span> I voluntarily authorize the clinical physicians and allied healthcare team to perform diagnostic evaluations, blood tests, physical examinations, and outpatient clinical care deemed medically necessary. </div> <div> <span class="text-foreground font-semibold">3. Assignment of Benefits & Financial Agreement:</span> I assign all medical insurance benefits directly to the provider. I acknowledge that I remain financially responsible for copayments, deductibles, and non-covered services not adjudicated by my carrier. </div> </div> </div> <div class="divide-border divide-y rounded-lg border"> <div class="flex items-start gap-3 p-3.5"> <ui-checkbox id="cs-hipaa-consent" [checked]="form().hipaaConsent" (checkedChange)="setField('hipaaConsent', !!$event)" class="mt-0.5" /> <div class="space-y-0.5"> <label ui-label for="cs-hipaa-consent" class="cursor-pointer text-xs font-medium"> HIPAA Privacy Practices Acknowledgement * </label> <p class="text-muted-foreground text-xs"> I have read, understood, and accept the HIPAA Notice of Privacy Practices and consent to electronic medical record management. </p> </div> </div> <div class="flex items-start gap-3 p-3.5"> <ui-checkbox id="cs-treatment-consent" [checked]="form().treatmentConsent" (checkedChange)="setField('treatmentConsent', !!$event)" class="mt-0.5" /> <div class="space-y-0.5"> <label ui-label for="cs-treatment-consent" class="cursor-pointer text-xs font-medium"> Informed Treatment Consent & Benefit Assignment * </label> <p class="text-muted-foreground text-xs"> I authorize medical evaluation and agree to the assignment of insurance benefits to the clinical care provider. </p> </div> </div> <div class="flex items-start gap-3 p-3.5"> <ui-checkbox id="cs-telehealth-consent" [checked]="form().telehealthConsent" (checkedChange)="setField('telehealthConsent', !!$event)" class="mt-0.5" /> <div class="space-y-0.5"> <label ui-label for="cs-telehealth-consent" class="cursor-pointer text-xs font-medium"> Telehealth & Encrypted Digital Communications (Optional) </label> <p class="text-muted-foreground text-xs"> I consent to virtual video consultations and automated SMS/email appointment notifications and lab report alerts. </p> </div> </div> </div> <ui-separator /> <div class="space-y-4"> <div class="flex flex-wrap items-center justify-between"> <div class="flex items-center gap-2"> <lucide-icon [img]="Lock" class="text-primary size-4" /> <h3 class="text-foreground text-xs font-semibold">Electronic Signature & Digital Certification</h3> </div> <span ui-badge wrap variant="outline" class="border-primary/30 text-primary font-mono text-xs"> 256-bit Certified </span> </div> <div class="grid grid-cols-1 gap-3 sm:grid-cols-3"> <div class="space-y-1.5 sm:col-span-1"> <label ui-label for="cs-signer-name" class="text-xs">Type Full Legal Name as Signature *</label> <ui-input id="cs-signer-name" [value]="form().signatureName" (valueChange)="setField('signatureName', $event)" placeholder="e.g. Eleanor R. Vance" size="small" /> </div> <div class="space-y-1.5 sm:col-span-1"> <label ui-label for="cs-signer-role" class="text-xs">Signer Capacity / Role *</label> <ui-select [value]="form().signerRole" (valueChange)="setField('signerRole', $event)"> <button ui-select-trigger id="cs-signer-role" size="sm" class="h-8 text-xs"> <ui-select-value placeholder="Select role" /> </button> <ui-select-content> <ui-select-item value="patient">Patient (Self)</ui-select-item> <ui-select-item value="parent_guardian">Parent / Legal Guardian</ui-select-item> <ui-select-item value="poa">Medical Power of Attorney</ui-select-item> </ui-select-content> </ui-select> </div> <div class="space-y-1.5 sm:col-span-1"> <label ui-label for="cs-sig-date" class="text-xs">Signature Date *</label> <ui-input id="cs-sig-date" [value]="form().signatureDate" (valueChange)="setField('signatureDate', $event)" type="date" size="small" /> </div> </div> <div class="border-primary/30 bg-primary/5 space-y-3 rounded-xl border p-4"> <div class="flex flex-wrap items-center justify-between"> <span class="text-muted-foreground text-xs font-medium"> Legal Digital Signature Stamp </span> <span class="text-primary flex items-center gap-1 font-mono text-xs"> <lucide-icon [img]="ShieldCheck" class="size-3.5" /> Verified & Timestamped </span> </div> <div class="border-primary/20 border-b pt-1 pb-3"> <p class="text-primary text-2xl font-medium tracking-wide italic"> {{ form().signatureName || 'Your Signature' }} </p> </div> <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-2 font-mono text-xs"> <span>Signer: {{ form().signatureName || 'Eleanor Vance' }} ({{ form().signerRole }})</span> <span>Date: {{ form().signatureDate }} · Ref #SIG-49102-HIPAA</span> </div> </div> </div> </div> } <!-- STEP 5: Success / Confirmation Receipt --> @if (step() === 5) { <div class="space-y-6 py-4 text-center"> <div class="border-success/30 bg-success/10 text-success mx-auto flex size-16 items-center justify-center rounded-full border shadow-xs" > <lucide-icon [img]="Check" class="size-8" /> </div> <div class="space-y-1"> <h3 class="text-foreground text-xl font-bold tracking-tight">Patient Intake Submitted Successfully</h3> <p class="text-muted-foreground mx-auto max-w-md text-xs sm:text-sm"> Your new patient registration and encrypted medical disclosures have been received and queued for clinical triage. </p> </div> <div class="border-border inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs"> <span class="text-muted-foreground">Confirmation ID:</span> <span class="text-foreground font-mono font-bold">{{ intakeId() }}</span> </div> <div class="border-border bg-muted/30 mx-auto max-w-xl space-y-3 rounded-xl border p-4 text-left text-xs"> <div class="grid grid-cols-1 gap-3 sm:grid-cols-2"> <div> <span class="text-muted-foreground block">Patient Name:</span> <span class="text-foreground font-semibold" >{{ form().firstName }} {{ form().middleName }} {{ form().lastName }}</span > </div> <div> <span class="text-muted-foreground block">Date of Birth:</span> <span class="text-foreground font-medium tabular-nums" >{{ form().dob }} ({{ form().biologicalSex }})</span > </div> <div> <span class="text-muted-foreground block">Primary Insurance:</span> <span class="text-foreground font-medium" >{{ selectedCarrierLabel() }} (ID: {{ form().memberId }})</span > </div> <div> <span class="text-muted-foreground block">Emergency Contact:</span> <span class="text-foreground font-medium" >{{ form().emergencyName }} ({{ form().emergencyPhone }})</span > </div> <div class="border-border/60 col-span-1 flex flex-wrap items-center justify-between border-t pt-2 sm:col-span-2" > <span class="text-muted-foreground">Legal Signature:</span> <span class="text-primary font-medium tracking-wide italic" >{{ form().signatureName }} ({{ form().signatureDate }})</span > </div> </div> </div> <div class="flex flex-wrap items-center justify-center gap-3 pt-2"> <button ui-button aria-label="Download attachment" variant="outline" size="sm" class="gap-1.5 text-xs font-medium" > <lucide-icon [img]="Download" class="size-3.5" /> Download Intake PDF </button> <button ui-button size="sm" class="gap-1.5 text-xs font-medium" (click)="resetForm()"> <lucide-icon [img]="RotateCcw" class="size-3.5" /> Register Another Patient </button> </div> </div> } <div slot="footer" class="border-border bg-muted/20 flex w-full items-center justify-between border-t px-6 py-4"> @if (step() > 1 && step() <= 4) { <button ui-button variant="ghost" size="sm" class="gap-1.5 text-xs font-medium" (click)="goBack()"> <lucide-icon [img]="ChevronLeft" class="size-4" /> Back </button> } @else { <span aria-hidden="true"></span> } @if (step() <= 4) { <span class="text-muted-foreground text-xs font-medium"> Step {{ step() }} of 4 — {{ stepTitle() }} </span> } @else { <span class="text-muted-foreground text-xs font-medium">Registration Complete</span> } @if (step() < 4) { <button ui-button size="sm" class="gap-1.5 text-xs font-medium" [disabled]="continueDisabled()" (click)="goNext()" > Continue <lucide-icon [img]="ChevronRight" class="size-4" /> </button> } @else if (step() === 4) { <button ui-button size="sm" class="bg-primary text-primary-foreground gap-1.5 text-xs font-medium" [disabled]="submitDisabled()" (click)="handleSubmit()" > <lucide-icon [img]="FileCheck" class="size-4" /> Submit Registration </button> } @else { <span aria-hidden="true"></span> } </div> </ui-section-card> `, }) export class UiPatientIntakeFormComponent { protected readonly Activity = Activity protected readonly AlertTriangle = AlertTriangle protected readonly Building2 = Building2 protected readonly Calendar = Calendar protected readonly Check = Check protected readonly ChevronLeft = ChevronLeft protected readonly ChevronRight = ChevronRight protected readonly CreditCard = CreditCard protected readonly Download = Download protected readonly FileCheck = FileCheck protected readonly FileText = FileText protected readonly Lock = Lock protected readonly Phone = Phone protected readonly RotateCcw = RotateCcw protected readonly ShieldCheck = ShieldCheck protected readonly Stethoscope = Stethoscope protected readonly User = User protected readonly UserCheck = UserCheck protected readonly steps = steps protected readonly chronicConditions = CHRONIC_CONDITIONS protected readonly insuranceCarriers = INSURANCE_CARRIERS @Input('class') className?: string readonly step = signal(1) readonly submitted = signal(false) readonly intakeId = signal('INTAKE-2026-8942') readonly form = signal<PatientIntakeData>({ ...DEFAULT_FORM, chronicConditions: [...DEFAULT_FORM.chronicConditions], }) @Input('initialStep') set initialStepAttr(v: number | undefined) { if (v !== undefined) this.step.set(v) } @Input('initialData') set initialDataAttr(v: Partial<PatientIntakeData> | undefined) { if (v !== undefined) { this.form.update((prev) => ({ ...prev, ...v, chronicConditions: v.chronicConditions ? [...v.chronicConditions] : [...prev.chronicConditions], })) } } @Output() readonly submit = new EventEmitter<PatientIntakeData>() @Output() readonly stepChange = new EventEmitter<number>() readonly selectedCarrierLabel = computed(() => { const found = INSURANCE_CARRIERS.find((c) => c.id === this.form().insuranceCarrier) return found ? found.label : this.form().insuranceCarrier }) readonly stepTitle = computed(() => steps[this.step() - 1]?.title ?? '') readonly continueDisabled = computed( () => this.step() === 1 && (this.form().firstName.trim() === '' || this.form().lastName.trim() === ''), ) readonly submitDisabled = computed( () => !this.form().hipaaConsent || !this.form().treatmentConsent || this.form().signatureName.trim() === '', ) get cardClass(): string { return cn('max-w-4xl', this.className) } setField<K extends keyof PatientIntakeData>(key: K, value: PatientIntakeData[K]): void { this.form.update((prev) => ({ ...prev, [key]: value })) } toggleCondition(conditionId: string, checked: boolean | 'indeterminate'): void { this.form.update((prev) => { if (checked === true) { if (prev.chronicConditions.includes(conditionId)) return prev return { ...prev, chronicConditions: [...prev.chronicConditions, conditionId] } } return { ...prev, chronicConditions: prev.chronicConditions.filter((id) => id !== conditionId) } }) } toggleUpload(key: 'frontCardUploaded' | 'backCardUploaded'): void { this.form.update((prev) => ({ ...prev, [key]: !prev[key] })) } onStepperInput(value: number): void { // Backward navigation through the stepper is free; forward goes through Continue. if (value < this.step() && this.step() !== 5) { this.step.set(value) this.stepChange.emit(value) } } goNext(): void { if (this.step() < 4) { const next = this.step() + 1 this.step.set(next) this.stepChange.emit(next) } } goBack(): void { if (this.step() > 1 && this.step() <= 4) { const prev = this.step() - 1 this.step.set(prev) this.stepChange.emit(prev) } } handleSubmit(): void { this.submitted.set(true) this.step.set(5) const form = this.form() this.submit.emit({ ...form, chronicConditions: [...form.chronicConditions] }) this.stepChange.emit(5) } resetForm(): void { this.step.set(1) this.submitted.set(false) this.stepChange.emit(1) } }src/app/components/blocks/patient-intake-form/index.ts0.1 kB
export { UiPatientIntakeFormComponent, type PatientIntakeData } from './patient-intake-form.component'
Raw manifest:https://uipkge.dev/r/angular/patient-intake-form.json