A production-grade, multi-tenant marketplace built with Next.js 16. Vendors self-onboard and manage independent storefronts; customers browse, place orders, and track deliveries; admins govern the entire platform — all from a single codebase.
Think Shopify meets a regional marketplace — built ground-up on a modern React/Node stack.
┌─────────────────────────────────────────────────────────┐
│ Next.js 16 App Router │
├──────────────┬──────────────────────┬───────────────────┤
│ Customer │ Vendor Dashboard │ Admin Panel │
│ Storefront │ /store/* (SSR) │ /admin/* (SSR) │
│ /(public)/ │ TailAdmin UI │ TailAdmin UI │
├──────────────┴──────────────────────┴───────────────────┤
│ Auth.js v5 (JWT · Google OAuth · Credentials) │
│ Role enforcement via Edge Middleware │
├─────────────────────────────────────────────────────────┤
│ Prisma 7 + PostgreSQL (Docker / VPS) │
├─────────────────────────────────────────────────────────┤
│ Cloudinary CDN · Redux Toolkit (client) │
└─────────────────────────────────────────────────────────┘
Three fully isolated role zones share one PostgreSQL schema and one deployment. Server Components handle all data fetching; Server Actions handle all mutations — zero REST endpoints in the admin/vendor layer.
| Concern | Choice | Notes |
|---|---|---|
| Framework | Next.js 16.2.9 (Turbopack) | App Router, RSC, Server Actions |
| Language | React 19.2, Node 24, full ESM | "type": "module" throughout |
| Styling | Tailwind CSS v4 | JIT, zero config |
| Back-office UI | TailAdmin (Next.js) | Components copied-in, not a runtime dependency |
| Auth | Auth.js v5 (next-auth@beta) |
Google OAuth + Credentials; JWT strategy; Prisma adapter |
| ORM | Prisma 7 + @prisma/adapter-pg |
Driver-adapter pattern; generated client in prisma/generated/ |
| Database | PostgreSQL | Docker locally; any VPS Postgres in production |
| Image CDN | Cloudinary | Vendor product images; auth-gated upload endpoint |
| State (client) | Redux Toolkit | Cart, address, ratings — client-only slices |
| Testing | Vitest | 120 tests across 15 suites; unit + integration |
- Product catalog with category filtering, search, and infinite scroll
- Product detail pages with image gallery and computed star ratings
- Persistent cart (Redux + DB sync — hydrates on login)
- Checkout with saved delivery addresses and coupon validation
- COD order placement and live order status tracking
- Individual vendor store profile pages
- Self-serve store creation and onboarding flow
- Product management — create, edit, delete, toggle stock availability
- Up to 4 Cloudinary-hosted product images per listing
- Order management with status progression (Placed → Processing → Shipped → Delivered)
- Revenue dashboard: earnings summary, product counts, recent customer ratings
- Platform KPIs: order volume, GMV, user counts, pending approvals
- Store approval workflow — approve or reject vendor applications
- Store activation toggle — suspend a live store instantly
- User directory and role inspection
- Coupon engine — percentage-off codes with expiry, audience targeting (new user / member / public)
- Cross-platform order management and status overrides
- Role-based access control enforced at the Edge (Next.js middleware) and server (
lib/auth.jshelpers) - All vendor data strictly scoped to session —
storeIdis always derived server-side, never accepted from the client - Shared Prisma singleton — no
new PrismaClient()in route handlers - Cloudinary upload endpoint auth-gated to
vendorandadminroles - Coupon validation enforces isPublic / forMember / forNewUser flags at both validate and order-placement endpoints
User ──< Order (as buyer)
User ── Store (1:1, vendor)
Store ──< Product
Store ──< Order
Order ──< OrderItem >── Product
Product ──< Rating
User ──< Rating
User ──< Address
Order >── Address
Coupon (standalone — validated at checkout)
Auth.js: Account, Session, VerificationToken
Selected schema decisions:
User.role(customer|vendor|admin) — single source of truth for RBAC, checked server-side on every protected routeStore.status(pending|approved|rejected) +Store.isActive— two-gate model separating onboarding approval from runtime suspensionOrder.coupon Json— coupon snapshot embedded at order time, immune to future edits or deletionsUser.cart Json— server-side cart fallback alongside Redux client state
app/
(public)/ customer storefront — home, shop, product, cart, orders, create-store
admin/ admin panel — dashboard, stores, approve, coupons, orders, users
actions.js all admin Server Actions (requireAdmin guard)
store/ vendor dashboard — dashboard, add/edit/manage products, orders
actions.js all vendor Server Actions (requireVendor + ownership check)
api/
auth/ Auth.js handler + registration endpoint
upload/ Cloudinary image upload (auth-gated)
public/ customer-facing REST: products, categories, stores, coupons/validate
customer/ authenticated REST: cart, addresses, orders, ratings, store
components/
admin/ TailAdmin-based admin components
ui/ shared primitives: StatCard, DataTable, Badge, PageHeader
store/ vendor components (reuses admin/ui/ primitives)
(public)/ storefront: Navbar, Footer, Hero, ProductCard, Banner
lib/
prisma.js shared Prisma client singleton
auth.js requireAdmin(), requireVendor(), getAuthUser() helpers
cloudinary.js Cloudinary SDK wrapper
syncCart.js fire-and-forget Redux ↔ DB cart sync
features/ Redux slices: cartSlice, productSlice, addressSlice, ratingSlice
store.js Redux store
prisma/
schema.prisma canonical DB schema
generated/ Prisma 7 generated client — do not edit
migrations/ SQL migration files
__tests__/ Vitest test suites (120 tests, 15 files)
docs/ PRD, technical specs, implementation plans
- Node 24 (
nvm use 24) - Docker (for local PostgreSQL)
- Cloudinary account (free tier sufficient)
- Google Cloud project with OAuth 2.0 credentials
git clone https://github.com/jsoftsol/Dastiyab.git
cd Dastiyab
npm installdocker compose up -dCopy .env.example to .env.local and populate:
DATABASE_URL=postgresql://user:password@localhost:5432/dastiyab
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=<32-char random string — openssl rand -base64 32>
GOOGLE_CLIENT_ID=<from Google Cloud Console>
GOOGLE_CLIENT_SECRET=<from Google Cloud Console>
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=...
CLOUDINARY_API_KEY=...
CLOUDINARY_API_SECRET=...npx prisma db push
npx prisma generatenpm run dev # Turbopack dev server → http://localhost:3000Sign in once with your email, then run:
UPDATE "User" SET role = 'admin' WHERE email = 'you@example.com';Sign out and back in — you'll be routed to /admin.
- Go to
/sign-in— register with email/password or sign in with Google. - Browse products at
/shop— filter by category, search by name. - Open a product to view details, images, and ratings; add it to your cart.
- Go to
/cartto review items, then proceed to checkout. - At checkout: add a delivery address, optionally apply a coupon code, and place a COD order.
- Track your orders at
/orders; once an order is marked Delivered you can leave a star rating.
Create a store
- Sign in (any account — customer role is the default).
- Go to
/create-storeand fill out the store form (name, description, etc.). - Submit — the platform creates your store, upgrades your role to
vendor, and signs you out automatically. - Sign back in; you'll now have access to the vendor dashboard at
/store.
Manage your store
| Page | Path | What you can do |
|---|---|---|
| Dashboard | /store |
Revenue summary, product counts, recent ratings |
| Add product | /store/add-product |
Create a listing with up to 4 Cloudinary images |
| Manage products | /store/manage-product |
Edit, delete, toggle in-stock |
| Orders | /store/orders |
View orders and update status (Placed → Processing → Shipped → Delivered) |
Note: Your store must be approved by an admin before products are visible to customers on the storefront.
Become an admin
- Sign in once to create your user record.
- Connect to the database and run:
UPDATE "User" SET role = 'admin' WHERE email = 'you@example.com';With the local Docker setup:
docker exec -it gocart_db psql -U postgres -d gocart \
-c "UPDATE \"User\" SET role = 'admin' WHERE email = 'you@example.com';"- Sign out and sign back in — the JWT is refreshed with the new role.
- Go to
/admin.
Admin capabilities
| Page | Path | What you can do |
|---|---|---|
| Dashboard | /admin |
Platform KPIs — orders, GMV, user count, pending approvals |
| Stores | /admin/stores |
Toggle any store active / inactive |
| Approve | /admin/approve |
Approve or reject pending vendor applications |
| Coupons | /admin/coupons |
Create and delete coupon codes (percentage-off, expiry, audience targeting) |
| Orders | /admin/orders |
View all orders across all stores; override status |
| Users | /admin/users |
View all user accounts and their roles |
npm test # run all 120 tests
npm run test:watch # watch mode| Suite | Tests | What is covered |
|---|---|---|
| smoke | 2 | env sanity |
| lib/auth | 8 | requireAdmin, requireVendor, getAuthUser |
| lib/cloudinary | 3 | upload helper |
| lib/prisma | 2 | singleton behaviour |
| api/register | 4 | registration endpoint, validation, duplicate email |
| api/upload | 3 | Cloudinary endpoint, auth guards |
| admin/actions | 12 | 5 admin Server Actions — auth, enum validation, DB calls |
| store/actions | 15 | 5 vendor Server Actions — auth, ownership enforcement |
| api/public/products | 8 | list with filters/pagination, detail with ratings |
| api/public/categories | 3 | distinct category list |
| api/public/stores | 6 | store lookup, store creation, vendor role update |
| api/public/coupons | 8 | validate endpoint — expiry, audience flags, auth-aware |
| api/customer/cart | 4 | GET (unauthed returns empty), PUT validation |
| api/customer/addresses | 8 | CRUD, ownership check on DELETE |
| api/customer/orders | 7 | multi-store transaction, address ownership, server-side prices |
| api/customer/ratings | 7 | DELIVERED check, duplicate prevention |
The project ships as a Docker multi-stage image behind a GitHub Actions workflow.
# Production stack (app + postgres + migrate)
docker compose -f docker-compose.prod.yml up -dThe Dockerfile uses three stages: deps (install), builder (Next.js build + prisma generate), runner (minimal production image). Next.js standalone output is used for a lean container.
Push to the deploy branch triggers .github/workflows/deploy.yml:
- rsync project files to VPS
- SSH: write
.env.production, start postgres, runprisma migrate deploy, start app with--build - Prune old Docker images
Required GitHub secrets: SERVER_HOST, SERVER_USER, SERVER_SSH_KEY, SERVER_APP_DIR, PRODUCTION_ENV
VPS pre-deploy checklist:
- Docker + Docker Compose v2 installed
- SSH key in
~/.ssh/authorized_keys $SERVER_APP_DIR/release/directory exists- All 5 secrets configured in GitHub repository settings
| Phase | Scope | Status |
|---|---|---|
| 0 | Auth migration — Clerk → Auth.js v5 + Prisma adapter | Complete |
| 1 | Foundation — PostgreSQL, Prisma 7, Cloudinary, middleware | Complete |
| 2 | Admin Panel — TailAdmin UI + 6 pages + Server Actions | Complete |
| 3 | Vendor Dashboard — 5 pages + Cloudinary upload + Server Actions | Complete |
| 4 | Public Storefront — wire existing pages to real Prisma data | Complete |
| 5 | Platform Services — coupon engine, product ratings | Complete |
| — | Deployment — Docker, docker-compose.prod.yml, GitHub Actions CI/CD | Complete |
Server Actions over REST for admin/vendor mutations.
Keeps auth checks co-located with the mutation, eliminates a client/server serialization layer, and lets revalidatePath handle cache invalidation without a separate fetch. The pattern is consistent across both role zones: Server Component fetches → Server Action mutates → path revalidated.
Auth.js v5 over a managed auth service. Full ownership of the token shape and session callbacks, no third-party dependency, and clean self-hosting. JWT strategy avoids a session-table lookup on every request. Credentials + Google in a single config keeps onboarding flexible without managing two separate auth systems.
Prisma 7 driver adapter (@prisma/adapter-pg).
The Prisma 7 mandated pattern for direct PostgreSQL connections. The generated client lives at prisma/generated/prisma/ — outside node_modules — making artefacts explicit, auditable, and version-controlled.
PostgreSQL over a managed database service. Direct connection keeps the local dev loop fast and removes a managed-DB dependency. The same schema deploys unchanged to any VPS Postgres instance, or migrates to RDS/Supabase with a connection string swap.
COD-only payments for now.
Stripe is not available in all target markets. The schema carries a PaymentMethod enum (COD | STRIPE) so the integration can be added in a future phase without a breaking migration.
TailAdmin components copied-in, not installed as a package.
Allows targeted per-component modifications without fighting an upstream update cycle. Both the admin and vendor zones share a common primitive layer (components/admin/ui/) so changes propagate to both surfaces.
Pull requests are welcome. Please open an issue first to discuss substantial changes. See CONTRIBUTING.md for guidelines.
MIT — see LICENSE.md.