This repository contains a decoupled CRM self-service portal built with two independent layers: a Frappe backend app (backend/custom_crm_api) that exposes authenticated REST endpoints via @frappe.whitelist(), and a React + Vite frontend (frontend/) that communicates with those endpoints through Axios. The frontend uses Zustand for auth state, TanStack Query for server state, and React Router v6 for client-side routing. All UI components are built with Tailwind CSS and contain inline comments pointing to Untitled UI React replacements, making a design-system upgrade a straightforward swap.
| Requirement | Version |
|---|---|
| Node.js | 20 + |
| Python | 3.11 + |
| Frappe Bench | Latest stable |
| npm | 10 + |
You must have a running Frappe bench with at least one site before proceeding.
# 1. Copy the app into your bench's apps directory
cp -r backend/custom_crm_api /path/to/bench/apps/
# 2. Install the app onto your site
cd /path/to/bench
bench --site your-site.localhost install-app custom_crm_api
# 3. Run migrations (creates any required DocType records)
bench --site your-site.localhost migrate
# 4. Restart the bench
bench restartTip: If you want the role "Customer Portal User" to exist, create it in Frappe's Role Manager (Setup → Roles) before registering portal users.
Because the React dev server runs on a different origin (localhost:5173) from
Frappe (localhost:8000), you must enable CORS in Frappe's
common_site_config.json (found at frappe-bench/sites/common_site_config.json):
{
"allow_cors": "http://localhost:5173",
"cors_headers": "Authorization,Content-Type,X-Frappe-CSRF-Token"
}This project's Frappe instance:
medic-demo.thedaystar.co.zaAdd your local dev server origin (e.g.http://localhost:5173) toallow_corson that server.
Restart gunicorn after editing:
bench restartIn production the React build is served from the same domain via nginx, so CORS headers are not required. Remove or restrict
allow_corsin production.
cd frontend
# Copy the development environment file
cp .env.development .env.local # or just use .env.development directly
# Install dependencies
npm install
# Start the Vite dev server (proxies /api → localhost:8000)
npm run devOpen http://localhost:5173.
-
Build the frontend:
cd frontend npm run build # Output is in frontend/dist/
-
Serve
dist/with nginx and proxy/apito Frappe:server { listen 80; server_name your-frappe-site.com; root /path/to/frontend/dist; index index.html; # SPA fallback location / { try_files $uri $uri/ /index.html; } # Frappe API proxy location /api { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Frappe-CSRF-Token $http_x_frappe_csrf_token; } }
-
Update
frontend/.env.productionwith your production URL, then rebuild.
Every placeholder component contains a comment in the format:
// UNTITLED UI: Replace with <ComponentName> —
// see untitledui.com/react/components/<slug>To upgrade:
-
Install Untitled UI React via their CLI:
npx @untitled-ui/react init
-
Follow the token setup documented at untitledui.com/react/docs and add the token overrides to
frontend/src/assets/global.cssinside the@theme {}block. -
Swap each component by searching for
UNTITLED UI:infrontend/src/and replacing the placeholder implementation with the imported Untitled UI component according to the linked documentation. -
Remove the placeholder component files once all usages are migrated.