DevDash is a monorepo using pnpm workspaces and Turborepo. It consists of three packages:
apps/web- React SPA (Vite)apps/api- Express REST APIpackages/shared- Shared TypeScript types and Zod schemas
All monetary amounts are stored as integers in cents to avoid floating-point errors.
12350= EUR 123.50- Calculations are done in cents, displayed with
formatCurrency()
The invoice calculation engine in packages/shared/src/utils/italian-tax.ts handles:
- Subtotal = sum of line items (qty * unit price)
- Cassa previdenziale = % of subtotal (e.g. INPS 4%)
- Taxable base = subtotal + cassa
- IVA = % of taxable base (e.g. 22%)
- Bollo virtuale = EUR 2.00 flat (when IVA-exempt)
- Gross total = taxable base + IVA + bollo
- Ritenuta d'acconto = % of taxable base (e.g. 20%)
- Net payable = gross total - ritenuta
Invoices use atomic sequence numbering within a Prisma transaction:
- Format:
FT-{n}/{year}(e.g.FT-3/2026) - Unique constraint:
(userId, year, sequenceNumber)
Each feature (auth, clients, invoices, dashboard, settings) contains:
pages/- Route componentsapi.ts- API calls- Optional
components/,hooks/
SENT invoices past their dueDate are automatically marked as OVERDUE via:
- A check that runs before every
listInvoicesquery (ensures fresh data on page load) - A background hourly
setIntervalin the API server (catches overdue invoices without user interaction)
Both clients and invoices can be exported as CSV files (GET /clients/export, GET /invoices/export). Files include a UTF-8 BOM for Excel compatibility.
Users can upload a company logo (max 2MB, PNG/JPG/SVG/WebP) which is stored in uploads/logos/. When present, the logo is rendered in the PDF invoice header.
Zod schemas in packages/shared are used on both frontend (react-hook-form) and backend (Express middleware).
See apps/api/prisma/schema.prisma for the full schema.
Key models: User, BusinessProfile, Client, Invoice, InvoiceItem, UserSettings, RefreshToken.
- JWT access tokens (15min expiry)
- Refresh tokens stored in DB (7 day expiry)
- Automatic token refresh via Axios interceptor
- Zustand store persisted to localStorage