Features
Client Management
Enrich client profiles with avatars and social links, track referral chains, monitor lead sources, and measure per-client profitability.
Client Enrichment
When a client is created (either manually or through lead conversion), the app enriches their profile with additional data:
Avatar
Fetched from Gravatar based on the client email. Falls back to initials if unavailable.
LinkedIn URL
Optional field to link directly to the client's LinkedIn profile for quick reference.
Domain
Automatically extracted from the email address (e.g., acme.com from jane@acme.com).
Company Name
Free-text field used for grouping and searching. Displayed on invoices.
Referral Chain Tracking
The CRM supports multi-level referral tracking. Each client can have a "referred by" relationship pointing to another client, forming a chain. This lets you see which clients bring you the most downstream revenue.
Sarah (original) → referred Mike → referred Lisa → referred Tom
All downstream revenue is attributed back through the chain
- *Referral chains are visualized in the Revenue Sankey chart on the dashboard
- *Each client profile shows who referred them and who they referred
- *Self-referential relationship in Prisma using the same Client model
Source Tracking
Every client has a source field that records how they found you. Common values include:
Source data flows into the Dashboard Sankey chart, showing you which acquisition channels generate the most revenue.
Per-Client Profitability
The client detail page shows a profitability summary calculated from all paid invoices and logged time entries. Key metrics include:
- *Total Revenue — sum of all paid invoices for this client
- *Total Hours — sum of all time entries across all projects
- *Effective Hourly Rate — total revenue divided by total hours, showing what you actually earned per hour
// Per-client profitability calculation
function calculateProfitability(client: ClientWithRelations) {
const totalRevenue = client.invoices
.filter((inv) => inv.status === "PAID")
.reduce((sum, inv) => sum + inv.amount, 0);
const totalHours = client.projects
.flatMap((p) => p.timeEntries)
.reduce((sum, entry) => sum + entry.hours, 0);
const effectiveRate = totalHours > 0
? totalRevenue / totalHours
: null;
return { totalRevenue, totalHours, effectiveRate };
}Data Model
// Client model with enrichment fields
model Client {
id String @id @default(cuid())
name String
email String @unique
company String?
avatarUrl String? // Auto-fetched or uploaded
linkedinUrl String?
domain String? // Extracted from email
source String? // "referral", "upwork", "cold", etc.
referredById String? // Links to another Client
referredBy Client? @relation("Referrals", fields: [referredById])
referrals Client[] @relation("Referrals")
projects Project[]
invoices Invoice[]
createdAt DateTime @default(now())
}Related pages
- Lead Funnel — Where clients originate as leads
- Projects — Work tracked per client
- Invoices — Billing linked to each client