Files
dashboards/docs/PROJECT_CONTEXT.md
T
Alireza HassaniandCursor 9ce93b0b48 Align user-product links and chart copy with id+slug URLs.
Support pathSlug in types/website helpers and note that product views include customer listings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 15:35:46 +03:30

33 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: September 8, 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.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/Edit domain accepts optional Git repo URL → provisions storefront on websites VM and sets deploy_slug. Optional Update DNS records checkbox upserts tenant DNS (@ → ns.meshkee.com, www CNAME → apex, business/customer/api CNAMEs) via Arvan or Cloudflare (switch; Cloudflare is DNS-only / not proxied).
/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). Edit domain can optionally update DNS (Arvan or Cloudflare, same tenant records as Businesses). Overflow ⋯ includes website/dashboard links, Email DNS (Stalwart DKIM + MX/SPF on Arvan or Cloudflare; removes extra MX, dkim._domainkey, _dmarc), AI prompts, and remove. Parked domains button: alias hosts 301 to the main domain. Per alias, choose Arvan or Cloudflare for DNS (@ + www only, not proxied); SSL is always Let's Encrypt on the websites VM.
/templates Global AI prompt templates. Assign from Businesses or Websites ⋯ → AI prompts (add from library; public pack link).
/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 (online → bank redirect)
/checkout/result Bank return callback (status, orderId, payment meta)
/checkout/success Order confirmation (+ payment data when from gateway)
/checkout/failed Checkout / payment failure (+ payment data when present)
/ 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/static-images Named website image slots, grouped by page Yes
/website/special-items Special item carousels Yes
/website/settings Website settings (special products source; sitemap rebuild; site-wide Schema.org Organization/LocalBusiness; e-payment when store module on) Yes
/website/contact Contact us submissions list Yes
/website/subscriptions Subscriptions Placeholder
/website/faq FAQ Placeholder
/website/badges Badges Placeholder
/settings General settings E-payment: Mellat + ZarinPal credentials; stubs for SEP / Snapp Pay / DigiPay

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
Dashboard SSO POST /auth/handoff + POST /auth/handoff/consume Admin icon on customer dashboard; 60s one-time ticket

