Files
dashboards/docs/PROJECT_CONTEXT.md
T
Alireza HassaniandCursor a50685ec69 Gate Finance behind a super-admin business module.
Add finance to the modules picker and hide sidebar, home tile, and customer invoice actions when it is disabled.

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

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

cd MeshkeeApp
npm install

2. Backend (sibling folder)

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:

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 (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/settingsbranding, 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 (issuedapproved)

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
  • Platform invoices: https://{INVOICE_PUBLIC_DOMAIN}/invoices/{publicId} (default meshkee.com)
  • Business invoices: https://{primaryBusinessDomain}/invoices/{publicId} (e.g. sanihome.ir)

Migrations: 036_invoices.sql041_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.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 + 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

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)
  • 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