Skip to content

Repository files navigation

WealthFlow

Personal wealth management web application for tracking assets, liabilities, and financial goals with real-time price updates and multi-currency support.

Tech Stack

  • Frontend: React 19 + Tailwind CSS v4 + shadcn/ui
  • Landing Page: Astro 5 (static site)
  • Routing: TanStack Router (file-based)
  • Data Fetching: TanStack Query + @convex-dev/react-query
  • Forms: TanStack Form + Effect Schema (Standard Schema v1)
  • Charts: Recharts
  • Backend & Database: Convex (reactive document database)
  • Core Library: Effect (schemas, services, typed errors, branded types)
  • Build: Vite 7 (dashboard), Astro (landing)
  • Language: TypeScript 5.9 (strict mode)
  • Linting & Formatting: Ultracite (Oxlint + Oxfmt)
  • Git Hooks: Lefthook (pre-commit lint/format)

Prerequisites

Getting Started

# Enter the reproducible environment (installs dependencies on first use)
devenv shell
# Start dashboard, landing page, and Convex together
devenv up
# Start dashboard Vite dev server (port 5173)
pnpm dev:dashboard
# Start landing page Astro dev server (port 4321)
pnpm dev:landing
# Start Convex dev server
pnpm convex:dev
# The original pnpm command remains available too
pnpm dev:all

Scripts

CommandDescription
pnpm dev:dashboardStart dashboard Vite dev server
pnpm dev:landingStart landing page Astro dev server
pnpm build:dashboardTypeScript check + Vite production build
pnpm build:landingBuild landing page for production
pnpm convex:devStart Convex dev server
pnpm dev:allRun dashboard + landing + Convex concurrently
pnpm convex:deployDeploy Convex functions (prompts for creds)
pnpm convex:auth-keysGenerate JWT keys and optionally set in Convex
pnpm checkLint & format check (ultracite)
pnpm fixAuto-fix lint & format issues (ultracite)

Project Structure

wealthflow/
├── apps/
│ ├── dashboard/ # @wealthflow/dashboard (React SPA)
│ │ ├── convex/ # Convex schema & functions
│ │ └── src/ # React app source
│ └── landing/ # @wealthflow/landing (Astro static site)
│ └── src/ # Astro pages, components, styles
├── packages/
│ └── tsconfig/ # @wealthflow/tsconfig (shared TS configs)
├── scripts/ # Deployment & utility scripts
├── devenv.nix # Reproducible Node 24 development environment
├── devenv.yaml # Pinned Nix inputs
└── docs/ # Architecture & roadmap documentation

Self-Hosted Deployment (Docker Compose)

Architecture

The stack runs 4 Docker services:

ServiceInternal PortRole
convex-backend3210 / 3211Convex backend (API + site)
convex-dashboard6791Convex admin dashboard
dashboard8080Vite SPA served by serve
landing4321Astro static site

All services communicate over an internal Docker network. No ports are published (expose, not ports) — a reverse proxy or tunnel is expected to expose services publicly.

Prerequisites

  • Docker & Docker Compose
  • Node.js + pnpm (on your local machine, for npx convex commands)
  • A reverse proxy or tunnel to expose services publicly

1. Create the .env file

Create a .env file at the repository root with the following variables:

# Public URL of the Convex backend (must be reachable from the user's browser)VITE_CONVEX_URL=https://convex.example.com# Public URL of the Convex site endpoint (used for auth callbacks)VITE_CONVEX_SITE_URL=https://convex-site.example.com# Same values, used by the Convex backend itselfCONVEX_CLOUD_ORIGIN=https://convex.example.comCONVEX_SITE_ORIGIN=https://convex-site.example.com# Convex admin dashboard — URL to the Convex backendNEXT_PUBLIC_DEPLOYMENT_URL=https://convex.example.com# Instance secret — MUST be exactly 32 bytes (64 hex characters)# Generate with: openssl rand -hex 32# ⚠️ 64 bytes (128 hex chars) will crash the backend:# "Hex-decoded key was 64 bytes, not 32"INSTANCE_SECRET=<64-hex-chars># Landing pageSITE_URL=https://example.comPUBLIC_DASHBOARD_URL=https://app.example.comPUBLIC_CONTACT_EMAIL=hello@example.com# Optional — Plausible analyticsPUBLIC_PLAUSIBLE_DOMAIN=PUBLIC_PLAUSIBLE_SCRIPT_URL=https://plausible.io/js/script.js

Important:VITE_CONVEX_URL and VITE_CONVEX_SITE_URL are injected at build time by Vite into the client JS bundle. They must be publicly accessible URLs (not internal Docker hostnames).

2. Create the external Docker network

docker network create cloudflared-net

This network is declared as external in compose.yaml and must exist before starting the services. It allows your reverse proxy (e.g., Cloudflare Tunnel, Traefik, Caddy) to reach the containers.

3. Build and start

docker compose up -d --build

4. Generate the admin key

docker compose exec convex-backend ./generate_admin_key.sh

This outputs a key in the format convex-self-hosted|.... Save it — you'll need it for the next steps.

5. Deploy Convex functions

Run this from your local machine (not inside a container):

pnpm convex:deploy

6. Set up JWT keys for auth

The project uses @convex-dev/auth with the Password provider. It requires two environment variables:

  • JWT_PRIVATE_KEY — RSA 2048 private key in PKCS8 format (signs tokens)
  • JWKS — the corresponding public key as a JSON Web Key Set (verifies tokens)

Generate both keys and optionally set them as Convex environment variables:

pnpm convex:auth-keys

The script generates an RSA 2048 key pair, prints both values, then prompts you to set them directly via convex env set. You can also skip the prompt and set them manually via the Convex dashboard.

Gotchas:

  • JWT_PRIVATE_KEYmust contain real newlines, not literal \n characters.
  • Ed25519 keys will not work — the error will be "PrivateKeyInfo algorithm is not rsaEncryption". Use RSA 2048.
  • Both variables are required. Missing JWKS will cause Missing environment variable JWKS at runtime.

Documentation

See the docs/ folder for detailed documentation:

  • Architecture — Data model, tech stack, and project structure
  • Roadmap — Feature sprints and progress tracking

About

Personal wealth management web application for tracking assets, liabilities, and financial goals with real-time price updates and multi-currency support.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages