Airbnb Host Earnings
blockreal-estateShort-term rental host revenue dashboard: gross earnings with month-over-month growth, occupancy rate progress, ADR & RevPAR metrics, automated direct deposit payout schedule card, dynamic pricing AI rate recommendations, and an itemized reservation payout breakdown table.
Installation
$pnpm dlx uipkge-ng@latest add airbnb-host-earnings$npx uipkge-ng@latest add airbnb-host-earnings$yarn dlx uipkge-ng@latest add airbnb-host-earnings$bunx uipkge-ng@latest add airbnb-host-earningsManifest:
https://uipkge.dev/r/angular/airbnb-host-earnings.jsonInstalls to:src/app/components/blocks/airbnb-host-earnings/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.
ReservationGuestinterface ReservationGuest {
name: string
avatar: string
initials: string
}ReservationPayoutinterface ReservationPayout {
id: string
confirmationCode: string
guest: ReservationGuest
property: string
location: string
datesBooked: string
nightsCount: number
nightlyRate: string
cleaningFee: string
hostServiceFee: string
netPayout: string
status: ReservationPayoutStatus
}Files installed (2)
src/app/components/blocks/airbnb-host-earnings/airbnb-host-earnings.component.ts27.8 kB
// Short-term rental host revenue dashboard: gross earnings, occupancy rate, // ADR & RevPAR metrics, payout schedule, dynamic pricing suggestions, and an // itemized reservation payout table. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core' import { Building2, Calendar, CalendarCheck, Check, CheckCircle2, Clock, DollarSign, Download, Home, Landmark, LucideAngularModule, TrendingUp, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component' 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 { UiProgressComponent } from '@/ui/progress/progress.component' import { UiSelectComponent, UiSelectContentComponent, UiSelectItemComponent, UiSelectTriggerComponent, UiSelectValueComponent, } from '@/ui/select/select.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' export type ReservationPayoutStatus = 'paid' | 'processing' export interface ReservationGuest { name: string avatar: string initials: string } export interface ReservationPayout { id: string confirmationCode: string guest: ReservationGuest property: string location: string datesBooked: string nightsCount: number nightlyRate: string cleaningFee: string hostServiceFee: string netPayout: string status: ReservationPayoutStatus } const reservations: ReservationPayout[] = [ { id: 'res-1', confirmationCode: 'HM-8924A', guest: { name: 'Sophia Martinez', avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=150&auto=format&fit=crop&q=80', initials: 'SM', }, property: 'Malibu Beachfront Villa', location: 'Malibu, CA', datesBooked: 'Aug 18-24 (6 nights)', nightsCount: 6, nightlyRate: '$650.00', cleaningFee: '$350.00', hostServiceFee: '-$127.50', netPayout: '$4,122.50', status: 'paid', }, { id: 'res-2', confirmationCode: 'HM-8919B', guest: { name: 'David Kim', avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&auto=format&fit=crop&q=80', initials: 'DK', }, property: 'Aspen Luxury Chalet', location: 'Aspen, CO', datesBooked: 'Aug 12-16 (4 nights)', nightsCount: 4, nightlyRate: '$720.00', cleaningFee: '$250.00', hostServiceFee: '-$94.00', netPayout: '$3,036.00', status: 'paid', }, { id: 'res-3', confirmationCode: 'HM-8912C', guest: { name: 'Elena Rostova', avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150&auto=format&fit=crop&q=80', initials: 'ER', }, property: 'Maui Oceanfront Suite', location: 'Maui, HI', datesBooked: 'Aug 04-10 (6 nights)', nightsCount: 6, nightlyRate: '$580.00', cleaningFee: '$300.00', hostServiceFee: '-$113.40', netPayout: '$3,666.60', status: 'paid', }, { id: 'res-4', confirmationCode: 'HM-8931D', guest: { name: 'Marcus Vance', avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=150&auto=format&fit=crop&q=80', initials: 'MV', }, property: 'Malibu Beachfront Villa', location: 'Malibu, CA', datesBooked: 'Aug 25-31 (6 nights)', nightsCount: 6, nightlyRate: '$690.00', cleaningFee: '$350.00', hostServiceFee: '-$134.70', netPayout: '$4,355.30', status: 'processing', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-airbnb-host-earnings, [ui-airbnb-host-earnings]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiProgressComponent, UiSelectComponent, UiSelectContentComponent, UiSelectItemComponent, UiSelectTriggerComponent, UiSelectValueComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <div data-slot="airbnb-host-earnings" [class]="rootClass"> <!-- Header Section --> <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between"> <div class="space-y-1"> <h1 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl"> Host Earnings & Revenue Analytics </h1> <p class="text-muted-foreground text-sm"> Short-term rental portfolio revenue, occupancy yield metrics, and automated payout disbursement schedule. </p> </div> <div class="flex flex-wrap items-center gap-2.5"> <ui-select [value]="selectedProperty()" (valueChange)="selectedProperty.set($event)"> <button ui-select-trigger class="w-48 text-xs" aria-label="Select property"> <lucide-icon [img]="Home" class="text-muted-foreground mr-1.5 size-3.5" /> <ui-select-value placeholder="Select property" /> </button> <ui-select-content> <ui-select-item value="all">All Properties (3)</ui-select-item> <ui-select-item value="malibu">Malibu Beachfront Villa</ui-select-item> <ui-select-item value="aspen">Aspen Luxury Chalet</ui-select-item> <ui-select-item value="maui">Maui Oceanfront Suite</ui-select-item> </ui-select-content> </ui-select> <ui-select [value]="selectedTimeframe()" (valueChange)="selectedTimeframe.set($event)"> <button ui-select-trigger class="w-52 text-xs" aria-label="Select timeframe"> <lucide-icon [img]="Calendar" class="text-muted-foreground mr-1.5 size-3.5" /> <ui-select-value placeholder="Select timeframe" /> </button> <ui-select-content> <ui-select-item value="this-month">This Month · August 2026</ui-select-item> <ui-select-item value="last-month">Last Month · July 2026</ui-select-item> <ui-select-item value="q3">Last 90 Days (Peak Summer)</ui-select-item> <ui-select-item value="ytd">Year to Date · 2026</ui-select-item> </ui-select-content> </ui-select> <button ui-button aria-label="Download attachment" variant="outline" size="sm" class="gap-1.5 text-xs"> <lucide-icon [img]="Download" class="size-3.5" /> <span>Export Tax 1099 CSV</span> </button> </div> </div> <!-- 4 Primary Host KPI Cards --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <div ui-card class="flex flex-col justify-between shadow-xs"> <div ui-card-header class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex flex-wrap items-center gap-2"> <div class="border-success/20 bg-success/10 text-success flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" aria-hidden="true" > <lucide-icon [img]="DollarSign" class="size-4" /> </div> <h3 ui-card-title class="text-muted-foreground text-sm font-medium">Gross Earnings</h3> </div> <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-bold tabular-nums" > +14.2% vs last month </span> </div> </div> <div ui-card-content class="space-y-3"> <div> <div class="flex items-baseline gap-1.5"> <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">$18,450.00</span> </div> <p class="text-muted-foreground mt-0.5 text-xs">Net after 3% fee: $17,896.50</p> </div> <div class="border-border/60 border-t pt-2 text-xs"> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Projected month-end:</span> <span class="text-foreground font-semibold tabular-nums">$21,400.00</span> </div> </div> </div> </div> <div ui-card class="flex flex-col justify-between shadow-xs"> <div ui-card-header class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex flex-wrap items-center gap-2"> <div class="border-border bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" aria-hidden="true" > <lucide-icon [img]="CalendarCheck" class="size-4" /> </div> <h3 ui-card-title class="text-muted-foreground text-sm font-medium">Occupancy Rate</h3> </div> <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-bold tabular-nums" > +6.2% vs area avg </span> </div> </div> <div ui-card-content class="space-y-3"> <div> <div class="flex items-baseline gap-1.5"> <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">88.5%</span> </div> <p class="text-muted-foreground mt-0.5 text-xs tabular-nums">27 of 31 nights booked</p> </div> <div class="space-y-1.5"> <ui-progress [value]="88.5" class="[&_[data-slot=progress-indicator]]:bg-success dark:[&_[data-slot=progress-indicator]]:bg-success h-1.5" /> <div class="text-muted-foreground flex items-center justify-between text-xs tabular-nums"> <span>4 nights open</span> <span>2 instant holds</span> </div> </div> </div> </div> <div ui-card class="flex flex-col justify-between shadow-xs"> <div ui-card-header class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex flex-wrap items-center gap-2"> <div class="border-border bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" aria-hidden="true" > <lucide-icon [img]="TrendingUp" class="size-4" /> </div> <h3 ui-card-title class="text-muted-foreground text-sm font-medium">Average Daily Rate (ADR)</h3> </div> <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-medium tabular-nums" > +$45.00 vs baseline </span> </div> </div> <div ui-card-content class="space-y-3"> <div> <div class="flex items-baseline gap-1"> <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">$683.33</span> <span class="text-muted-foreground text-xs font-normal">/ night</span> </div> <p class="text-muted-foreground mt-0.5 text-xs">Blended across active portfolio</p> </div> <div class="border-border/60 border-t pt-2 text-xs"> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Weekend surge rate:</span> <span class="text-foreground font-semibold tabular-nums">$820.00 / night</span> </div> </div> </div> </div> <div ui-card class="flex flex-col justify-between shadow-xs"> <div ui-card-header class="pb-2"> <div class="flex items-center justify-between gap-2"> <div class="flex flex-wrap items-center gap-2"> <div class="border-border bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" aria-hidden="true" > <lucide-icon [img]="Building2" class="size-4" /> </div> <h3 ui-card-title class="text-muted-foreground text-sm font-medium">RevPAR (Yield / Unit)</h3> </div> <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-bold tabular-nums" > +18.4% YoY </span> </div> </div> <div ui-card-content class="space-y-3"> <div> <div class="flex items-baseline gap-1.5"> <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">$604.75</span> </div> <p class="text-muted-foreground mt-0.5 text-xs tabular-nums">Market benchmark: $512.00</p> </div> <div class="border-border/60 border-t pt-2 text-xs"> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Regional yield index:</span> <span class="text-success font-semibold">Top 5% in submarket</span> </div> </div> </div> </div> </div> <!-- Middle Grid: Upcoming Payouts Schedule & Dynamic Pricing Suggestions --> <div class="grid grid-cols-1 gap-6 lg:grid-cols-2"> <div ui-card class="flex flex-col justify-between shadow-xs"> <div ui-card-header> <div class="flex items-center justify-between"> <div class="space-y-1"> <h3 ui-card-title>Upcoming Payouts Schedule</h3> <p ui-card-description> Automated direct deposits sent to your verified checking account 24 hours post check-in. </p> </div> <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success text-xs font-medium" > <lucide-icon [img]="CheckCircle2" class="mr-1 size-3" /> Auto-Transfer Active </span> </div> </div> <div ui-card-content class="space-y-4"> <div class="border-border bg-muted/40 rounded-lg border p-4"> <div class="flex items-start justify-between gap-4"> <div class="flex items-start gap-3"> <div class="border-success/20 bg-success/10 text-success flex size-9 shrink-0 items-center justify-center rounded-lg border" > <lucide-icon [img]="Landmark" class="size-4.5" /> </div> <div class="space-y-0.5"> <span class="text-muted-foreground text-xs font-medium tracking-wider uppercase">Next Payout</span> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">$4,250.00</div> <p class="text-foreground text-xs font-medium"> Direct Deposit to Chase •••• 4892 · <span class="text-success font-semibold">Arriving Aug 24</span> </p> </div> </div> <span ui-badge wrap variant="secondary" class="text-xs">Scheduled</span> </div> </div> <div class="grid grid-cols-1 gap-3 sm:grid-cols-2"> <div class="border-border bg-card rounded-lg border p-3.5 shadow-xs"> <span class="text-muted-foreground text-xs font-medium">Completed payouts YTD</span> <div class="text-foreground mt-1 text-xl font-bold tracking-tight tabular-nums">$92,400.00</div> <p class="text-muted-foreground mt-0.5 text-xs">18 completed disbursement cycles</p> </div> <div class="border-border bg-card rounded-lg border p-3.5 shadow-xs"> <span class="text-muted-foreground text-xs font-medium">Settlement routing</span> <div class="text-foreground mt-1 text-sm font-semibold">JPMorgan Chase Bank</div> <p class="text-muted-foreground mt-0.5 text-xs tabular-nums">Routing: 021000021 · $0 held</p> </div> </div> <div class="flex items-center justify-between pt-1"> <p class="text-muted-foreground text-xs">Payouts release automatically 24h after each guest check-in.</p> <button ui-button variant="outline" size="sm" class="gap-1.5 text-xs"> <lucide-icon [img]="Building2" class="size-3.5" /> <span>Manage Bank Account</span> </button> </div> </div> </div> <div ui-card class="flex flex-col justify-between shadow-xs"> <div ui-card-header> <div class="flex items-center justify-between"> <div class="space-y-1"> <h3 ui-card-title>Dynamic Pricing Suggestions</h3> <p ui-card-description> Machine-learning demand forecasts and local holiday surge pricing recommendations. </p> </div> <span ui-badge wrap variant="outline" class="border-warning/20 bg-warning/10 text-warning gap-1 text-xs font-semibold" > <lucide-icon [img]="TrendingUp" class="size-3" /> Smart Rates </span> </div> </div> <div ui-card-content class="space-y-4"> <div class="border-border/80 bg-muted/30 hover:bg-muted/50 rounded-lg border p-4 transition-colors"> <div class="flex items-start gap-3"> <div class="border-warning/20 bg-warning/10 text-warning mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs" > <lucide-icon [img]="TrendingUp" class="size-4" /> </div> <div class="flex-1 space-y-2"> <div class="flex flex-wrap items-center justify-between gap-2"> <h4 class="text-foreground text-sm font-semibold"> High demand weekend Sep 4-7: increase ADR by +15% </h4> <span ui-badge wrap variant="outline" class="text-xs font-medium">Labor Day Peak</span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> Regional guest search volume is up +48% in Malibu and Maui. Raising base rate from $680.00 to $782.00/night captures peak holiday yield. </p> <div class="border-border/60 grid grid-cols-1 gap-2 border-t pt-2.5 text-xs sm:grid-cols-3"> <div> <span class="text-muted-foreground">Current ADR:</span> <p class="text-foreground font-semibold tabular-nums">$680.00 / night</p> </div> <div> <span class="text-muted-foreground">Suggested ADR:</span> <p class="text-success font-semibold tabular-nums">$782.00 (+15%)</p> </div> <div class="sm:text-right"> <span class="text-muted-foreground">Est. Extra Rev:</span> <p class="text-foreground font-semibold tabular-nums">+$408.00 (4 nights)</p> </div> </div> </div> </div> </div> @if (isPricingApplied()) { <div class="border-success/20 bg-success/10 flex items-center justify-between rounded-lg border p-3 text-xs" > <div class="text-success flex flex-wrap items-center gap-2 font-medium"> <lucide-icon [img]="CheckCircle2" class="size-4 shrink-0" /> <span>Suggested +15% rate applied for Sep 4-7 ($782.00/night).</span> </div> <button ui-button variant="ghost" size="sm" class="h-7 text-xs" (click)="isPricingApplied.set(false)"> Undo </button> </div> } @else { <div class="flex flex-wrap items-center justify-between gap-2 pt-1"> <span class="text-muted-foreground text-xs">Algorithm confidence score: 96%</span> <div class="flex flex-wrap items-center gap-2"> <button ui-button variant="outline" size="sm" class="text-xs">Customize Rules</button> <button ui-button size="sm" class="gap-1.5 text-xs" (click)="isPricingApplied.set(true)"> <lucide-icon [img]="Check" class="size-3.5" /> <span>Apply Suggested Rate</span> </button> </div> </div> } </div> </div> </div> <!-- Monthly Revenue Breakdown Table Card --> <div ui-card class="shadow-xs"> <div ui-card-header> <div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between"> <div> <h3 ui-card-title>Monthly Revenue Breakdown</h3> <p ui-card-description> Itemized guest reservations, cleaning fee payouts, platform service commissions, and net disbursements. </p> </div> <span ui-badge wrap variant="outline" class="w-fit text-xs font-normal tabular-nums"> 4 reservations · August 2026 </span> </div> </div> <div ui-card-content class="p-0 sm:p-6 sm:pt-0"> <div class="overflow-x-auto"> <table ui-table> <thead ui-table-header> <tr ui-table-row> <th ui-table-head class="min-w-[180px]">Guest & Reservation</th> <th ui-table-head class="min-w-[160px]">Property Booked</th> <th ui-table-head>Dates Booked</th> <th ui-table-head class="text-right">Nightly Rate</th> <th ui-table-head class="text-right">Cleaning Fee</th> <th ui-table-head class="text-right">Host Fee (3%)</th> <th ui-table-head class="text-right">Net Payout</th> <th ui-table-head class="text-right">Status</th> </tr> </thead> <tbody ui-table-body> @for (res of reservations; track res.id) { <tr ui-table-row class="hover:bg-muted/50"> <td ui-table-cell> <div class="flex items-center gap-3"> <ui-avatar size="sm" class="shrink-0"> <ui-avatar-image [src]="res.guest.avatar" [alt]="res.guest.name" /> <ui-avatar-fallback class="text-xs font-medium">{{ res.guest.initials }}</ui-avatar-fallback> </ui-avatar> <div class="space-y-0.5"> <div class="text-foreground text-sm font-semibold">{{ res.guest.name }}</div> <div class="text-muted-foreground font-mono text-xs tabular-nums"> {{ res.confirmationCode }} </div> </div> </div> </td> <td ui-table-cell> <div class="space-y-0.5"> <div class="text-foreground text-sm font-medium">{{ res.property }}</div> <div class="text-muted-foreground text-xs">{{ res.location }}</div> </div> </td> <td ui-table-cell> <div class="text-foreground text-xs font-medium tabular-nums">{{ res.datesBooked }}</div> </td> <td ui-table-cell class="text-right"> <div class="text-foreground text-xs font-semibold tabular-nums">{{ res.nightlyRate }}</div> <div class="text-muted-foreground text-xs tabular-nums">{{ res.nightsCount }} nights</div> </td> <td ui-table-cell class="text-muted-foreground text-right text-xs tabular-nums"> {{ res.cleaningFee }} </td> <td ui-table-cell class="text-muted-foreground text-right text-xs tabular-nums"> {{ res.hostServiceFee }} </td> <td ui-table-cell class="text-foreground text-right text-sm font-bold tabular-nums"> {{ res.netPayout }} </td> <td ui-table-cell class="text-right"> <span ui-badge variant="outline" [class]="statusBadgeClass(res)"> @if (res.status === 'paid') { <span class="flex items-center gap-1"> <lucide-icon [img]="Check" class="size-3" /> Paid </span> } @else { <span class="flex items-center gap-1"> <lucide-icon [img]="Clock" class="size-3" /> Processing </span> } </span> </td> </tr> } </tbody> </table> </div> </div> </div> </div> `, }) export class UiAirbnbHostEarningsComponent { protected readonly Building2 = Building2 protected readonly Calendar = Calendar protected readonly CalendarCheck = CalendarCheck protected readonly Check = Check protected readonly CheckCircle2 = CheckCircle2 protected readonly Clock = Clock protected readonly DollarSign = DollarSign protected readonly Download = Download protected readonly Home = Home protected readonly Landmark = Landmark protected readonly TrendingUp = TrendingUp protected readonly reservations = reservations @Input() set initialProperty(v: string | undefined) { this.selectedProperty.set(v ?? 'all') } @Input() set initialTimeframe(v: string | undefined) { this.selectedTimeframe.set(v ?? 'this-month') } @Input('class') className?: string readonly selectedProperty = signal('all') readonly selectedTimeframe = signal('this-month') readonly isPricingApplied = signal(false) get rootClass(): string { return cn('mx-auto w-full max-w-6xl space-y-6', this.className) } statusBadgeClass(res: ReservationPayout): string { return cn( 'text-xs font-semibold tabular-nums', res.status === 'paid' && 'border-success/20 bg-success/10 text-success', res.status === 'processing' && 'border-info/20 bg-info/10 text-info', ) } }src/app/components/blocks/airbnb-host-earnings/index.ts0.2 kB
export { UiAirbnbHostEarningsComponent, type ReservationGuest, type ReservationPayout, type ReservationPayoutStatus, } from './airbnb-host-earnings.component'
Raw manifest:https://uipkge.dev/r/angular/airbnb-host-earnings.json