Earnings dashboard, transaction ledger, and payout celebrations
FinancesDashboard — useQuery(getEarningsSummary, getPendingPayouts, getPaymentHistory)
TransactionHistory — useQuery(contractor.ledger.getLedger, getLedgerSummary), filter chips state
PayoutReceived — ConfettiOverlay, spring animation, useQuery(api.jobs.jobs.get), payoutType="first"
PayoutReceived — Same component, payoutType="final" variant
escrow {
jobId, contractorId, posterId,
amount, status, holdExpiresAt,
firstPayoutAt, secondPayoutAt,
platformFee, createdAt
}
payouts {
escrowId, contractorId,
amount, type (first | final),
stripeTransferId, status,
createdAt
}
jobs {
posterId, contractorId, propertyId,
title, description, jobTypeId,
status, address, budget
}
contractorUsers {
clerkId, email, stripeAccountId,
stripeCustomerId, subscription
}
ledgerEntries {
contractorId, type, amount,
description, referenceId,
createdAt
}
contractor.payments
.getEarningsSummary
(total, pending, escrow, completed)
contractor.payments
.getPendingPayouts
(list of pending payout records)
contractor.payments
.getPaymentHistory
(paginated, filtered by tab)
contractor.transactions
.getLedger
(all ledger entries, paginated)
contractor.ledger
.getLedgerSummary
(income, expenses, net totals)
jobs.jobs.get
(job details for celebration card)
Hooks:
useThemedStyles
useThemeColors
useQuery (Convex)
useResponsive
useDebounce (search)
Components:
ScreenLayout
Card
StatusBadge
SearchInput
Loading
ConfettiOverlay
ProgressBar
FlatList (virtualized)
BASIC_PLATFORM_FEE_RATE = 0.067
OWN_LEAD_PLATFORM_FEE_RATE = 0.00
MARKETPLACE_PLATFORM_FEE_RATE = 0.05
FIRST_PAYOUT_RATE = 0.40
SECOND_PAYOUT_RATE = 0.60
HOLD_PERIOD_HOURS = 24
Payout Flow:
Contract signed → Poster funds
→ 24h hold period
→ 40% first payout released
→ Job completed
→ 60% final payout released
→ platform fee deducted per plan
Escrow States:
pending → hold_period
→ funded → first_payout_sent
→ released
Mutations: None (read-only views
+ celebration is triggered by
payout webhook, not user action)