- License key generator tool at ~/Desktop/LicenseGenerator - Backend validator route (GET /api/license/status, POST /api/license/activate) - Activation page in sidebar with status, key input, and free/premium feature list - useLicense hook for frontend license state - Feature gates: premium eligibility buttons (DDMA, DeltaIns, Tufts, United, CCA) disabled without license - Feature gates: premium claim buttons (CCA, Delta MA, United, Tufts) and all PreAuth buttons disabled without license - Free features always active: MassHealth eligibility/claim, Documents, Payments, Backups, Reports - README: license key generator usage instructions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
13 KiB
Dental Manager - Starter
A monorepo setup to manage both Backend and Frontend of the Dental Manager application.
🖥️ Setup Guide (Fresh Machine)
Follow these steps in order after cloning the repository.
Step 1 — Clone the repository
git clone <your-repo-url>
cd DentalManagementMHAprilgg
Step 2 — Install Node.js
Required to run the Backend and Frontend.
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Verify
node -v # should print v20.x.x
npm -v
Step 3 — Install Python
Required to run the Selenium and OCR services.
sudo apt-get install -y python3 python3-pip python3-venv
# Verify
python3 --version # should print 3.10 or higher
Step 4 — Install Chrome
Required for the Selenium service to control a browser.
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
sudo apt-get update
sudo apt-get install -y google-chrome-stable
# Verify
google-chrome --version
The
webdriver-managerpackage (included inrequirements.txt) automatically downloads the matching ChromeDriver — no manual driver setup needed.
Step 5 — Install PostgreSQL
Primary database for the application.
sudo apt-get install -y postgresql postgresql-contrib
sudo systemctl enable postgresql
sudo systemctl start postgresql
# Create the database the app uses
sudo -u postgres psql -c "CREATE DATABASE dentalapp OWNER postgres;"
# Set the postgres user password to match packages/db/.env
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'mypassword';"
Enable password authentication over TCP
By default Debian uses scram-sha-256 or peer auth for local connections, which blocks password login. Switch to md5:
sudo sed -i 's/scram-sha-256/md5/g' /etc/postgresql/*/main/pg_hba.conf
sudo systemctl restart postgresql
Verify
psql -U postgres -d dentalapp -h 127.0.0.1 -W
# Enter: mypassword
# You should see: dentalapp=#
The
DATABASE_URLinpackages/db/.envis already set to:DATABASE_URL="postgresql://postgres:mypassword@localhost:5432/dentalapp"
Step 6 — Install Redis
Used as the job queue for Selenium and OCR background tasks.
sudo apt-get install -y redis-server
sudo systemctl enable redis-server
sudo systemctl start redis-server
# Verify
redis-cli ping # should print: PONG
Step 7 — Install Node.js dependencies
npm install
Step 8 — Install Python dependencies
Python dependencies are installed automatically by npm install (Step 7) via each service's postinstall script. Each service creates its own .venv virtual environment — no manual pip commands needed.
This approach is required on Debian 13+ where system-wide pip installs are blocked (PEP 668).
Step 9 — Set up environment variables
Copy the .env.example files and fill in the required values.
npm run setup:env
Step 9a — Set the Cloudflare subdomain for this office
After running npm run setup:env, open the two .env files and fill in this office's Cloudflare subdomain.
apps/Frontend/.env
VITE_CLOUDFLARE_HOST=yoursubdomain.mydentalofficemanagement.com
apps/Backend/.env
CLOUDFLARE_HOST=yoursubdomain.mydentalofficemanagement.com
FRONTEND_URLS=http://localhost:3000,http://yoursubdomain.mydentalofficemanagement.com,https://yoursubdomain.mydentalofficemanagement.com
Replace yoursubdomain with this office's actual subdomain (e.g. summitdentalcare).
If you skip this step, Cloudflare tunnel access will not work. LAN access still works without it.
Step 10 — Set up the database
# Run migrations
npm run db:migrate
# Generate Prisma types
npm run db:generate
# Insert seed data
npm run db:seed
Step 11 — Configure nginx
The repo includes nginx.conf in the project root. Install it as the active site config:
sudo cp nginx.conf /etc/nginx/sites-available/dental-app
sudo ln -sf /etc/nginx/sites-available/dental-app /etc/nginx/sites-enabled/dental-app
sudo nginx -t && sudo systemctl reload nginx
Important: The
/api/location block must includeproxy_set_header Authorization $http_authorization;Without it, nginx strips the Authorization header and the backend returns "Access denied. No token provided."
Step 12 — Run the app
Open two terminals:
Terminal 1 — Backend + Frontend:
npm run dev
On first boot the server automatically seeds all AI chat templates, SMS templates, and greeting messages for every user — no manual configuration needed.
Terminal 2 — Selenium service:
cd apps/SeleniumService
.venv/bin/python3 agent.py
Step 13 — Create desktop shortcut (optional)
Instead of opening two terminals manually every time, you can create a desktop shortcut that starts both services with a single double-click.
Run this once after cloning and installing:
bash ~/Desktop/DentalManagementMH06/setup-desktop-shortcut.sh
A Dental App shortcut will appear on your desktop. Double-clicking it opens:
- A terminal running
npm run dev(Backend + Frontend) - A terminal running the Selenium service
No username or path editing needed — the script automatically detects the current user's home folder.
📖 Developer Documentation
- Setting up server environment — the first step, to run this app in environment.
- Development Hosts & Ports — which app runs on which host/port, and how to configure
.envfor LAN or single-device access
This is a Turborepo. What's inside?
Apps and Packages
apps/Backend— Express.js API serverapps/Frontend— React + Vite frontendapps/SeleniumService— Python FastAPI service for browser automation (insurance eligibility, claims)apps/PaymentOCRService— Python service for payment OCR extraction@repo/eslint-config— shared ESLint configuration@repo/typescript-config— sharedtsconfig.jsons
Each package/app is 100% TypeScript (except the Python services).
Utilities
- Tailwind CSS for styles
- TypeScript for static type checking
- ESLint for code linting
- Prettier for code formatting
Cloudflare Tunnel Setup (Remote Access per Office)
This connects each office's local app to a public subdomain via a Cloudflare Tunnel — no port forwarding needed, local network access is unchanged.
How it works:
- Local network: other office PCs reach the app directly at
http://<machine-ip>:3000 - Internet: anyone reaches the app via
https://<subdomain>.mydentalofficemanagement.com - Both paths hit the same app simultaneously with no conflict
Step 1 — Add domain to Cloudflare (done once for all offices)
Skip this step if the domain is already on Cloudflare.
- Go to
dash.cloudflare.com→ Add a site → entermydentalofficemanagement.com(free plan) - Cloudflare scans existing DNS records — review and keep them
- Cloudflare gives you 2 nameservers (e.g.
holly.ns.cloudflare.com,amir.ns.cloudflare.com) - Log into Ionos → replace the domain's nameservers with Cloudflare's two
- Wait 10–30 min → Cloudflare emails you when active
- DNSSEC: if not purchased on Ionos, it was never enabled — nothing to turn off
Step 2 — Install cloudflared on the office PC
Run this in a terminal on the office machine:
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
sudo dpkg -i cloudflared.deb
cloudflared --version
Step 3 — Authenticate with Cloudflare
cloudflared tunnel login
A browser window opens → click mydentalofficemanagement.com → terminal shows success and saves a certificate to ~/.cloudflared/cert.pem.
Step 4 — Create a tunnel for this office
Use a unique tunnel name per office:
cloudflared tunnel create <office-tunnel-name>
# Example: cloudflared tunnel create summit-dental-app
Note the tunnel ID (UUID) printed — you need it in Step 5.
Step 5 — Create the config file
sudo mkdir -p /etc/cloudflared
sudo nano /etc/cloudflared/config.yml
Paste (replace tunnel ID, credentials path, and subdomain for this office):
tunnel: <tunnel-ID>
credentials-file: /home/ee/.cloudflared/<tunnel-ID>.json
ingress:
- hostname: <subdomain>.mydentalofficemanagement.com
service: http://localhost:3000
- service: http_status:404
Save: Ctrl+O → Enter → Ctrl+X.
Step 6 — Route DNS for this office's subdomain
cloudflared tunnel route dns <office-tunnel-name> <subdomain>.mydentalofficemanagement.com
Step 7 — Install as a system service (auto-starts on boot)
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
sudo systemctl status cloudflared
Step 8 — Allow the subdomain in Vite
In apps/Frontend/vite.config.js, add the subdomain to server.allowedHosts so Vite does not block external requests.
Step 9 — Allow the subdomain in backend CORS
In apps/Backend, add the subdomain URL to the allowed CORS origins so login and API calls work from the public URL.
Example — Adding Summit Dental Care
Office subdomain: summitdentalcare.mydentalofficemanagement.com
Step 1: Already done (domain is on Cloudflare).
Step 2: Install cloudflared on Summit Dental's PC.
Step 3: Run cloudflared tunnel login on Summit Dental's PC.
Step 4:
cloudflared tunnel create summit-dental-app
# Example output: Created tunnel summit-dental-app with id a1b2c3d4-...
Step 5 — /etc/cloudflared/config.yml:
tunnel: a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx
credentials-file: /home/ee/.cloudflared/a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json
ingress:
- hostname: summitdentalcare.mydentalofficemanagement.com
service: http://localhost:3000
- service: http_status:404
Step 6:
cloudflared tunnel route dns summit-dental-app summitdentalcare.mydentalofficemanagement.com
Step 7:
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
Step 8: Add summitdentalcare.mydentalofficemanagement.com to allowedHosts in vite.config.js.
Step 9: Add https://summitdentalcare.mydentalofficemanagement.com to backend CORS allowed origins.
Multi-office overview
Each office runs its own cloudflared tunnel on its own PC. Ports never conflict because each PC is a separate machine.
| Office | Local access | Public access | Tunnel name |
|---|---|---|---|
| Community Dentists of Lowell | http://192.168.1.236:3000 |
https://communitydentistsoflowell.mydentalofficemanagement.com |
dental-app |
| Summit Dental Care | http://<its-ip>:3000 |
https://summitdentalcare.mydentalofficemanagement.com |
summit-dental-app |
| Next office | http://<its-ip>:3000 |
https://<subdomain>.mydentalofficemanagement.com |
<office>-app |
License Key Generator
The license key generator is a private tool that lives only on your dev PC. Use it to generate a new license key for any office every 3 months.
Location: /home/ff/Desktop/LicenseGenerator/
Generate a 3-month key (default):
node /home/ff/Desktop/LicenseGenerator/generate-license.js
Generate a key with a custom duration:
node /home/ff/Desktop/LicenseGenerator/generate-license.js --months=6
Example output:
=== Dental App License Key ===
License Key: DENTAL-8ED7AAEF3E0CA008D98CC1E0-2026-08-26
Expires: 2026-08-26
Duration: 3 month(s)
Paste this key into the Activation page in the app.
Workflow:
- Office pays renewal fee
- Run the generator script above
- Copy the License Key
- Paste it into the Activation page in the app (via RustDesk or in person)
- Record the key, office name, expiry, and payment in your records
Important — secret.key:
/home/ff/Desktop/LicenseGenerator/secret.keyis the private secret used to sign all keys- Back it up on a USB drive or password manager
- If lost, all existing keys become invalid and new keys must be issued to all offices
Claude Code Memory
Claude Code (the AI assistant used to build this project) stores its memory locally on the PC. This memory contains project context, architecture decisions, feature history, and working preferences — allowing Claude to pick up where it left off in new sessions.
Memory location:
/home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/
To copy to a new PC:
- On the old PC, copy the memory folder:
cp -r /home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/ /media/usb/claude-memory-backup/
- On the new PC, recreate the directory and paste:
mkdir -p /home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/
cp -r /media/usb/claude-memory-backup/* /home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/
The memory is plain markdown files and can also be copied manually via a USB drive or file manager. Enable "show hidden files" (Ctrl+H) in the file manager to see the .claude folder.