# 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 1, 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 | | `/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 | | `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 app’s `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 app’s `--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.` (e.g. `business.sanihome.ir`) | | Customer | `https://customer.` | | 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