A full-stack platform for managing tour guide operations in Bangladesh, built with Next.js 16 (Beta), MongoDB, and NextAuth v5. It provides a public landing page for travelers and a role-based dashboard for Guides and Assistants to manage tours, bookings, reviews, employees, payments, and real-time communication.
- Tech Stack
- System Architecture
- User Roles
- Features
- Project Structure
- API Usage
- Getting Started
- Configuration
- Data Models
- State Management
- Glossary
Core Framework & Runtime
| Technology | Version | Role |
|---|---|---|
| Next.js | ^16.0.0-beta.0 |
App Router framework with Turbopack, hosts both pages and API route handlers |
| React | ^19.2.3 |
UI library |
| TypeScript | ^5 |
Static typing across the stack |
Data & Infrastructure
- Database: MongoDB via Mongoose (
^8.19.1) - Caching & Rate Limiting: Upstash Redis (
^1.36.0) - Authentication: NextAuth.js v5 (Credentials + Google OAuth)
- Media Pipeline: Cloudinary
- State Management: Zustand (
^5.0.8) - Payments: Stripe
UI & UX Engine
- Styling: Tailwind CSS 4.0
- Components: Radix UI primitives (shadcn/ui style)
- Animations: Framer Motion
- Visualization: Recharts, React Leaflet
- Real-time: Socket.IO
graph TB
subgraph "Client Layer"
UI["React 19 Components"]
STORE["Zustand Stores"]
end
subgraph "Application Layer"
ROUTES["src/app (Pages)"]
API["src/app/api (Route Handlers)"]
end
subgraph "Middleware"
PROXY["src/proxy.ts"]
end
subgraph "Service Layer"
HANDLERS["src/lib/handler"]
BUILDERS["src/lib/build-responses"]
end
subgraph "Data Layer"
MONGO[("MongoDB")]
REDIS[("Upstash Redis")]
CLOUDINARY[("Cloudinary")]
end
UI --> STORE
UI --> ROUTES
ROUTES --> API
PROXY --> API
API --> HANDLERS
HANDLERS --> BUILDERS
HANDLERS --> MONGO
HANDLERS --> REDIS
HANDLERS --> CLOUDINARY
Every API route is wrapped with a shared withErrorHandler higher-order function that standardizes success/error JSON shapes, and src/proxy.ts applies rate limiting and session/role gating before requests reach the route handlers.
| Role | Identifier | Permissions |
|---|---|---|
| Guide | USER_ROLE.GUIDE |
Full ownership: tours, employees, payment accounts, financial analytics |
| Assistant | USER_ROLE.ASSISTANT |
Operational support: review moderation, report triage, logistics updates |
| Admin | USER_ROLE.ADMIN |
Platform-level moderation (tour approval, notifications) |
| Traveler | Public user | Browse tours, leave reviews, chat with support |
Login is gated at the credential-validation stage: Guides must have an APPROVED GuideModel status, and Assistants must have a non-deleted EmployeeModel record.
π Landing Page
- Server-rendered hero, advantages, "How it Works", testimonials, and CTA sections
- Live stats hydrated from MongoDB
- Sticky navbar with conditional Login/Dashboard rendering
π Authentication & Authorization
- NextAuth v5 (Credentials + Google OAuth), JWT sessions (30-day max age, hourly refresh)
- Email verification via hashed tokens
- Rate-limited credential validation (10/min per IP, 5/min per email) via Upstash Redis
- Role-based UI + API gating
π Dashboard
- KPI grid (tours, bookings, revenue, reports, ratings, staff)
- Charts for bookings/reviews via Recharts
- Per-tab date-range filtering, cursor-paginated transactions, CSV export
- TTL-based client-side caching (
useDashboardStore)
πΊοΈ Tour Management
- Lifecycle:
DRAFT β SUBMITTED β APPROVED β ACTIVE β COMPLETED/ARCHIVED/TERMINATED - Multi-step wizard with modular PATCH endpoints per schema section
- Bangladesh-specific geography and itinerary modeling
- Moderation workflow (archive/terminate/re-approval)
β Review & π© Report Management
- Threaded replies with approval gating
- Report triage with priority and bulk-resolve actions
π Employee Management & π³ Payments
- Payroll, shifts, soft-deletion, Stripe payment account linkage
π¬ Real-time Communication
- Socket.IO presence tracking and system notifications with priority-based TTL
Root Structure
bd-travel-spirit-guide-system/
βββ src/
β βββ app/ # Routes + API handlers
β βββ components/ # React components
β βββ config/ # Stripe/DB config
β βββ constants/ # Enums
β βββ lib/ # Business logic, builders, handlers, helpers
β βββ models/ # Mongoose schemas
β βββ socket/ # Socket.IO helpers
β βββ store/ # Zustand stores
β βββ types/ # Shared types/DTOs
β βββ utils/ # API clients, validators
β βββ proxy.ts # Middleware (rate-limit + auth guard)
βββ public/
βββ package.json
src/app/api β API Route Handlers
src/app/api/
βββ auth/
β βββ [...nextauth]/ # NextAuth handler (signIn/signOut/session)
β βββ token/v1/ # Token-based flows
β βββ user/v1/
β βββ validate/ # Pre-NextAuth credential validation
β βββ password/ # Password change/reset
β βββ audits/ # Audit log queries
β βββ employee/, owner/, name/
β βββ route.ts # User CRUD (register)
βββ dashboard/v1/
β βββ stats/, tours/, bookings/, reviews/, reports/
β βββ employees/, running-tours/, faqs/, refunds/, transactions/
β βββ profile/, export/, search/
βββ operations/
β βββ tours/v1/
β β βββ route.ts # GET list / POST create
β β βββ [tourId]/
β β βββ route.ts # GET detail / POST submit-for-approval
β β βββ bangladesh-fields/ # PATCH step-1
β β βββ logistics/ # PATCH step-3
β β βββ pricing/ # PATCH step-4
β β βββ gallery/ # PATCH gallery images
β β βββ destinations/images-bulk/ # PATCH destination images
β β βββ destinations/attractions/images-bulk/
β β βββ moderation-status/
β β βββ archive/ # DELETE (soft-delete/archive)
β β βββ terminate/ # DELETE (terminate + refunds)
β βββ bookings/v1/
β β βββ route.ts
β β βββ summary/
β βββ reviews/v1/
β β βββ route.ts
β β βββ [reviewId]/replies/[replyId]/ # PATCH/DELETE reply
β βββ reports/v1/
β βββ route.ts
β βββ [reportId]/
β βββ bulk-resolve/
βββ users/employees/v1/
β βββ route.ts # POST create employee
β βββ [employeeId]/ # GET/PATCH/DELETE
β βββ payroll/
βββ settings/payment-accounts/v1/
β βββ route.ts
β βββ [id]/
βββ support/
β βββ password-requests/v1/ # Employee forgot-password workflow
β βββ tour-faq/
β βββ route.ts
β βββ [faqId]/
β βββ stats/
βββ notifications/guide/v1/
β βββ route.ts
β βββ [id]/
βββ (mock)/mock/ # Mock/dev-only endpoints
All API routes live under src/app/api following Next.js App Router conventions (route.ts files exporting GET/POST/PATCH/DELETE). Every handler is wrapped by withErrorHandler.
Response & Error Contract
Every route handler returns a HandlerResult<T> ({ data, status? }), and withErrorHandler converts it into a NextResponse:
- Success:
{ "data": <T> }with the given HTTP status (default200) - Failure:
{ "error": "<message>" }with a status derived from a thrownApiError(message, status), or500for unhandled errors
// Example handler shape
export const PATCH = withErrorHandler(async (req, { params }) => {
// ...validation, DB ops...
return { data: result, status: 200 };
});Throwing throw new ApiError("Tour not found", 404) anywhere inside a handler is automatically caught and serialized.
Authentication Flow
- Credential pre-validation β
POST /api/auth/user/v1/validatechecks email/password againstUserModel, enforces IP- and email-based rate limiting (10/min per IP, 5/min per email) viaauthRateLimit(Upstash Redis), and confirms role eligibility (GUIDEmust beAPPROVED,ASSISTANTmust not be soft-deleted) before returning a minimal{ id, email, role }payload. - NextAuth session issuance β
src/app/api/auth/[...nextauth]/(backed bysrc/lib/auth/options.auth.ts) re-runs the same checks inside itsCredentialsProvider.authorizeandsignIncallback (for Google OAuth), then issues a JWT session (30-day max age, refreshed hourly). - Session consumption β Downstream API routes call
requireSessionUserId()/getUserIdFromSession()to identify the caller, andVERIFY_USER_ROLE.GUIDE(...)/.MULTIPLE([...])helpers to enforce role-based authorization per endpoint.
Middleware: Rate Limiting & Route Protection (`src/proxy.ts`)
- Public routes pass through without checks.
- All other
/api/*routes are rate-limited to 100 requests / 60 seconds per IP, returning429on breach. - Protected pages require a valid NextAuth session (redirect to
/if missing). - Admin-tier pages (
ADMIN_ROLES = [ADMIN, GUIDE]) redirect non-privileged users to/dashboard?error=unauthorized.
Tour Endpoints (/api/operations/tours/v1)
| Method | Path | Purpose |
|---|---|---|
GET |
/ |
List tours (filterable) |
POST |
/ |
Create a new tour (DRAFT status) |
GET |
/[tourId] |
Full tour detail via buildTourDetailDTO |
POST |
/[tourId] |
Submit tour for moderation/re-approval (creates a SupportSystemNotification and triggers a socket event to admins) |
PATCH |
/[tourId]/bangladesh-fields |
Update step-1 (division, district, accommodation, etc.), validated via Step1BangladeshSchema |
PATCH |
/[tourId]/logistics |
Update step-3 logistics (main location, transport), validated via Step3LogisticsSchema |
PATCH |
/[tourId]/pricing |
Update step-4 pricing/discounts/duration, validated via Step4PricingSchema |
PATCH |
/[tourId]/gallery |
Replace/add gallery images (base64 or Cloudinary URL), resolves & dedupes assets outside the DB transaction |
PATCH |
/[tourId]/destinations/images-bulk |
Bulk add/delete images for a destination |
PATCH |
/[tourId]/destinations/attractions/images-bulk |
Bulk add/delete images for a specific attraction within a destination |
DELETE |
/[tourId]/moderation-status/archive |
Soft-delete/archive a tour (Guide/Assistant only) |
DELETE |
/[tourId]/moderation-status/terminate |
Terminate a tour, cascading into bookings/transactions/refunds via Stripe |
All mutating endpoints share common guardrails: tours in TERMINATED, ARCHIVED, or ACTIVE/COMPLETED states reject further edits with 409 Conflict, and every mutation is wrapped in withTransaction for atomicity, followed by auditTourMutation logging.
Review Endpoints (/api/operations/reviews/v1)
| Method | Path | Purpose |
|---|---|---|
GET/other |
/ |
List/query reviews |
POST |
/[reviewId]/replies |
Add a reply (Guide/Assistant only), rejects if review isn't isApproved |
PATCH |
/[reviewId]/replies/[replyId] |
Edit own reply |
DELETE |
/[reviewId]/replies/[replyId] |
Soft-delete own reply |
Booking & Report Endpoints
/api/operations/bookings/v1βroute.ts(CRUD/list) +summary/(aggregate stats)/api/operations/reports/v1βroute.ts(list/create),[reportId]/(detail/update),bulk-resolve/(batch resolution action)
Dashboard Endpoints (/api/dashboard/v1)
Sub-resources: stats/, tours/, bookings/, reviews/, reports/, employees/, running-tours/, faqs/, refunds/, transactions/, profile/, export/, search/ β each accepts date-range query params (e.g. statsDateRangeFrom/statsDateRangeTo) matching the DashboardFilters shape consumed by useDashboardStore on the client, and export/ produces CSV downloads.
Employee & Payment Endpoints
POST /api/users/employees/v1β create employee (validates via Yup, requiresGUIDErole, uploads avatar/documents to Cloudinary, creates linkedUserModelwithASSISTANTrole)/api/users/employees/v1/[employeeId]β detail/update/terminate/api/users/employees/v1/payrollβ payroll record management/api/settings/payment-accounts/v1and[id]/β Stripe payment account CRUD
Support Endpoints
POST /api/support/password-requests/v1β employee "forgot password" request; rate-limited per email (5/min), branches by role (ASSISTANTvsGUIDE), rejects duplicate pending requests/api/support/tour-faq/βroute.ts(list/create),[faqId]/(update/delete),stats/(aggregate counts)
Notification Endpoints
/api/notifications/guide/v1βroute.ts(list) +[id]/(mark read/delete). Notifications carry priority-based TTL:LOW/MEDIUMexpire after 60 days,HIGH/CRITICALnever auto-expire.
Calling Conventions Summary
- Dynamic route params are
Promise<{ id: string }>-typed (Next.js 15+/16 async params) and resolved withresolveMongoId(...). - Most mutation routes: (1) resolve/validate the ID, (2) resolve the acting user via
requireSessionUserId(), (3) validate body with Yup/Zod, (4) run DB writes insidewithTransaction, (5) callauditTourMutation/logAuditForActorfor audit trail, (6) return a rebuilt DTO viabuildTourDetailDTOor similar builder. - Cloudinary uploads are deliberately executed outside Mongo transactions to avoid the 60-second transaction timeout, with asset dedup/cleanup handled separately.
Prerequisites
| Requirement | Version | Purpose |
|---|---|---|
| Node.js | 20.x+ | Runtime |
| npm | 10.x+ | Package management |
| MongoDB | 7.0+ | Database |
| Redis | Upstash / Local | Caching & rate limiting |
Installation & Development
git clone https://github.com/ByteCrister/bd-travel-spirit-guide-system
cd bd-travel-spirit-guide-system
npm install
npm run dev # http://localhost:3000, Turbopack
npm run build
npm run lintConfigure a .env with MongoDB, NextAuth, Cloudinary, Stripe, and Upstash Redis credentials (.env* is git-ignored).
| File | Purpose |
|---|---|
tsconfig.json |
@/* β src/* path alias |
next.config.ts |
Image remote patterns (Cloudinary, GitHub avatars, etc.) |
postcss.config.mjs |
Tailwind CSS 4.0 |
eslint.config.mjs |
Lint presets |
erDiagram
"UserModel" ||--o| "GuideModel" : "owns"
"UserModel" ||--o| "EmployeeModel" : "references"
"GuideModel" ||--o{ "TourModel" : "operates"
"TourModel" ||--o{ "AssetModel" : "has (gallery)"
"AssetModel" ||--|| "AssetFileModel" : "points to"
"ReviewModel" ||--|| "TourModel" : "belongs to"
"ReportModel" ||--|| "TourModel" : "targets"
Key stores: useDashboardStore, useTourDetailStore, useReviewsStore, useReportsStore, useEmployeeStore, useNotificationStore, useCurrentUserStore, usePaymentAccountStore β using TTL caching and in-flight request de-duplication.
| Term | Meaning |
|---|---|
| Guide | Tour operator/business owner role |
| Assistant | Employee/support role |
| DTO | Data Transfer Object returned by builder functions like buildTourDetailDTO |
| ApiError | Custom error class carrying an HTTP status, caught by withErrorHandler |
| withTransaction | Helper wrapping Mongo multi-document writes in a session/transaction |
| Asset / AssetFile | Two-tier deduplicated media model |