۱۲ — Cursor Seed Prompts
Stack: Next.js 16 full-stack (App Router + API Routes) + Prisma + PostgreSQL + Redis
بدون FastAPI — یک codebase، یک زبان (TypeScript)
ترتیب پیشنهادی
- Prompt 1 — scaffold
- Prompt 2 — Prisma schema + migrations
- Prompt 3 — auth (OTP + JWT)
- Prompt 4 — client home UI
- Prompt 5 — order flow
- Prompt 6 — vendor panel
- Prompt 7 — admin panel
- Prompt 8 — payment (Zarinpal)
- Prompt 9 — SMS (Panelchi)
- Prompt 10 — discount codes
- Prompt 11 — cron jobs
Prompt 1 — Scaffold
Create a Next.js 16 App Router project called "TakeEat" with the following setup:
Stack:
- TypeScript
- Tailwind CSS v4 (with @theme in globals.css — NO tailwind.config.js)
- shadcn/ui (init with custom theme tokens)
- Framer Motion
- Lucide icons
- TanStack Query
- React Hook Form + Zod
- Prisma v5 + @prisma/client
- ioredis (Redis client)
- next-pwa (PWA manifest)
Project structure (route groups):
src/app/(client)/ — مشتری
src/app/(vendor)/ — وندور
src/app/(admin)/ — ادمین
src/app/api/ — API Routes
src/lib/ — prisma.ts, redis.ts, auth.ts, sms.ts, payment/
src/components/ — shared UI components
src/middleware.ts — role-based guard
Root layout: dir="rtl", lang="fa", load Vazirmatn + Estedad via next/font/google.
globals.css @theme:
--color-clay: #EFEAE1
--color-surface: #FBF9F5
--color-ink: #21241F
--color-ink-muted: #6E6A60
--color-saffron: #B5651D
--color-firouzeh: #1F5C56
--color-plum: #4A2740
--font-body: "Vazirmatn", sans-serif
--font-display: "Estedad", sans-serif
docker-compose.yml: postgres:15 + redis:7-alpine
.env.local.example: all vars from doc/17-environment-and-secrets.md
Read doc/07-tech-stack.md for full details.Prompt 2 — Prisma Schema
Create prisma/schema.prisma for TakeEat.
Copy the full schema from doc/08-data-model.md (the Prisma schema block).
Also create:
- prisma/seed.ts with a Beta package and superadmin user
- src/lib/prisma.ts as Prisma singleton (PrismaClient with global caching)
Run: npx prisma generatePrompt 3 — Auth (OTP + JWT)
Implement OTP auth for TakeEat (Next.js API Routes):
Files to create:
- src/lib/otp.ts — generateOtp, hashOtp, storeOtp, verifyOtp (Redis TTL 120s)
- src/lib/auth.ts — signJwt, verifyJwt (httpOnly cookie: db_access, db_refresh)
- src/lib/sms.ts — sendSms() via Panelchi (pattern for OTP, simple for others); Kavenegar legacy fallback; SMS_DEV_MODE
- src/lib/panelchi.ts — panelchiSendSimple, panelchiSendPattern
- src/lib/otpSms.ts — buildOtpSmsBody() with WebOTP domain line
- src/app/api/auth/otp/send/route.ts — rate limit 3/hour/phone via Redis
- src/app/api/auth/otp/verify/route.ts — verify OTP → JWT cookie
- src/app/api/auth/refresh/route.ts
- src/app/api/auth/logout/route.ts
- src/middleware.ts — guard (client)/(vendor)/(admin) routes by JWT role
JWT payload: { sub: userId|vendorStaffId|adminId, role: "client"|"vendor"|"admin" }
TTL: access 15min, refresh 30 days.
Read doc/09-api-endpoints.md §Auth and doc/10-sms-notifications.md §OTP rules.Prompt 4 — Client Home UI
Build the client home page for TakeEat.
Design spec: doc/03-ui-design-direction.md
Reference mockup: doc/mockups/client-home.html
Pages/components:
- src/app/(client)/page.tsx — fetches GET /api/boxes, renders list
- src/components/SurpriseBoxCard.tsx:
• image 16:9 top (radius 18px top)
• NO discount % badge — only salePrice (Saffron) + strikethrough originalValueEstimate
• Tag "آخرین باکس" (Plum bg) only if quantityRemaining <= 1
• card press: scale(0.98)
- src/components/BottomNav.tsx — 3 items: home (active=Saffron), search, profile
- src/components/MohrSeal.tsx — SVG seal (circle + inner dot, Saffron stroke)
- Header: seal logo + "باکسهای نزدیک شما"
Empty state: "هنوز باکسی ایندور و بر نیست. فردا همین ساعت دوباره سر بزن"
RTL: all utilities ps-/pe-/ms-/me- (Tailwind logical properties)
Use mock data if API not ready.Prompt 5 — Order Flow
Implement order creation and pickup for TakeEat.
API Routes (per doc/09-api-endpoints.md):
- POST /api/orders — create order, decrement inventory in Prisma transaction
- POST /api/orders/[id]/pay — Zarinpal stub → returns mock paymentUrl
- GET /api/orders/[id] — detail with pickupCode
- POST /api/orders/[id]/cancel
- POST /api/orders/confirm-pickup (vendor)
State machine per doc/06-order-lifecycle.md:
pending → confirmed → picked_up
↓
cancelled / no_show
Inventory: use prisma.$transaction with quantityRemaining decrement + check > 0.
Pickup code: 6 uppercase alphanumeric, unique.
Pages:
- src/app/(client)/order/[id]/page.tsx — PickupTicket component (Mohr seal, code)
- src/components/PickupTicket.tsx — fullscreen, seal with code inside, address, windowPrompt 6 — Vendor Panel
Build vendor panel for TakeEat.
Route group: src/app/(vendor)/
Auth: JWT role=vendor required (middleware)
Pages:
1. (vendor)/register/page.tsx — registration form (name, category, address, phone)
2. (vendor)/dashboard/page.tsx — today's stats + quick link to box/orders
3. (vendor)/box/page.tsx — create/publish today's box (one per day limit)
4. (vendor)/orders/page.tsx — today's orders list + confirm pickup by code input
API Routes:
- POST /api/vendor/register
- GET /api/vendor/me
- POST /api/vendor/boxes
- POST /api/vendor/boxes/[id]/publish
- GET /api/vendor/orders?date=today
- POST /api/vendor/orders/confirm-pickup { pickupCode }
- POST /api/vendor/orders/[id]/mark-paid
Use same design tokens. Keep UI simple (table/list + form).
Read doc/02-mvp-requirements.md §vendor flow.Prompt 7 — Admin Panel
Build admin panel for TakeEat.
Route group: src/app/(admin)/
Auth: JWT role=admin required. AdminUser logs in with email+password (not OTP).
Pages:
1. (admin)/vendors/page.tsx — list pending vendors, approve/reject
2. (admin)/packages/page.tsx — CRUD packages
3. (admin)/discounts/page.tsx — CRUD discount codes
4. (admin)/orders/page.tsx — list all orders with filters
API Routes:
- POST /api/admin/vendors/[id]/approve
- POST /api/admin/vendors/[id]/reject
- CRUD /api/admin/packages
- CRUD /api/admin/discount-codes
- GET /api/admin/orders
Discount code validation logic: doc/15-discount-rules.md
Use shadcn Table + Form components.Prompt 8 — Zarinpal Payment
Implement Zarinpal payment for TakeEat.
Files:
- src/lib/payment/index.ts — PaymentProvider interface { createPayment, verifyCallback }
- src/lib/payment/zarinpal.ts — ZarinpalProvider implementation
- src/app/api/orders/[id]/pay/route.ts — POST → { paymentUrl }
- src/app/api/payments/zarinpal/callback/route.ts — GET callback from Zarinpal
Flow:
1. createPayment(orderId, amount, callbackUrl) → { authority, paymentUrl }
2. User pays → Zarinpal GET callback with ?Authority=&Status=OK
3. verifyCallback(authority) → { refId }
4. order.paymentStatus = paid, order.orderStatus = awaiting_vendor
5. SMS to vendor (new_order_vendor)
6. redirect → /order/[orderId] (awaiting vendor confirm)
Env: ZARINPAL_MERCHANT_ID, ZARINPAL_SANDBOX=true
Read doc/02-mvp-requirements.md §payment.Prompt 9 — SMS + Cron
Implement SMS notifications and cron jobs for TakeEat.
SMS (src/lib/sms.ts):
- sendSms({ to, template, vars }) — Panelchi primary; Kavenegar legacy if PANELCHI_* empty
- OTP: PANELCHI_OTP_PATTERN → pattern send; else simple
- SMS_DEV_MODE=true → console.log only
- Log each send in Notification table (Prisma)
Cron (src/lib/cron.ts + src/app/api/cron/tick/route.ts):
- markNoShows(): orders past pickup_end in confirmed/ready → no_show
- sendPickupReminders(): 25-35 min before pickup_end → SMS
- expireBoxes(): active boxes past pickup_end → expired
- Protect /api/cron/tick with x-cron-secret header
Start cron in src/app/layout.tsx server-side (or use Vercel Cron if hosting there).
Read doc/10-sms-notifications.md for full templates and OTP rules.Prompt 10 — Discount Codes
Implement discount code validation for TakeEat.
Logic (doc/15-discount-rules.md):
1. isActive + date range
2. usageCount < usageLimitTotal (if set)
3. per-user limit (count Orders with this discountCodeId for this user)
4. minOrderAmount
5. if vendor scope → must match order's vendor
6. compute: percent → min(subtotal * rate, maxDiscountAmount); fixed → min(value, subtotal)
API Routes:
- POST /api/orders/[id]/apply-discount { code } → { discountAmount, totalPrice }
- Validate same rules in POST /api/orders (if code provided in body)
Prisma: atomic usageCount increment when order confirmed (in transaction).Prompt 11 — PWA + Polish
Make TakeEat client PWA-ready and polish for demo.
PWA:
- public/manifest.json (name: TakeEat, theme_color: #B5651D, background_color: #EFEAE1)
- src/app/layout.tsx: <link rel="manifest"> + meta theme-color
- Service worker with next-pwa (offline fallback)
Polish:
- MohrSeal animation (Framer Motion): on order confirmed, scale + ease 250ms "stamp" effect
- prefers-reduced-motion: skip animation
- Loading skeleton for SurpriseBoxCard list
- Error boundary for API failures with EmptyState fallback
Quality checklist (doc/03-ui-design-direction.md §8):
- RTL all logical properties (ps-/pe-/ms-/me-)
- WCAG contrast Ink/Clay + Saffron/Surface
- Focus visible on all interactive elements
- Test with real Persian text (not Lorem Ipsum)Context files — همیشه @mention کنید
@doc/02-mvp-requirements.md
@doc/03-ui-design-direction.md
@doc/06-order-lifecycle.md
@doc/08-data-model.md
@doc/09-api-endpoints.mdبرای UI:
@doc/mockups/client-home.html