Files
meezi/.cursorrules
soroush.asadi a85890f30a chore: Flutter mobile app, CI, and dev tooling
- mobile/: Flutter/Dart merchant mobile app skeleton
- .github/: GitHub Actions CI workflows
- .dockerignore: exclude host node_modules from build context
- .cursorrules: Cursor IDE project rules
- .claude/: Claude Code project settings and launch config

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-27 21:35:27 +03:30

159 lines
6.8 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
You are building Meezi (میزی) — a Persian-first SaaS POS and community
platform for Iranian cafés in Tehran and Karaj.
Always read MEEZI_PRD.md at the start of any new session for full context.
## Product
Brand: Meezi (میزی) | Tagline: میزت منتظرته
Competitor: Sepidz (سپیدز) — legacy license, no SaaS, no customer app
Markets V1: Tehran (تهران) + Karaj (کرج)
Languages: Farsi fa (default) + Arabic ar + English en
Pricing: Free / Pro 1.49M ت / Business 3.49M ت / Enterprise custom
Hardware: Android tablet + thermal printer bundle
## Stack
Backend: ASP.NET Core 10 C# — src/Meezi.API
Web: Next.js 14 TypeScript — web/dashboard
Mobile: Flutter 3 Dart — mobile/meezi_app
DB: PostgreSQL 16 + Redis
ORM: EF Core 10 (Npgsql)
Queue: Hangfire
Realtime: SignalR (KDS live orders)
SMS: Kavenegar API
Payment: ZarinPal
Maps: Neshan API
Tax: Taraz API (سامانه مودیان)
Delivery: Snappfood webhook
Hosting: Arvan Cloud Iran
## C# / ASP.NET Core Rules
- Async/await everywhere — NEVER .Result or .Wait()
- EF Core 10 only — no raw SQL unless aggregation requires it
- EVERY query: .Where(x => x.CafeId == _tenant.CafeId) — multi-tenant
- Return ApiResponse<T> always:
record ApiResponse<T>(bool Success, T? Data, ApiError? Error = null)
record ApiError(string Code, string Message, string? Field = null)
- Use record types for all DTOs
- FluentValidation for ALL request models
- ILogger<T> for logging — never Console.WriteLine
- Hangfire for all background jobs (SMS, coupons, renewal reminders)
- SignalR hub /hubs/kds for real-time kitchen display
- Program.cs minimal hosting style
## Next.js / TypeScript Rules
- next-intl for ALL i18n — zero hardcoded strings in components
- ALL user text in messages/fa.json + messages/ar.json + messages/en.json
- Dynamic direction: fa/ar → dir="rtl" | en → dir="ltr"
- Spacing: ms-* me-* ps-* pe-* ALWAYS — never ml-* mr-* pl-* pr-*
- TanStack Query v5 for ALL server state
- Zustand for cart + UI-only state
- Dates: date-fns-jalali ALWAYS — never display Gregorian to user
- Numbers fa: n.toLocaleString('fa-IR')
- Currency: n.toLocaleString('fa-IR') + ' ت'
- shadcn/ui components — don't rebuild what shadcn provides
- TypeScript strict — no `any`, no `as unknown`
## Flutter / Dart Rules
- Riverpod 2.x for ALL state — no setState in business logic
- GoRouter for all navigation
- Drift SQLite for offline storage (lib/core/db/)
- Sync pattern: write to Drift first → queue → upload on reconnect
- shamsi_date package for ALL date display — never show Gregorian
- 3 locales: fa (RTL), ar (RTL), en (LTR)
- Feature-first folders: lib/features/{feature}/
- Thermal printer: bluetooth_print or esc_pos_utils_plus
- QR scanner: mobile_scanner
- Dio + Retrofit for API calls
- freezed for immutable models
## Multi-Tenancy (CRITICAL)
- JWT claims: { userId, cafeId, role, planTier, lang }
- TenantMiddleware injects ITenantContext into every request
- Every EF query filters by CafeId — no exceptions
- PlanLimitMiddleware checks limits before: orders, customers, SMS
- On limit hit return: { code: "PLAN_LIMIT_REACHED", message: "..." }
## Plan Limits to enforce
Free: 50 orders/day, 1 terminal, 50 CRM, 0 SMS, 1 branch
Pro: unlimited orders, 3 terminals, unlimited CRM, 50 SMS, 1 branch
Business: unlimited everything, 200 SMS, 5 branches + HR + delivery
Enterprise: unlimited + badges + white_label + API
## API Format
GET list: { success: true, data: [...], meta: { total, page, pageSize } }
GET single: { success: true, data: { ... } }
POST/PATCH: { success: true, data: { id, ... } }
Error: { success: false, error: { code: "...", message: "..." } }
## Endpoint Pattern
/api/cafes/{cafeId}/orders → protected, validate cafeId == JWT cafeId
/api/public/discover → no auth
/api/q/{qrCode} → no auth, returns cafeSlug + tableId
/api/webhooks/snappfood → no JWT, verify HMAC secret
/api/auth/send-otp → no auth, rate limit 5/hour/phone
/api/billing/verify → ZarinPal callback
## Security
- Validate cafeId ownership: if (order.CafeId != _tenant.CafeId) return 403
- OTP rate limit: Redis INCR "otp:attempts:{phone}" with 1h TTL, block at 5
- Never log phone, nationalId, or payment tokens
- Soft delete: DeletedAt DateTime? — never hard DELETE customer data
- File upload: validate MIME + max 5MB
## i18n String Keys Convention
fa.json:
{
"common": { "save":"ذخیره", "cancel":"انصراف", "confirm":"تأیید",
"delete":"حذف", "search":"جستجو", "loading":"در حال بارگذاری..." },
"pos": { "order":"سفارش", "table":"میز", "total":"مبلغ نهایی",
"confirmOrder":"ثبت و پرداخت", "applyСoupon":"اعمال کوپن" },
"crm": { "customer":"مشتری", "nationalId":"کد ملی", "phone":"موبایل" },
"hr": { "employee":"کارمند", "shift":"شیفت", "salary":"حقوق",
"clockIn":"ورود", "clockOut":"خروج", "leave":"مرخصی" },
"errors": { "planLimit":"به سقف پلن رسیده‌اید. برای ادامه ارتقا دهید",
"notFound":"یافت نشد", "unauthorized":"دسترسی ندارید" }
}
UI QUALITY RULES — apply to every screen:
Visual hierarchy: 3 levels always
Level 1: page title + primary action button (largest, highest contrast)
Level 2: section headers + card titles (medium, color-coded)
Level 3: metadata, secondary info (small, muted)
Cards: always border-radius-lg (12px), 0.5px border, white background
Never flat boxes without border — everything lives in a card
Color system:
Primary action: #0F6E56 (Meezi green)
Positive/money: #0F6E56 green
Warning/promo: #BA7517 amber
Destructive: #A32D2D red
Info: #0C447C blue
Backgrounds: tertiary (page) → secondary (section) → primary (card)
Typography:
Page titles: 18px weight 500
Section labels: 11px UPPERCASE letter-spacing .06em muted
Body text: 13px regular
Prices/amounts: 13-14px weight 500 green
Metadata: 11px muted
Status indicators:
All orders/statuses have colored dot + badge — never plain text
Badges: colored background matching meaning (green=active, amber=pending)
Every list row: icon or emoji + name + metadata + right-side value + action
Never a plain text list — always structured rows with visual anchors
Interactive states:
Hover: border-color changes to primary (#0F6E56)
Active: scale(0.98) transform
Selected: green background tint #E1F5EE
Section headers above every group of items:
"پیشنهاد ویژه امروز" / "همه آیتم‌ها" / "پرفروش‌ترین"
Small uppercase label + optional "مشاهده همه" link
Promo tags on items with active discount:
Small amber badge top-right of item card showing "۱۵٪ تخفیف"