Files
dashboards/docs/PROJECT_CONTEXT.md
T
Alireza HassaniandCursor 0212148a73 Ensure per-row SSL for apex, business.*, and customer.* hosts.
Lock icon probes live TLS and issues only missing certs; toast z-index sits above modals; ssl-sync agent supports blocking wait.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 16:32:05 +03:30

24 KiB
Raw Blame History

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 7, 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

cd MeshkeeApp
npm install

2. Backend (sibling folder)

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:

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:

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.comsafeteb.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
/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

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/settingsbranding, dashboard, store, modules
  • modules.enabled: optional CMS modules (products, store, portfolio, blog, warehouse, videos); missing key → all enabled (legacy). 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): customers, website, profile, settings, home
  • 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

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 (issuedapproved), “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.sql040_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.tscreateId() 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:

--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 Home + Sidebar hide disabled modules
  • 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

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 TenantBrandingProviderapplyDocumentFavicon
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-uiLocaleContext
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