Auth helpers: apps/*/src/services/authService.ts (login, loginWithOtp, resetPassword, sendOtp, register, createHandoff). AuthContext exposes login + loginWithOtp. Business dashboard active tenant is resolved from the host domain (never businesses[0] alone). Customer header “open business dashboard” uses a one-time SSO ticket so staff do not log in again.


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
  • Translations (EN/AR, stored in entity_translations): GET .../products/:id/translations, POST .../translations/:locale/ai, PUT .../translations/:locale. Same three routes on categories. Dashboard category list includes translationCount. Bulk: POST .../categories/translations/ai-all and POST .../products/translations/ai-all (missing EN/AR only; skips existing). Business dashboard shows translation controls only when the multilanguage_data module is enabled for that business.

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, website
  • website.specialProductsSource: product | store_item. Unset → store_item if the store module is enabled, otherwise product.
  • website.schema: site-wide Schema.org settings (enabled, type Organization|LocalBusiness, optional name/description/priceRange/sameAs). Public GET /tenants/:host/website/business-info returns schema.jsonLd ready for <script type="application/ld+json">. When enabled, public product/blog detail responses also include schema.jsonLd (Product / BlogPosting).
  • Per-entity optional SEO overrides (seoMetaTitle, seoMetaDescription) on products, blogs, portfolios, videos, instructions, workshops, user-products, and categories (stored in metadata JSON).
  • Torob: store-module websites with Store settings → Torob on. Nginx on the shop apex proxies POST /torob_api/v3/products to POST /api/v1/tenants/{domain}/torob_api/v3/products. Otherwise 404. Do not implement this in Next.js.
  • 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, product_views_30d, website_views_30d, portfolio_views_30d); none hides that slot; defaults orders + customers. View charts read website_page_views (auto-counted on public list/detail GETs; ~6 month retention). website_views_30d aggregates all page kinds (kind=all); product chart counts product_detail + user_product_detail; portfolio/blog charts filter by detail kind. Home-only hits also increment on GET .../website/sliders and GET .../website/static-images?pageKey=home. Lifetime totals in website_page_view_counters.
  • Statistics (/statistics, always-on sidebar): same two home chart slots, then visit KPI rows (today / this week Sat–Fri / this month / lifetime total) for all pages, home, products (+ store items + user products), blog, portfolio, videos — module-gated except total + home. API: GET businesses/:id/analytics/views/summary.
  • Always-on (not in modules list): business — home, statistics, customers, website, profile, settings; customer — home, profile, addresses, orders, favorites
  • Tenant public: GET /tenants/:host includes enabledModules + homeCharts + specialProductsSource

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
  • GET /businesses/:businessId/store-items/export — Excel of every variant (id, names, variation, price, stock)
  • POST /businesses/:businessId/store-items/import — upload that Excel to bulk-update 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/static-images — named image slots (single or list) with pageKey + per-image titleFa / titleEn / subtext / linkUrl
  • POST /businesses/:businessId/website/static-images/sync — read slot keys from the live website (GET https://{domain}/meshkee/static-image-slots) and upsert them
  • GET /tenants/:host/website/static-images — public slots (?pageKey=home to filter by page)
  • GET /tenants/:host/website/static-images/:key — one public slot
  • 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
  • GET /businesses/:businessId/customers/export — Excel (.xlsx) of business customers (phone, first_name, last_name)
  • POST /businesses/:businessId/customers/import — Excel import for SMS lists; only phone required; new users get a random password hash (OTP login still works); existing users are linked; already-linked / staff rows skipped
  • POST /businesses/:businessId/customers/:userId/sms — single SMS via Gama SendQuick; body { message, templateId? }
  • POST /businesses/:businessId/customers/sms — bulk SMS via Gama SendBulk (or per-recipient when template/vars); body { message, userIds?, templateId? } — empty/omitted userIds → all enabled customers
  • Free-text SMS source: settings.sms.senderNumber (Super Admin only) or advertising shortcode 5000990009
  • Verified SMS template → service shortcode 5000110005 (fixed); unverified template → 5000990009
  • Per-business prepaid businesses.sms_balance (SMS credits) — all Meshkee SMS (OTP login/forgot-password, customer SMS, partner/public SMS for linked domains) costs 1 credit per 64 characters per recipient (ceil(len/64)). Insufficient balance blocks send.
  • Top-up (Super Admin): enter IRT amount → adds floor(IRT / 500) credits (e.g. 1,000,000 IRT → 2,000 credits)
  • New businesses created in Super Admin start with 1,000 SMS credits
  • businesses.sms_sent_count — lifetime messages billed (one per recipient), plus typed counters: mass / singular / auth / auto_customer / admin
  • External domains (no Meshkee website) still bill via their business row once the domain is linked
  • Business Settings → SMS settings: credit balance (read-only) + template CRUD
  • Super Admin business overflow → SMS settings modal: sender number + current credits + IRT top-up
  • Super Admin → SMS Templates (/sms-templates): verify / unverify
  • Online payment gateways live under Website → Settings (not main Settings)
  • Template vars: {{user.name}}, {{user.lastname}}, {{user.title}}

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:

--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); business/super-admin → IranYekan (iranyekan.css). --font-en / --font-fa are named faces only; --font-ui appends system fallbacks. Self-host files in public/fonts/ — see .cursor/rules/ui-farsi-fonts.mdc
  • Per-business theme: Super Admin sets branding.primaryColor and branding.themeMode (light | dark); business + customer dashboards apply both via their theme providers (data-theme on <html>). Dark mode uses neutral gray surfaces; --primary stays for accents only.
  • 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 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

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 what’s 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