diff --git a/.env.example b/.env.example
index 4ae7439..9ce1f0b 100644
--- a/.env.example
+++ b/.env.example
@@ -1,10 +1,12 @@
# App runtime
NODE_ENV="development"
-# Frontend env vars for the Vite app
+# Frontend env vars for the Vite apps
+# Use "/api" for same-origin nginx proxy setups (app/admin subdomain or docker web surfaces).
VITE_API_BASE_URL="http://localhost:4000/api"
VITE_GOOGLE_MAPS_PLATFORM_KEY="YOUR_BROWSER_MAPS_KEY"
WEB_PORT="3000"
+ADMIN_WEB_PORT="3001"
# Backend env vars
## For Docker Compose deployments, point DATABASE_URL at the internal "db" host.
@@ -13,7 +15,7 @@ DATABASE_URL="postgres://postgres:postgres@localhost:5432/leads4less"
COOKIE_SECRET="CHANGE_ME_IN_LOCAL_ENV"
APP_HOST="0.0.0.0"
APP_PORT="4000"
-APP_ORIGIN="http://localhost:3000"
+APP_ORIGIN="http://localhost:3000,http://localhost:3001"
SESSION_TTL_DAYS="30"
GOOGLE_MAPS_SERVER_KEY="YOUR_SERVER_MAPS_KEY"
diff --git a/.gitignore b/.gitignore
index 4e3861c..f8e9c31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
node_modules/
build/
dist/
+dist-admin/
dist-server/
coverage/
.DS_Store
diff --git a/Dockerfile b/Dockerfile
index c6e3440..f0206f4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,12 +11,16 @@ COPY package*.json ./
RUN npm ci
COPY . .
-RUN npm run build && npm run build:api
+RUN npm run build && npm run build:admin && npm run build:api
FROM nginx:alpine AS web
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.default.conf /etc/nginx/conf.d/default.conf
+FROM nginx:alpine AS web-admin
+COPY --from=build /app/dist-admin /usr/share/nginx/html
+COPY nginx.admin.conf /etc/nginx/conf.d/default.conf
+
FROM node:22-alpine AS runtime-base
WORKDIR /app
diff --git a/README.md b/README.md
index 564619a..04650aa 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,10 @@ LocaleScope is a React + Vite app for researching local markets, saving business
- `COOKIE_SECRET`
- `GOOGLE_MAPS_SERVER_KEY`
- Stripe env vars below if you want to test billing locally
-3. Run the frontend:
+3. Run the user frontend:
`npm run dev:web`
+4. Run the admin frontend (dedicated surface):
+ `npm run dev:admin`
The local backend and scripts load both `.env` and `.env.local`, with `.env.local` taking precedence, so you can keep the full local setup in one file during development.
@@ -42,8 +44,8 @@ If you open the app from another machine on your LAN, set `VITE_API_BASE_URL` an
- [ ] Copy `.env.example` to `.env.local` and fill required keys: `DATABASE_URL`, `COOKIE_SECRET`, `GOOGLE_MAPS_SERVER_KEY`, `VITE_GOOGLE_MAPS_PLATFORM_KEY`, and Stripe keys if testing billing
- [ ] Run migrations: `npm run migrate`
- [ ] Set `ALLOW_ADMIN_BOOTSTRAP=true` and define `ADMIN_BOOTSTRAP_TOKEN`
-- [ ] Start web, API, and worker: `npm run dev:web`, `npm run dev:api`, `npm run dev:worker`
-- [ ] Visit `/auth` and create the first site admin
+- [ ] Start app web, admin web, API, and worker: `npm run dev:web`, `npm run dev:admin`, `npm run dev:api`, `npm run dev:worker`
+- [ ] Visit the admin surface and create the first site admin
- [ ] Disable bootstrap after first admin creation: `ALLOW_ADMIN_BOOTSTRAP=false`
- [ ] Verify admin billing access at `/api/admin/billing/workspaces`
@@ -56,7 +58,7 @@ Bootstrap mode is only needed when no active application admin exists.
1. Run migrations first: `npm run migrate`
2. Set `ALLOW_ADMIN_BOOTSTRAP=true` and `ADMIN_BOOTSTRAP_TOKEN` in your env file
-3. Visit `/auth`, then create the first account in "Create first site admin" mode
+3. Visit the dedicated admin surface, then create the first account in "Create first site admin" mode
4. After the first admin is created, set `ALLOW_ADMIN_BOOTSTRAP=false`
## Stripe Billing Setup
@@ -127,18 +129,26 @@ Safe mutation pilot:
- `POSTGRES_USER`
- `POSTGRES_PASSWORD`
- `WEB_PORT`
+ - `ADMIN_WEB_PORT`
- `APP_PORT`
- `COOKIE_SECRET`
- `GOOGLE_MAPS_SERVER_KEY`
- - `VITE_API_BASE_URL` (use `/api` when web and API share a domain via the nginx proxy)
+ - `VITE_API_BASE_URL` (use `/api` when web/admin and API share a domain via the nginx proxy)
- `VITE_GOOGLE_MAPS_PLATFORM_KEY`
2. Create the shared Docker network if it does not exist:
`docker network create locale-all || true`
3. Build and start the full stack:
`docker compose up --build`
-The Compose stack starts PostGIS, waits for the database to become healthy, runs migrations automatically, and then starts the API, worker, and web containers.
-With the default `.env.example` values, the app is exposed on `http://localhost:3000` and the API on `http://localhost:4000`.
+The Compose stack starts PostGIS, waits for the database to become healthy, runs migrations automatically, and then starts the API, worker, user-web, and admin-web containers.
+With the default `.env.example` values, the user app is exposed on `http://localhost:3000`, the admin app on `http://localhost:3001`, and the API on `http://localhost:4000`.
+
+## App and Admin Surfaces
+
+- User and admin UIs now run on separate frontend surfaces/build targets.
+- The main app no longer links to or renders admin routes/tabs.
+- Admin tasks live on the dedicated admin surface and continue using the same API/session auth.
+- `APP_ORIGIN` should include both origins for CORS/session checks, for example: `https://localescope.com,https://admin.localescope.com`.
## Database Layout
diff --git a/admin.html b/admin.html
new file mode 100644
index 0000000..b6a8099
--- /dev/null
+++ b/admin.html
@@ -0,0 +1,12 @@
+
+
+