Files
dashboards/docs/PROJECT_CONTEXT.md
T
Alireza HassaniandCursor c33cab40fe Wire SMS registration, forgot password, and one-time login.
Connect business and customer login pages to login-otp and reset-password, and document the auth flows in project context.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 15:10:51 +03:30

574 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: August 5, 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 (Parspack) 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 (migrate-from-old + delete-data for portfolios/blogs(+news)/customers + categories; single-select) |
| `/businesses/:businessId/invoices` | Business invoices list |
| `/businesses/:businessId/invoices/new` | Issue invoice (full page) |
| `/users` | Users |
| `/websites` | Websites / domains |
| `/settings` | Platform settings (invoice templates + item templates) |
| `/settings/invoice-templates/new` | Create invoice template |
| `/settings/invoice-templates/:templateId` | Edit invoice template |
| `/invoices/:invoiceId` | Public invoice viewer (no auth; print to PDF) |
| `/profile` | Profile |
### Customer (`apps/customer`)
| Path | Page |
|------|------|
| `/login` | Login (password, register, forgot-password SMS, one-time OTP 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 (password, forgot-password SMS, one-time OTP 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 |
---
## Auth (login pages)
Business and customer login pages share the same SMS-backed flows (super-admin stays password-only).
| Flow | API | Notes |
|------|-----|--------|
| Password login | `POST /auth/login` | Rejects unverified cell when SMS is enabled |
| Register | `POST /auth/register` | Customer by website domain; with SMS on, customer goes to OTP step |
| Send OTP | `POST /auth/send-otp` | Redis 5-min code via Gama |
| One-time login | `POST /auth/login-otp` | Code only → tokens + marks cell verified |
| Forgot password | `POST /auth/reset-password` | Code + new password |
| Verify only | `POST /auth/verify-otp` | Marks verified; no tokens |
Auth helpers: `apps/*/src/services/authService.ts` (`login`, `loginWithOtp`, `resetPassword`, `sendOtp`, `register`). `AuthContext` exposes `login` + `loginWithOtp`.
---
## 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 |
| `036_invoices.sql` | Platform/business invoices + invoice item templates |
| `037_invoice_name.sql` | Optional invoice name |
| `038_invoice_templates.sql` | Full invoice templates + key points / accounts |
| `039_invoice_account_holder.sql` | Account holder name on bank accounts |
| `040_invoice_public_id.sql` | Opaque 12-digit `public_id` for public links |
After schema changes: `npx prisma generate` and restart the backend.
### Invoices (super-admin)
Two template layers + issued invoices:
| Layer | Purpose |
|-------|---------|
| **Invoice item templates** | Reusable line items (title, duration, worktime, desc, price, discounted) |
| **Invoice templates** | Full blueprints: name, top text, items (from item templates or custom), duplicatable key points, duplicatable bank accounts (bank name, account holder, card, IBAN) |
| **Invoices** | Issued to a business — start from an invoice template (editable) or blank |
| Endpoint | Purpose |
|----------|---------|
| `GET/POST /invoice-item-templates` | Platform line-item presets |
| `PATCH/DELETE /invoice-item-templates/:id` | Update/remove line-item preset |
| `GET/POST /invoice-templates` | Platform full invoice templates |
| `GET/PATCH/DELETE /invoice-templates/:id` | Get / update / delete invoice template |
| `GET/POST /businesses/:businessId/invoices` | List / issue invoices for a business |
| `GET/PATCH/DELETE /businesses/:businessId/invoices/:invoiceId` | Detail, status update, delete |
| `GET /public/invoices/:id` | Public show payload (issued/paid, no auth) |
**Invoice fields:** optional `name`, `topText`, `notes`, `invoiceTemplateId`, `status`, `publicUrl`, nested `items`, `keyPoints`, `accounts` (bank name, account holder, card, IBAN).
**Public invoice viewer (platform):**
- Route: super-admin SPA `/invoices/:id` (`PublicInvoicePage`) — glass layout, print-to-PDF, Approve (`issued``approved`), “Issued by” Meshkee footer
- Status `approved`: set from public show page; content becomes immutable for admins
- Edit: list pencil → `/businesses/:businessId/invoices/:invoiceId/edit` (hidden when approved)
- Links use opaque **12-digit `publicId`** (not sequential PK) — `GET /public/invoices/:publicId`
- Local/dev link: current Vite origin (e.g. `https://meshkee.app:5174/invoices/{publicId}`)
- Production link domain: `VITE_INVOICE_PUBLIC_DOMAIN` / `INVOICE_PUBLIC_DOMAIN` (default `meshkee.com`) — optional full origin override via `*_PUBLIC_BASE_URL`
- Until `meshkee.com` proxies or hosts `/invoices/*`, production links may need that DNS/nginx wiring (viewer code ships with super-admin build)
**Migrations:** `036_invoices.sql``040_invoice_public_id.sql`
**Super Admin UI:**
- `/settings` — Invoice templates list + item templates (top text preview = one-line ellipsis)
- `/settings/invoice-templates/new` · `/settings/invoice-templates/:id` — full-page template editor (not modal)
- `/businesses/:businessId/invoices` — list + view modal
- `/businesses/:businessId/invoices/new` — full-page issue form
- `/invoices/:id` — public viewer (no auth)
- Schema supports future `owner_scope=business` (business-owned templates)
---
## 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` |
| Invoices | `src/invoices/` |
| Media / S3 | `src/media/media.service.ts` |
| Prisma schema | `prisma/schema.prisma` |
### Super Admin
| Area | Path |
|------|------|
| Settings / invoice templates | `apps/super-admin/src/pages/SettingsPage.tsx` |
| Template / issue editors | `InvoiceTemplateEditorPage.tsx`, `IssueInvoicePage.tsx` |
| Shared draft fields | `apps/super-admin/src/components/InvoiceDraftFields.tsx` |
| Public invoice viewer | `apps/super-admin/src/pages/PublicInvoicePage.tsx` |
| Business invoices | `apps/super-admin/src/pages/BusinessInvoicesPage.tsx` |
| Invoice API client | `apps/super-admin/src/services/invoiceService.ts` |
| Invoice URL helpers | `apps/super-admin/src/lib/config.ts` |
---
## 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 + Super Admin **Sync SSL now** (`POST /domains/ssl-sync` → dashboards agent).
---
## Locale (FA / EN)
Dashboards support **Farsi + English** via shared `LocaleProvider` (`@meshkee/dashboard-ui`) and `LanguageSelect` in headers.
| Piece | Where |
|-------|--------|
| Locale state | `packages/dashboard-ui``LocaleContext` |
| Default language per business | `settings.branding.defaultLocale`: `'fa' \| 'en'` (default **`fa`**) |
| Applied on open | Business + customer `TenantBrandingProvider` once from tenant branding |
| Super-admin edit | Businesses list **Lang** column + edit modal |
| Business UI copy | `apps/business/src/i18n/messages.ts` + `useT()` |
| Customer UI copy | `apps/customer/src/i18n/` (parallel pattern) |
| Document titles | Locale-aware via `routeTitles` + `useDashboardDocumentTitle` |
| Fonts | `--font-ui: var(--font-en), var(--font-fa)` — see `.cursor/rules/ui-farsi-fonts.mdc` |
RTL: `dir="rtl"` when locale is `fa`.
---
## Business home dashboard
Home is **not** a 1:1 mirror of the sidebar. Tiles are curated (no Settings tile).
| Feature | Detail |
|---------|--------|
| Section cards | Count pill + arrow (settings/website have arrow only); hover fills primary |
| Counts | Existing list `total` with `pageSize=1` — light, not a new stats join |
| Charts (6/12 each) | Orders (+ add-to-basket) and Customers (+ active logins), last 30 days |
| Chart API | `GET .../orders/activity?days=30`, `GET .../customers/activity?days=30` |
| Chart colors | Primary series = theme primary; accent = purple for red theme, cyan for blue themes (`--chart-accent` from `businessPrimaryColors`) |
| Page aura | Slow-moving radial blobs on `body::before` (tokens + business `index.css`) |
Products overview page uses the same i18n + theme-aware `ProductActivityChart` (added vs updated, 12 months).
---
## Suggested next work
- Point `meshkee.com/invoices/*` at the public invoice viewer (proxy or dedicated host)
- Business-dashboard invoice templates + issue flow (`owner_scope=business`)
- Migrate business and super-admin fully onto `@meshkee/dashboard-core` / `@meshkee/dashboard-ui` (LocaleProvider already shared)
- Finish FA/EN coverage on remaining business form pages (many labels still English)
- 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