
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
Short-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.
Also available for Vue ->$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/airbnb-host-earnings.json$npx shadcn@latest add https://uipkge.dev/r/react/airbnb-host-earnings.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/airbnb-host-earnings.json$bunx shadcn@latest add https://uipkge.dev/r/react/airbnb-host-earnings.jsonnpx shadcn@latest add @uipkge-react/airbnb-host-earningsInstalls to:components/blocks/Type aliases exported from this item's source. Use these to shape the data you pass in.
ReservationPayoutinterface ReservationPayout {
id: string
confirmationCode: string
guest: {
name: string
avatar: string
initials: string
}
property: string
location: string
datesBooked: string
nightsCount: number
nightlyRate: string
cleaningFee: string
hostServiceFee: string
netPayout: string
status: 'paid' | 'processing'
}'use client'
import * as React from 'react'
import {
Building2,
Calendar,
CalendarCheck,
Check,
CheckCircle2,
Clock,
DollarSign,
Download,
Home,
Landmark,
TrendingUp,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Progress } from '@/components/ui/progress'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
interface ReservationPayout {
id: string
confirmationCode: string
guest: {
name: string
avatar: string
initials: string
}
property: string
location: string
datesBooked: string
nightsCount: number
nightlyRate: string
cleaningFee: string
hostServiceFee: string
netPayout: string
status: 'paid' | 'processing'
}
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',
},
]
export function AirbnbHostEarnings({ className }: { className?: string }) {
const [selectedProperty, setSelectedProperty] = React.useState('all')
const [selectedTimeframe, setSelectedTimeframe] = React.useState('this-month')
const [isPricingApplied, setIsPricingApplied] = React.useState(false)
return (
<div data-slot="airbnb-host-earnings" className={cn('mx-auto w-full max-w-6xl space-y-6', className)}>
{/* Header Section */}
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div className="space-y-1">
<h1 className="text-foreground text-2xl font-bold tracking-tight sm:text-3xl">
Host Earnings & Revenue Analytics
</h1>
<p className="text-muted-foreground text-sm">
Short-term rental portfolio revenue, occupancy yield metrics, and automated payout disbursement schedule.
</p>
</div>
<div className="flex flex-wrap items-center gap-2.5">
<Select value={selectedProperty} onValueChange={setSelectedProperty}>
<SelectTrigger className="w-48 text-xs" aria-label="Select property">
<Home className="text-muted-foreground mr-1.5 size-3.5" />
<SelectValue placeholder="Select property" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Properties (3)</SelectItem>
<SelectItem value="malibu">Malibu Beachfront Villa</SelectItem>
<SelectItem value="aspen">Aspen Luxury Chalet</SelectItem>
<SelectItem value="maui">Maui Oceanfront Suite</SelectItem>
</SelectContent>
</Select>
<Select value={selectedTimeframe} onValueChange={setSelectedTimeframe}>
<SelectTrigger className="w-52 text-xs" aria-label="Select timeframe">
<Calendar className="text-muted-foreground mr-1.5 size-3.5" />
<SelectValue placeholder="Select timeframe" />
</SelectTrigger>
<SelectContent>
<SelectItem value="this-month">This Month · August 2026</SelectItem>
<SelectItem value="last-month">Last Month · July 2026</SelectItem>
<SelectItem value="q3">Last 90 Days (Peak Summer)</SelectItem>
<SelectItem value="ytd">Year to Date · 2026</SelectItem>
</SelectContent>
</Select>
<Button aria-label="Download attachment" variant="outline" size="sm" className="gap-1.5 text-xs">
<Download className="size-3.5" />
<span>Export Tax 1099 CSV</span>
</Button>
</div>
</div>
{/* 4 Primary Host KPI Cards */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
{/* Gross Earnings */}
<Card className="flex flex-col justify-between shadow-xs">
<CardHeader className="pb-2">
<div className="flex items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2">
<div
className="border-success/20 bg-success/10 text-success text-success flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
aria-hidden="true"
>
<DollarSign className="size-4" />
</div>
<CardTitle className="text-muted-foreground text-sm font-medium">Gross Earnings</CardTitle>
</div>
<Badge
wrap
variant="outline"
className="border-success/20 bg-success/10 text-success text-success text-xs font-bold tabular-nums"
>
+14.2% vs last month
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-3">
<div>
<div className="flex items-baseline gap-1.5">
<span className="text-foreground text-2xl font-bold tracking-tight tabular-nums">$18,450.00</span>
</div>
<p className="text-muted-foreground mt-0.5 text-xs">Net after 3% fee: $17,896.50</p>
</div>
<div className="border-border/60 border-t pt-2 text-xs">
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Projected month-end:</span>
<span className="text-foreground font-semibold tabular-nums">$21,400.00</span>
</div>
</div>
</CardContent>
</Card>
{/* Occupancy Rate */}
<Card className="flex flex-col justify-between shadow-xs">
<CardHeader className="pb-2">
<div className="flex items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2">
<div
className="border-border bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
aria-hidden="true"
>
<CalendarCheck className="size-4" />
</div>
<CardTitle className="text-muted-foreground text-sm font-medium">Occupancy Rate</CardTitle>
</div>
<Badge
wrap
variant="outline"
className="border-success/20 bg-success/10 text-success text-success text-xs font-bold tabular-nums"
>
+6.2% vs area avg
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-3">
<div>
<div className="flex items-baseline gap-1.5">
<span className="text-foreground text-2xl font-bold tracking-tight tabular-nums">88.5%</span>
</div>
<p className="text-muted-foreground mt-0.5 text-xs tabular-nums">27 of 31 nights booked</p>
</div>
<div className="space-y-1.5">
<Progress
value={88.5}
className="[&_[data-slot=progress-indicator]]:bg-success dark:[&_[data-slot=progress-indicator]]:bg-success h-1.5"
/>
<div className="text-muted-foreground flex items-center justify-between text-xs tabular-nums">
<span>4 nights open</span>
<span>2 instant holds</span>
</div>
</div>
</CardContent>
</Card>
{/* Average Daily Rate (ADR) */}
<Card className="flex flex-col justify-between shadow-xs">
<CardHeader className="pb-2">
<div className="flex items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2">
<div
className="border-border bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
aria-hidden="true"
>
<TrendingUp className="size-4" />
</div>
<CardTitle className="text-muted-foreground text-sm font-medium">Average Daily Rate (ADR)</CardTitle>
</div>
<Badge
wrap
variant="outline"
className="border-success/20 bg-success/10 text-success text-success text-xs font-medium tabular-nums"
>
+$45.00 vs baseline
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-3">
<div>
<div className="flex items-baseline gap-1">
<span className="text-foreground text-2xl font-bold tracking-tight tabular-nums">$683.33</span>
<span className="text-muted-foreground text-xs font-normal">/ night</span>
</div>
<p className="text-muted-foreground mt-0.5 text-xs">Blended across active portfolio</p>
</div>
<div className="border-border/60 border-t pt-2 text-xs">
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Weekend surge rate:</span>
<span className="text-foreground font-semibold tabular-nums">$820.00 / night</span>
</div>
</div>
</CardContent>
</Card>
{/* RevPAR (Revenue per Available Room) */}
<Card className="flex flex-col justify-between shadow-xs">
<CardHeader className="pb-2">
<div className="flex items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2">
<div
className="border-border bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
aria-hidden="true"
>
<Building2 className="size-4" />
</div>
<CardTitle className="text-muted-foreground text-sm font-medium">RevPAR (Yield / Unit)</CardTitle>
</div>
<Badge
wrap
variant="outline"
className="border-success/20 bg-success/10 text-success text-success text-xs font-bold tabular-nums"
>
+18.4% YoY
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-3">
<div>
<div className="flex items-baseline gap-1.5">
<span className="text-foreground text-2xl font-bold tracking-tight tabular-nums">$604.75</span>
</div>
<p className="text-muted-foreground mt-0.5 text-xs tabular-nums">Market benchmark: $512.00</p>
</div>
<div className="border-border/60 border-t pt-2 text-xs">
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Regional yield index:</span>
<span className="text-success font-semibold">Top 5% in submarket</span>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Middle Grid: Upcoming Payouts Schedule & Dynamic Pricing Suggestions */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
{/* Upcoming Payouts Schedule Card */}
<Card className="flex flex-col justify-between shadow-xs">
<CardHeader>
<div className="flex items-center justify-between">
<div className="space-y-1">
<CardTitle>Upcoming Payouts Schedule</CardTitle>
<CardDescription>
Automated direct deposits sent to your verified checking account 24 hours post check-in.
</CardDescription>
</div>
<Badge
wrap
variant="outline"
className="border-success/20 bg-success/10 text-success text-xs font-medium"
>
<CheckCircle2 className="mr-1 size-3" />
Auto-Transfer Active
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* Next Payout Focus Box */}
<div className="border-border bg-muted/40 rounded-lg border p-4">
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3">
<div className="border-success/20 bg-success/10 text-success flex size-9 shrink-0 items-center justify-center rounded-lg border">
<Landmark className="size-4.5" />
</div>
<div className="space-y-0.5">
<span className="text-muted-foreground text-xs font-medium tracking-wider uppercase">
Next Payout
</span>
<div className="text-foreground text-2xl font-bold tracking-tight tabular-nums">$4,250.00</div>
<p className="text-foreground text-xs font-medium">
Direct Deposit to Chase •••• 4892 ·{' '}
<span className="text-success font-semibold">Arriving Aug 24</span>
</p>
</div>
</div>
<Badge wrap variant="secondary" className="text-xs">
Scheduled
</Badge>
</div>
</div>
{/* Completed YTD & Bank Info Row */}
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<div className="border-border bg-card rounded-lg border p-3.5 shadow-xs">
<span className="text-muted-foreground text-xs font-medium">Completed payouts YTD</span>
<div className="text-foreground mt-1 text-xl font-bold tracking-tight tabular-nums">$92,400.00</div>
<p className="text-muted-foreground mt-0.5 text-xs">18 completed disbursement cycles</p>
</div>
<div className="border-border bg-card rounded-lg border p-3.5 shadow-xs">
<span className="text-muted-foreground text-xs font-medium">Settlement routing</span>
<div className="text-foreground mt-1 text-sm font-semibold">JPMorgan Chase Bank</div>
<p className="text-muted-foreground mt-0.5 text-xs tabular-nums">Routing: 021000021 · $0 held</p>
</div>
</div>
<div className="flex items-center justify-between pt-1">
<p className="text-muted-foreground text-xs">
Payouts release automatically 24h after each guest check-in.
</p>
<Button variant="outline" size="sm" className="gap-1.5 text-xs">
<Building2 className="size-3.5" />
<span>Manage Bank Account</span>
</Button>
</div>
</CardContent>
</Card>
{/* Dynamic Pricing Suggestions Card */}
<Card className="flex flex-col justify-between shadow-xs">
<CardHeader>
<div className="flex items-center justify-between">
<div className="space-y-1">
<CardTitle>Dynamic Pricing Suggestions</CardTitle>
<CardDescription>
Machine-learning demand forecasts and local holiday surge pricing recommendations.
</CardDescription>
</div>
<Badge
wrap
variant="outline"
className="border-warning/20 bg-warning/10 text-warning gap-1 text-xs font-semibold"
>
<TrendingUp className="size-3" />
Smart Rates
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* Suggestion Banner Box */}
<div className="border-border/80 bg-muted/30 hover:bg-muted/50 rounded-lg border p-4 transition-colors">
<div className="flex items-start gap-3">
<div className="border-warning/20 bg-warning/10 text-warning text-warning mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs">
<TrendingUp className="size-4" />
</div>
<div className="flex-1 space-y-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<h4 className="text-foreground text-sm font-semibold">
High demand weekend Sep 4-7: increase ADR by +15%
</h4>
<Badge wrap variant="outline" className="text-xs font-medium">
Labor Day Peak
</Badge>
</div>
<p className="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 className="border-border/60 grid grid-cols-1 gap-2 border-t pt-2.5 text-xs sm:grid-cols-3">
<div>
<span className="text-muted-foreground">Current ADR:</span>
<p className="text-foreground font-semibold tabular-nums">$680.00 / night</p>
</div>
<div>
<span className="text-muted-foreground">Suggested ADR:</span>
<p className="text-success text-success font-semibold tabular-nums">$782.00 (+15%)</p>
</div>
<div className="sm:text-right">
<span className="text-muted-foreground">Est. Extra Rev:</span>
<p className="text-foreground font-semibold tabular-nums">+$408.00 (4 nights)</p>
</div>
</div>
</div>
</div>
</div>
{/* Applied State / Action Buttons */}
{isPricingApplied ? (
<div className="border-success/20 bg-success/10 flex items-center justify-between rounded-lg border p-3 text-xs">
<div className="text-success flex flex-wrap items-center gap-2 font-medium">
<CheckCircle2 className="size-4 shrink-0" />
<span>Suggested +15% rate applied for Sep 4-7 ($782.00/night).</span>
</div>
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setIsPricingApplied(false)}>
Undo
</Button>
</div>
) : (
<div className="flex flex-wrap items-center justify-between gap-2 pt-1">
<span className="text-muted-foreground text-xs">Algorithm confidence score: 96%</span>
<div className="flex flex-wrap items-center gap-2">
<Button variant="outline" size="sm" className="text-xs">
Customize Rules
</Button>
<Button size="sm" className="gap-1.5 text-xs" onClick={() => setIsPricingApplied(true)}>
<Check className="size-3.5" />
<span>Apply Suggested Rate</span>
</Button>
</div>
</div>
)}
</CardContent>
</Card>
</div>
{/* Monthly Revenue Breakdown Table Card */}
<Card className="shadow-xs">
<CardHeader>
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
<div>
<CardTitle>Monthly Revenue Breakdown</CardTitle>
<CardDescription>
Itemized guest reservations, cleaning fee payouts, platform service commissions, and net disbursements.
</CardDescription>
</div>
<Badge wrap variant="outline" className="w-fit text-xs font-normal tabular-nums">
4 reservations · August 2026
</Badge>
</div>
</CardHeader>
<CardContent className="p-0 sm:p-6 sm:pt-0">
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="min-w-[180px]">Guest & Reservation</TableHead>
<TableHead className="min-w-[160px]">Property Booked</TableHead>
<TableHead>Dates Booked</TableHead>
<TableHead className="text-right">Nightly Rate</TableHead>
<TableHead className="text-right">Cleaning Fee</TableHead>
<TableHead className="text-right">Host Fee (3%)</TableHead>
<TableHead className="text-right">Net Payout</TableHead>
<TableHead className="text-right">Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{reservations.map((res) => (
<TableRow key={res.id} className="hover:bg-muted/50">
{/* Guest Column */}
<TableCell>
<div className="flex items-center gap-3">
<Avatar size="sm" className="shrink-0">
<AvatarImage src={res.guest.avatar} alt={res.guest.name} />
<AvatarFallback className="text-xs font-medium">{res.guest.initials}</AvatarFallback>
</Avatar>
<div className="space-y-0.5">
<div className="text-foreground text-sm font-semibold">{res.guest.name}</div>
<div className="text-muted-foreground font-mono text-xs tabular-nums">
{res.confirmationCode}
</div>
</div>
</div>
</TableCell>
{/* Property Booked */}
<TableCell>
<div className="space-y-0.5">
<div className="text-foreground text-sm font-medium">{res.property}</div>
<div className="text-muted-foreground text-xs">{res.location}</div>
</div>
</TableCell>
{/* Dates Booked */}
<TableCell>
<div className="text-foreground text-xs font-medium tabular-nums">{res.datesBooked}</div>
</TableCell>
{/* Nightly Rate */}
<TableCell className="text-right">
<div className="text-foreground text-xs font-semibold tabular-nums">{res.nightlyRate}</div>
<div className="text-muted-foreground text-xs tabular-nums">{res.nightsCount} nights</div>
</TableCell>
{/* Cleaning Fee */}
<TableCell className="text-muted-foreground text-right text-xs tabular-nums">
{res.cleaningFee}
</TableCell>
{/* Host Service Fee */}
<TableCell className="text-muted-foreground text-right text-xs tabular-nums">
{res.hostServiceFee}
</TableCell>
{/* Net Payout */}
<TableCell className="text-foreground text-right text-sm font-bold tabular-nums">
{res.netPayout}
</TableCell>
{/* Status Badge */}
<TableCell className="text-right">
<Badge
variant="outline"
className={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',
)}
>
{res.status === 'paid' ? (
<span className="flex items-center gap-1">
<Check className="size-3" />
Paid
</span>
) : (
<span className="flex items-center gap-1">
<Clock className="size-3" />
Processing
</span>
)}
</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</div>
)
}
export default AirbnbHostEarnings
Raw manifest:https://uipkge.dev/r/react/airbnb-host-earnings.json