Files
dashboards/docs/PROJECT_CONTEXT.md
Alireza HassaniandCursor f21041b950 Make public invoices RTL for Farsi businesses and fix invoice back-arrow direction.
Also add optional English name fields on the add-customer modal to match the API.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 15:46:18 +03:30

600 lines
26 KiB
Markdown
Raw Permalink 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 11, 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, OPENAI_API_KEY (or GROQ_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). **Add domain** accepts optional Git repo URL → provisions storefront on websites VM and sets `deploy_slug` |
| `/businesses/:businessId/invoices` | Business invoices list |
| `/businesses/:businessId/invoices/new` | Issue invoice (full page) |
| `/users` | Users (business filter: Customer vs Manager → Admin/Editor/Viewer; Admin assignable by super-admin only; owners locked) |
| `/websites` | Websites / domains (Deploy when `deploy_slug` set) |
| `/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 |
| `/my-products` | Customer stock listings (gated by `customer_products`) — list API |
| `/my-products/:id` | User product details |
| `/my-products/new` | Add user product (3 steps: basics, images, technical) — create API |
| `/my-products/:id/edit` | Edit user product — update API |
### 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 |
| `/finance` | Finance hub (Invoices + Transactions tiles) | Yes |
| `/invoices` | Business-issued invoices list (`?userId=` filter) | Yes |
| `/invoices/new` | Issue invoice to a user | Yes |
| `/invoices/:invoiceId/edit` | Edit invoice (locked when approved) | Yes |
| `/invoices/templates` | Invoice templates + item templates | Yes |
| `/invoices/templates/new` | Create invoice template | Yes |
| `/invoices/templates/:templateId` | Edit invoice template | Yes |
| `/customer-products` | Customer user-product listings (admin API) | Yes |
| `/customer-products/new` | Admin create user product (under admin name) | Yes |
| `/customer-products/:id` | Customer user-product details | Yes |
| `/customer-products/:id/edit` | Admin edit user product | 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``branding`, `dashboard`, `store`, `modules`
- `modules.enabled`: optional modules — business: `products`, `store`, `portfolio`, `blog`, `warehouse`, `videos`, `finance`; customer: `customer_products`. Missing key → all business modules enabled (legacy); customer modules stay opt-in. Super-admin only for modules PATCH.
- `modules.charts`: two home chart slots (`none`, `orders_30d`, `customers_joined_1y`, `blog_views_30d`, `products_added_1y`); `none` hides that slot; defaults orders + customers.
- Always-on (not in modules list): business — customers, website, profile, settings, home; customer — home, profile, addresses, orders, favorites
- Tenant public: `GET /tenants/:host` includes `enabledModules` + `homeCharts`
### 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
### User products (customer listings)
- Customer (own stock): `GET/POST /businesses/:businessId/my-user-products`, `GET/PATCH/DELETE .../:productId`, `POST .../:productId/promote`, categories + technical-form + media
- Business admin (all listings): `GET/POST /businesses/:businessId/user-products`, `GET/PATCH/DELETE .../:productId`, `PATCH .../:productId/status` (`draft` | `published` | `rejected` | `archived`), `POST .../:productId/promote`, categories + technical-form. Admin create is attributed to the admin user.
---
## 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 |
| `041_invoice_status_approved.sql` | Invoice status `approved` |
| `058_invoice_user_id.sql` | `invoices.user_id` billed user |
After schema changes: `npx prisma generate` and restart the backend.
### Invoices (super-admin + business)
Billed party is always a **User** (`user_id`). `business_id` is tenant/context. Platform invoices: `owner_scope=platform` (super-admin). Business-issued: `owner_scope=business` + `issuer_business_id`.
| Layer | Purpose |
|-------|---------|
| **Invoice item templates** | Reusable line items (title, duration, worktime, desc, price, discounted) |
| **Invoice templates** | Full blueprints: name, top text, items, key points, bank accounts |
| **Invoices** | Issued **to a user** — from template or blank |
| Endpoint | Purpose |
|----------|---------|
| `GET/POST /invoice-item-templates` | Platform line-item presets (super-admin) |
| `PATCH/DELETE /invoice-item-templates/:id` | Update/remove platform preset |
| `GET/POST /invoice-templates` | Platform full invoice templates |
| `GET/PATCH/DELETE /invoice-templates/:id` | Platform template CRUD |
| `GET/POST /businesses/:businessId/invoice-item-templates` | Business item templates |
| `PATCH/DELETE .../invoice-item-templates/:id` | Business item template CRUD |
| `GET/POST /businesses/:businessId/invoice-templates` | Business invoice templates |
| `GET/PATCH/DELETE .../invoice-templates/:id` | Business invoice template CRUD |
| `GET/POST /businesses/:businessId/invoices` | List / issue (`?userId=` filter; body `userId` required for business) |
| `GET/PUT/PATCH/DELETE .../invoices/:invoiceId` | Detail, content, status, delete |
| `GET /public/invoices/:publicId` | Public show (platform or business; issued/approved/paid) |
| `POST /public/invoices/:publicId/approve` | Public approve (`issued``approved`) |
Auth: platform template routes → super-admin. Business routes → `BusinessPermissionGuard` (`invoices.*` / `invoice_templates.*`); super-admin on invoice paths still sees **platform** invoices for that business.
**Invoice fields:** `userId` + nested `user`, optional `name`, `topText`, `notes`, `invoiceTemplateId`, `status`, `publicUrl`, nested `items`, `keyPoints`, `accounts`.
**Public invoice viewer:**
- Super-admin SPA `/invoices/:publicId` (`PublicInvoicePage`) — print-to-PDF; opaque 12-digit `publicId`
- Payload includes `locale` (`fa` | `en` from business `settings.branding.defaultLocale`) — page is RTL + Farsi chrome when `fa`
- Platform invoices: `https://{INVOICE_PUBLIC_DOMAIN}/invoices/{publicId}` (default `meshkee.com`)
- Business invoices: `https://{primaryBusinessDomain}/invoices/{publicId}` (e.g. `sanihome.ir`)
**Migrations:** `036_invoices.sql``041_invoice_status_approved.sql`, `058_invoice_user_id.sql`
**Super Admin UI:** `/settings` templates; `/businesses/:id/invoices` list/issue/edit; public `/invoices/:publicId`
**Business UI:** sidebar Finance group → `/finance` (hub), `/invoices`, `/transactions`; home Finance tile; Users row Receipt icon → `/invoices?userId=`; i18n en/fa via `useT`
---
## 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`
- **Per-business modules:** Super Admin **Modules** column on Businesses list; stored in `settings.modules.enabled` (business + customer sections); business Home + Sidebar hide disabled business modules; `customer_products` gates customer My Products (`/my-products`, `/my-products/new`) which call `businesses/:id/my-user-products`
- **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 dashboard SSL** (`POST /domains/ssl-sync` → dashboards agent). Storefront SSL: **Issue website SSL** (`POST /domains/website-ssl`) → websites agent `/ssl`. Per-row lock: **Ensure SSL** (`POST /domains/:id/issue-ssl`) probes apex + `business.*` + `customer.*`, issues only whats missing (websites agent for apex when `deploy_slug` set; blocking dashboard ssl-sync when business/customer need certs).
---
## 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)
- 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
- Customer dashboard “my invoices” view
---
## 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