Initial commit: Meshkee dashboards monorepo.

Includes business, customer, and super-admin apps with shared packages and production deploy scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-07-22 13:48:53 +03:30
co-authored by Cursor
commit f566387c61
509 changed files with 62690 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
# Deploy dashboards (Debian VPS)
Stack: static Vite builds → Nginx → Let's Encrypt (HTTP-01).
Host: `45.149.76.52` · App path: `/opt/meshkee/dashboards` · Web root: `/var/www/meshkee/{super-admin,business,customer}`
| Host | App |
|------|-----|
| `manage.meshkee.com` | Super Admin |
| `business.<apex>` | Business dashboard |
| `customer.<apex>` | Customer dashboard |
API: `https://api.meshkee.com/api/v1` (separate server).
## New tenant SSL (automatic)
1. Add business + domain in Super Admin (apex, e.g. `sanihome.ir`)
2. Add Arvan **A** records (DNS-only): `business` + `customer` → dashboards VPS IP
3. Within ~2 hours, `/opt/meshkee/dashboards/scripts/ssl-sync.sh` expands the cert via
`GET https://api.meshkee.com/api/v1/internal/ssl/hosts` (`X-SSL-Sync-Token`)
Manual: `sudo /opt/meshkee/dashboards/scripts/ssl-sync.sh`
## Redeploy frontends
From your laptop (rsync source tree, then build on server):
```bash
rsync -az --delete \
--exclude node_modules --exclude .git --exclude '**/dist' --exclude '.env' \
-e 'ssh -i ~/.ssh/id_ed25519' \
./ root@45.149.76.52:/opt/meshkee/dashboards/
ssh root@45.149.76.52 'cd /opt/meshkee/dashboards && npm ci && npm run build && \
rsync -a --delete apps/super-admin/dist/ /var/www/meshkee/super-admin/ && \
rsync -a --delete apps/business/dist/ /var/www/meshkee/business/ && \
rsync -a --delete apps/customer/dist/ /var/www/meshkee/customer/'
```
Build env on server (`apps/*/.env`):
- All: `VITE_API_BASE_URL=https://api.meshkee.com/api/v1`
- Super Admin: `VITE_ADMIN_DOMAIN=manage.meshkee.com`
- Business/Customer: leave `VITE_BUSINESS_DOMAIN` unset
## Backend SSL sync (API server)
Env on API (`/opt/meshkee/app/.env`):
```
SSL_SYNC_TOKEN=<secret>
DASHBOARD_ADMIN_HOST=manage.meshkee.com
```
Endpoint: `GET /api/v1/internal/ssl/hosts``{ "hosts": ["manage.meshkee.com", "business.…", "customer.…"] }`
+453
View File
@@ -0,0 +1,453 @@
# MeshkeeApp — Project Context
> **For AI agents:** Read this file at the start of a new chat before making changes.
> Update this document when a major feature is completed or architecture changes.
Last updated: July 2026
---
## Repository layout
**Primary workspace:** open **`MeshkeeApp`** (this monorepo) as the Cursor workspace root.
```
MeshkeeApp/
apps/
super-admin/ # @meshkee/super-admin — port 5174 — meshkee.app
business/ # @meshkee/business-dashboard — port 5173 — business.{domain}
customer/ # @meshkee/customer-dashboard — port 5175 — customer.{domain}
packages/
dashboard-core/ # @meshkee/dashboard-core — API client, auth types, utils, tokens
dashboard-ui/ # @meshkee/dashboard-ui — shared React components
docs/ # This file + agent context
```
**Backend (separate repo):** sibling folder `MeshkeeApp Backend` — NestJS + Prisma + PostgreSQL. Not inside this monorepo.
**Stale workspace:** `MeshkeeApp Gen2` is empty/outdated — do not use it.
---
## Apps overview
| App | Package | Port | Local URL | Domain guard |
|-----|---------|------|-----------|--------------|
| Super Admin | `@meshkee/super-admin` | 5174 | `https://meshkee.app:5174` | `AdminDomainGuard` |
| Business | `@meshkee/business-dashboard` | 5173 | `http://business.sanihome.ir:5173` | `BusinessDomainGuard` |
| Customer | `@meshkee/customer-dashboard` | 5175 | `http://customer.sanihome.ir:5175` | `CustomerDomainGuard` |
### Shared packages migration
| App | `@meshkee/dashboard-core` | `@meshkee/dashboard-ui` |
|-----|---------------------------|-------------------------|
| Customer | ✅ wired | ✅ wired |
| Super Admin | ⏳ pending | ⏳ pending |
| Business | ⏳ pending | ⏳ pending |
When migrating an app to shared packages, prefer importing from `@meshkee/dashboard-core` / `@meshkee/dashboard-ui` instead of duplicating code.
---
## Tech stack
### All dashboard apps
- React 19, TypeScript, Vite, React Router
- CSS Modules + design tokens (`src/index.css`; customer also imports `@meshkee/dashboard-core/styles/tokens.css`)
- Auth via JWT (`src/context/AuthContext.tsx`, `src/services/authService.ts`)
- API client: `src/lib/api.ts` — base URL from `VITE_API_BASE_URL`
### Backend
- NestJS, Prisma, PostgreSQL, Redis
- S3-compatible storage (Parmin) for media
- API prefix: `/api/v1`
- BigInt IDs serialized as strings in JSON
---
## Local development
### 1. Monorepo setup
```bash
cd MeshkeeApp
npm install
```
### 2. Backend (sibling folder)
```bash
cd "../MeshkeeApp Backend"
cp .env.example .env # fill DATABASE_URL, JWT secrets, S3 keys, GROQ_API_KEY (or OPENAI_API_KEY)
npm install
# Run SQL migrations in database/migrations/ (in order)
npx prisma generate
npm run start:dev # default http://localhost:3000
```
### 3. Dashboard dev servers
From the monorepo root:
```bash
npm run dev:super-admin # https://meshkee.app:5174
npm run dev:business # http://business.sanihome.ir:5173
npm run dev:customer # http://customer.sanihome.ir:5175
```
Or from an app directory:
```bash
cd apps/business && npm run dev
```
Copy each app's `.env.example``.env` before first run.
**Super Admin HTTPS:** generate local certs with mkcert (see `apps/super-admin/.env.example`).
### 4. Hosts file
Add to `/etc/hosts` (one line per tenant):
```
127.0.0.1 meshkee.app
127.0.0.1 business.sanihome.ir
127.0.0.1 customer.sanihome.ir
127.0.0.1 business.safeteb.com
127.0.0.1 customer.safeteb.com
127.0.0.1 safeteb.com
```
**Multi-tenant:** leave `VITE_BUSINESS_DOMAIN` unset in business/customer `.env`. Each dashboard build resolves the tenant from `window.location.hostname` (`business.safeteb.com``safeteb.com`). One dev server and one production deploy serve all businesses; the backend maps domain → business in the DB.
### 5. Test accounts
| App | Phone | Password | Notes |
|-----|-------|----------|-------|
| Business | `+989122222222` | `password` | sanihome.ir → **business_id `4`** |
| Customer | (same user) | `password` | customer.sanihome.ir tenant |
| Super Admin | (platform admin) | — | see backend seed / team records |
---
## App routes
### Super Admin (`apps/super-admin`)
| Path | Page |
|------|------|
| `/login` | Login |
| `/` | Home |
| `/businesses` | Businesses list |
| `/users` | Users |
| `/websites` | Websites / domains |
| `/profile` | Profile |
### Customer (`apps/customer`)
| Path | Page |
|------|------|
| `/login` | Login |
| `/checkout` | Shopping cart checkout (standalone layout — login → cart → delivery → payment) |
| `/checkout/login` | Checkout sign-in step |
| `/checkout/cart` | Cart review |
| `/checkout/delivery` | Address or pickup |
| `/checkout/payment` | Discount code + payment |
| `/checkout/success` | Order confirmation |
| `/` | Home |
| `/profile` | Profile |
| `/addresses` | Addresses |
| `/orders` | Orders |
| `/favorites` | Favorites |
### Business (`apps/business`)
| Path | Page | Backend connected? |
|------|------|-------------------|
| `/login` | Login | Yes |
| `/` | Home | Partial |
| `/products` | Products hub | — |
| `/products/categories` | Category tree + variations | Yes |
| `/products/brands` | Product brands (linear list) | Yes |
| `/products/list` | My Products grid | Yes |
| `/products/new` | Add product | Yes |
| `/products/edit/:id` | Edit product | Yes |
| `/products/detail/:id` | Product details + gallery | Yes |
| `/products/settings` | Product moderation settings | Yes |
| `/store` | Store hub | Partial |
| `/store/items` | Store items (product variants) | Yes |
| `/store/settings` | Online sell + order process steps | Yes |
| `/customers` | Business customers list | Yes |
| `/store/orders` | Orders list + filters | Yes |
| `/blog` | Blog hub | Yes |
| `/blog/list` | My Blogs grid | Yes |
| `/blog/new`, `/blog/edit/:id` | Add/edit blog | Yes |
| `/blog/categories` | Blog category tree | Yes |
| `/blog/settings` | Comment moderation | Yes |
| `/portfolios` | Portfolios hub | Yes |
| `/portfolios/list` | My Portfolios grid | Yes |
| `/portfolios/detail/:id` | Portfolio detail + gallery | Yes |
| `/portfolios/new`, `/portfolios/edit/:id` | Add/edit portfolio | Yes |
| `/portfolios/categories` | Portfolio category tree | Yes |
| `/portfolios/settings` | Comment moderation | Yes |
| `/website` | Website hub | Placeholder |
| `/website/sliders` | Homepage sliders | Yes |
| `/website/special-categories` | Featured category groups | Yes |
| `/website/special-brands` | Featured brand groups | Yes |
| `/website/special-items` | Special item carousels | Yes |
| `/website/contact` | Contact us submissions list | Yes |
| `/website/subscriptions` | Subscriptions | Placeholder |
| `/website/faq` | FAQ | Placeholder |
| `/website/badges` | Badges | Placeholder |
| `/website/e-payment` | E-payment | Placeholder |
---
## API surface (business-scoped)
All routes require JWT + business permission. `businessId` comes from tenant context after login.
### Categories
- `GET/POST/PATCH/DELETE /businesses/:businessId/categories`
- `POST /businesses/:businessId/categories/ai-generate` — AI-generated category tree (`{ prompt }`)
- `GET /businesses/:businessId/categories/color-presets`
- `GET/PUT /businesses/:businessId/categories/:categoryId/variations`
- `POST /businesses/:businessId/categories/:categoryId/technical-form/ai-suggest`
Query: `?entityType=product` for product categories.
### Brands
- `GET/POST/PATCH/DELETE /businesses/:businessId/brands`
- `GET /businesses/:businessId/brands/:brandId`
Products accept optional `brandId` on create/update.
### Products
- `GET/POST/PATCH/DELETE /businesses/:businessId/products`
- `POST /businesses/:businessId/products/ai-create` — AI-generated product draft
- `GET /businesses/:businessId/products/:productId`
### Contact submissions
- `GET /businesses/:businessId/contact-submissions` — paginated list
- `GET /businesses/:businessId/contact-submissions/:submissionId`
- `POST /tenants/:host/contact-submissions` — public website contact form
### Business settings
- `GET/PATCH /businesses/:businessId/settings` — includes `branding.primaryColor`
### Portfolios
- `GET/POST/PATCH/DELETE /businesses/:businessId/portfolios`
- `GET /businesses/:businessId/portfolios/:portfolioId`
- Portfolio categories: `?entityType=portfolio`
### Store items (sellable product variants)
- `GET /businesses/:businessId/store-items` — paginated list
- `POST /businesses/:businessId/store-items` — batch create with price + stock
### Store specials
- `GET/POST/PATCH/DELETE /businesses/:businessId/store-specials`
- `GET /tenants/:host/store-specials` — public API for websites
### Website homepage widgets
- `GET/POST/PATCH/DELETE /businesses/:businessId/website/category-groups` — groups with `categoryIds[]`
- `GET/POST/PATCH/DELETE /businesses/:businessId/website/brand-groups` — groups with `brandIds[]`
- `GET /tenants/:host/website/category-groups` — public category groups
- `GET/POST/PATCH/DELETE /businesses/:businessId/website/sliders` — sliders with `slides[]` (imageMediaId, title, linkUrl)
- `GET /tenants/:host/website/sliders` — public sliders
### Customers
- `GET/POST/PATCH/DELETE /businesses/:businessId/customers`
- `GET /businesses/:businessId/customers/search` — autocomplete for order cart
### Orders
- `GET/POST/PATCH/DELETE /businesses/:businessId/orders`
### Product variants & variation values
- `GET/POST/DELETE /businesses/:businessId/products/:productId/variants`
- `GET/PUT /businesses/:businessId/products/:productId/variations`
### Technical forms
- `GET/PUT /businesses/:businessId/categories/:categoryId/technical-form`
- `POST .../technical-form/ai-suggest`
- `GET/PUT /businesses/:businessId/products/:productId/technical-info`
### Media
- `POST /businesses/:businessId/media` — upload to S3
---
## Feature status (business app)
### Connected to backend
- Auth, tenant, business context
- Product categories, variations, products CRUD, media upload
- Product variation values, store items, draft cart, admin orders
- Category technical data forms, product technical data
- Product/store settings, blog, portfolios, contact submissions
### Still local / dummy data
- Product comments modal (`apps/business/src/data/productComments.ts`)
- Legacy seeds in `apps/business/src/data/`
---
## Product variations — business rule
**Product Variations modal:** user selects which category variation **values** apply to the product. Saved via `PUT .../products/:id/variations`.
**Store items:** sellable variants with one value per variation type + price + stock. Saved via `POST .../store-items` (batch).
---
## Database migrations (Backend)
Run in order from `MeshkeeApp Backend/database/migrations/`:
| Migration | Purpose |
|-----------|---------|
| `009_categories_name_fa.sql` | `name_fa` on categories — **required** or categories 500 |
| `010_category_variations.sql` | variation tables |
| `016_product_variation_values.sql` | product variation values |
After schema changes: `npx prisma generate` and restart the backend.
---
## Key files by area
### Shared packages
| Area | Path |
|------|------|
| API client factory | `packages/dashboard-core/src/api/createApiClient.ts` |
| IRT price utils | `packages/dashboard-core/src/utils/irtPrice.ts` |
| Design tokens | `packages/dashboard-core/src/styles/tokens.css` |
| Toast, Breadcrumbs, etc. | `packages/dashboard-ui/src/` |
### Business app
| Area | Path |
|------|------|
| Categories | `apps/business/src/pages/CategoriesPage.tsx` |
| Products | `apps/business/src/pages/MyProductsPage.tsx`, `AddNewProductPage.tsx` |
| Store / orders | `apps/business/src/pages/StoreItemsPage.tsx`, `OrdersPage.tsx` |
| Services | `apps/business/src/services/` |
| ID helper | `apps/business/src/utils/id.ts``createId()` fallback |
### Backend (sibling repo)
| Area | Path |
|------|------|
| Products | `src/products/products.service.ts` |
| Categories | `src/categories/categories.service.ts` |
| Media / S3 | `src/media/media.service.ts` |
| Prisma schema | `prisma/schema.prisma` |
---
## UI conventions
Match **Super Admin** compact field sizing across all dashboards — see `.cursor/rules/ui-compact-fields.mdc`:
```css
--field-height: 38px;
--field-padding-y: 9px;
--field-font-size: 13px;
```
Global input sizing lives in `packages/dashboard-core/src/styles/tokens.css` (customer) and each apps `index.css` (business / super-admin). Change password → shared `PasswordResetModal` from `@meshkee/dashboard-ui`.
- **Farsi text:** Customer app → Yekan Bakh (`src/fonts/yekanbakh.css`, `--font-fa` in `index.css`); business/super-admin → IranYekan (`iranyekan.css`, `.faText`). Input + placeholder must share the same stack — see `.cursor/rules/ui-farsi-fonts.mdc`
- **Per-business theme:** Super Admin sets `branding.primaryColor`; business app applies via `BusinessThemeProvider`
- **RTL:** Farsi inputs use `dir="rtl"` and the apps `--font-fa`
- **IRT prices:** comma-separated thousands + `IRT` suffix — see `.cursor/rules/ui-irt-price.mdc`
- **Toasts:** see `.cursor/rules/ui-toasts.mdc`
- **Icon controls:** see `.cursor/rules/ui-control-buttons.mdc`, `ui-tooltips.mdc`
---
## Known issues & fixes
| Problem | Cause | Fix |
|---------|-------|-----|
| Categories 500 | Missing `name_fa` column | Run migration `009` |
| `crypto.randomUUID is not a function` | Non-secure origin | Use `createId()` from `apps/business/src/utils/id.ts` |
| Media upload 400 | Bad MIME / empty data URL | `ensureJpegUploadFile()`; backend Sharp validation |
| Super Admin HTTPS warning | Missing mkcert certs | Run mkcert per `apps/super-admin/.env.example` |
| Workspace approval popups | Wrong workspace root | Open `MeshkeeApp` monorepo root in Cursor |
---
## Build commands
```bash
npm run build:packages # shared packages only
npm run build:customer
npm run build:business
npm run build:super-admin
npm run build # everything
```
---
## Git & handoff checklist
1. Push **MeshkeeApp** monorepo and **MeshkeeApp Backend** to remote
2. Copy `.env` files securely (never commit)
3. Run migrations on the database
4. Open **MeshkeeApp** as Cursor workspace root
5. Start a new agent chat: **"Read `docs/PROJECT_CONTEXT.md` and continue…"**
---
## Branding: logo → favicon
When a business logo is uploaded/changed in **Business Profile**, the backend generates a **48×48 PNG favicon** (sharp), stores it as media, and links `businesses.favicon_media_id`.
| Surface | How favicon is applied |
|---------|------------------------|
| Business dashboard | `TenantBrandingProvider``applyDocumentFavicon` |
| Customer dashboard | same |
| Public website (sanihome) | `generateMetadata().icons` from `faviconUrl` |
APIs expose `faviconUrl` (falls back to `logoUrl` if favicon missing):
- `GET /businesses/:id/profile`
- `GET /tenants/:host`
- `GET /tenants/:host/website/business-info`
Migration: `database/migrations/033_business_favicon.sql`
---
## Production deploy (dashboards)
| | |
|--|--|
| VPS | `45.149.76.52` |
| Super Admin | `https://manage.meshkee.com` |
| Business | `https://business.<apex>` (e.g. `business.sanihome.ir`) |
| Customer | `https://customer.<apex>` |
| API | `https://api.meshkee.com/api/v1` (host `185.164.72.119`) |
| Docs | `docs/DEPLOY.md` |
SSL: Certbot cert `meshkee-dashboards` + cron `ssl-sync.sh` every 2h (option A: `GET /api/v1/internal/ssl/hosts`).
---
## Suggested next work
- Migrate business and super-admin to `@meshkee/dashboard-core` / `@meshkee/dashboard-ui`
- Connect product comments to backend
- Wire order status transitions to store `orderProcessSteps`
- Enforce `onlineSellEnabled` on public website checkout
- Postman collection updates for variants endpoints
- Add `sanihome.ir` (and other tenants) in production Super Admin so tenant APIs resolve
---
## Agent instructions
1. **Minimize scope** — only change what the task requires
2. **Match existing patterns** — services in `src/services/`, CSS modules, NestJS modules per domain
3. **Do not commit** unless the user explicitly asks
4. **Test user:** `+989122222222` / `password`; business id **4** for sanihome.ir
5. **Restart backend** after Prisma or module changes
6. Update **this file** when completing a major feature