Cafe Shop Documentation

A complete cafe and restaurant website template built with Next.js, MongoDB, and Cloudinary. Includes a public storefront and a full-featured admin dashboard.

Next.js 16 React 19 TypeScript MongoDB Cloudinary NextAuth 5 Tailwind CSS 4

Features

Everything included out of the box.

Dual Homepage Layouts
Two homepage designs switchable from admin settings.
Menu Management
Categorized product menu with ordering and status control.
Image Gallery
Masonry gallery with pagination and featured images.
Table Reservations
Customers book tables; admins manage with status updates.
Multi-Outlet Locations
Multiple branches with contacts and Google Maps links.
Chef Profiles
Showcase your culinary team with sortable profiles.
Hero Banners
Dynamic slider with 4 theme variants and drag-and-drop ordering.
Cloudinary CDN
All media uploaded and served via Cloudinary.
Secure Admin Auth
JWT + bcrypt authentication with session management.
Dark / Light Mode
Built-in theme toggle with persistent user preference.
Server-Side Caching
Cached public routes with manual flush endpoint.
SEO Metadata
Configurable title, description, keywords, OG image.
Showcase Sections
Editable homepage sections — Shop, Story, Offer, Reservation.
Business Hours
Per-day open/close times shown on the reservation page.
Drag & Drop Ordering
Reorder banners, categories, products, chefs, and gallery.
WYSIWYG Editor
Rich text editor for Terms and Privacy Policy content.

Requirements

RequirementDetails
Node.jsv18.x or higher (LTS recommended)
Package managernpm, yarn, or pnpm (latest)
MongoDBAtlas (cloud) or self-hosted v6+
CloudinaryFree tier is sufficient for development
GitAny recent version

Installation

1

Extract and open the project folder

cd cafe-shop
2

Install dependencies

npm install
3

Create environment file

cp .env.example .env

Fill in your MongoDB URI, NextAuth secret, and base URL. See Environment Variables.

4

Start the development server

npm run dev

App runs at http://localhost:3000. Turbopack is enabled for fast HMR.

5

Log into the admin panel

Go to http://localhost:3000/admin/login and sign in with the default credentials:

FieldDefault Value
Emailadmin@example.com
PasswordChangeMe123!
Change the default password immediately from Settings → Change Password.
6

Configure Cloudinary

Go to Admin → Settings → Cloudinary and enter your credentials before uploading any media.

Environment Variables

Create a .env file in the project root with the following variables.

MONGODB_URI
MongoDB connection string.
mongodb+srv://user:pass@cluster.mongodb.net/cafe-shop
NEXTAUTH_SECRET
Random string for JWT signing. Generate: openssl rand -base64 32
NEXTAUTH_URL
Canonical URL of the app. http://localhost:3000
NEXT_PUBLIC_BASE_URL
Public-facing base URL used by client-side API calls.
AUTH_TRUST_HOST
Set to "true" when deploying behind a proxy (Vercel, Nginx).
💡 Cloudinary credentials are stored in the database via Admin Settings — not in .env. This lets you update them without redeployment.

Database Setup

The project uses MongoDB with Mongoose. No manual migrations are needed — the database is seeded automatically on first run:

MongoDB Atlas Quick Setup

1

Create a Free Cluster

Sign up at mongodb.com/atlas and create an M0 free cluster.

2

Add a Database User

Security → Database Access → add a user with readWrite privileges.

3

Whitelist Network Access

Security → Network Access → add 0.0.0.0/0 for development, or your server IP for production.

4

Copy Connection String

Connect → Drivers → copy the string, replace <password>, and set it as MONGODB_URI.

Cloudinary Setup

All images are stored and served via Cloudinary.

1

Create a Free Account

Sign up at cloudinary.com. The free tier includes 25 GB storage and 25 GB/month bandwidth.

2

Get Your Credentials

From the Cloudinary dashboard, copy your Cloud Name, API Key, and API Secret.

3

Enter in Admin Settings

Admin → Settings → Cloudinary. Fill in all fields. Images are organized automatically into sub-folders:

your-folder/
├── product/
├── banner/
├── gallery/
├── chef/
├── outlet/
├── showcase/
└── settings/

Project Structure

cafe-shop/ ├── app/ # Next.js App Router │ ├── layout.tsx # Root layout with providers │ ├── globals.css # Global styles + Tailwind base │ ├── icon.tsx # App favicon │ ├── not-found.tsx # Custom 404 page │ ├── documentation/ # /documentation route │ ├── (public)/ # Public storefront (header + footer) │ │ ├── layout.tsx # Wraps pages with Header + Footer │ │ ├── page.tsx # Home — layout 1 │ │ ├── home2/ # Home — layout 2 │ │ ├── menu/ # Product menu with category filter │ │ ├── gallery/ # Photo gallery (masonry) │ │ ├── locations/ # Outlet locations + maps │ │ ├── reserve-table/ # Table booking form │ │ ├── our-story/ # About / brand story │ │ ├── policy/ # Privacy policy │ │ └── terms/ # Terms & conditions │ ├── (admin)/ # Admin auth pages (no auth guard) │ │ └── admin/ │ │ └── login/ # Admin login page │ ├── (auth)/ # NextAuth callback pages │ ├── (private)/ # Protected — requires session │ │ └── admin/ │ │ └── dashboard/ │ │ ├── page.tsx # Overview stats │ │ ├── banner/ # Banner list + create + edit/[id] │ │ ├── products/ # Product list + create + edit/[id] │ │ ├── categories/ # Category management │ │ ├── gallery/ # Gallery management │ │ ├── outlets/ # Outlet management │ │ ├── chef/ # Chef management │ │ ├── reserve/ # Reservation management │ │ ├── settings/ # Site settings (all tabs) │ │ └── (showcase)/ # Showcase section editors │ │ ├── offer/ │ │ ├── reservation/ │ │ ├── shop/ │ │ └── story/ │ └── api/ # API route handlers │ ├── auth/ │ │ └── [...nextauth]/ # NextAuth handler │ ├── admin/ # Protected — admin only │ │ ├── auth/ │ │ │ ├── login/ # POST — credential login │ │ │ ├── me/ # GET — current session │ │ │ └── password/ # PUT — change password │ │ ├── banner/ # GET list · POST create │ │ │ ├── [id]/ # GET · PUT · DELETE │ │ │ ├── sort/ # PUT — reorder │ │ │ └── status/[id]/ # PATCH — toggle active │ │ ├── category/ # GET list · POST create │ │ │ ├── [id]/ # GET · PUT · DELETE │ │ │ ├── sort/ # PUT — reorder │ │ │ └── status/[id]/ # PATCH — toggle active │ │ ├── product/ # GET list · POST create │ │ │ ├── [id]/ # GET · PUT · DELETE │ │ │ └── status/[id]/ # PATCH — toggle active │ │ ├── gallery/ # GET list · POST create │ │ │ ├── [id]/ # GET · PUT · DELETE │ │ │ ├── sort/ # PUT — reorder │ │ │ └── status/[id]/ # PATCH — toggle active │ │ ├── chef/ # GET list · POST create │ │ │ ├── [id]/ # GET · PUT · DELETE │ │ │ ├── sort/ # PUT — reorder │ │ │ └── status/[id]/ # PATCH — toggle active │ │ ├── outlet/ # GET list · POST create │ │ │ ├── [id]/ # GET · PUT · DELETE │ │ │ ├── sort/ # PUT — reorder │ │ │ └── status/[id]/ # PATCH — toggle active │ │ ├── reserve-table/ # GET list │ │ │ └── [id]/ # GET · PATCH status · DELETE │ │ ├── settings/ │ │ │ ├── general/ # Site name, logo, contact │ │ │ ├── metadata/ # SEO title, description, keywords │ │ │ ├── cloudinary/ # Cloudinary credentials │ │ │ ├── business-hour/ # Opening hours │ │ │ ├── page-banner/ # Per-page hero banners │ │ │ └── terms/ # Policy & terms content │ │ ├── dashboard/ # GET — stats summary │ │ ├── offer-showcase/ # GET · PUT │ │ ├── reservation-showcase/ # GET · PUT │ │ ├── shop-showcase/ # GET · PUT │ │ └── story-showcase/ # GET · PUT │ ├── banner/ # GET — active banners (public) │ ├── chef/ # GET — active chefs (public) │ ├── gallery/ │ │ ├── all/ # GET — all images │ │ └── featured/ # GET — featured images │ ├── menu/ │ │ └── [slug]/ # GET — products by category slug │ ├── product/ │ │ ├── featured/ # GET — featured products │ │ ├── most-loved/ # GET — most-loved products │ │ └── new/ # GET — new arrivals │ ├── outlet/ │ │ └── all/ # GET — all outlets │ ├── reserve-table/ # POST — submit reservation │ ├── settings/ # GET — public site settings │ ├── showcase/ │ │ ├── offer-showcase/ │ │ ├── reservation-showcase/ │ │ ├── shop-showcase/ │ │ └── story-showcase/ │ └── cache/ # DELETE — flush server cache │ ├── model/ # Mongoose schemas │ ├── User.ts │ ├── Banner.ts │ ├── Category.ts │ ├── Product.ts │ ├── Gallery.ts │ ├── Chef.ts │ ├── Outlet.ts │ ├── Reservation.ts │ ├── Settings.ts │ └── Showcase.ts │ ├── components/ # Shared React components │ ├── ui/ # shadcn/ui primitives │ ├── form/ # Reusable form input wrappers │ ├── landing-page/ # Homepage section components │ └── shared/ # Navbar, footer, common UI │ ├── actions/ # Next.js server actions ├── store/ # Zustand client state stores ├── lib/ # Utilities, validators, API helpers ├── config/ # DB connection, Cloudinary, constants ├── provider/ # React context providers ├── public/ # Static assets (images, fonts) │ ├── .env # Environment variables ├── next.config.ts # Next.js configuration ├── tailwind.config.ts # Tailwind CSS configuration ├── tsconfig.json # TypeScript configuration └── package.json # Dependencies

Tech Stack

CategoryTechnologyVersionPurpose
FrameworkNext.js16.2.3App Router, server actions, API routes
LanguageTypeScript5Type safety
UIReact19.1.0Component rendering
StylingTailwind CSS4Utility-first CSS
Componentsshadcn/ui + Radix UIlatestAccessible UI primitives
AnimationFramer Motion12.23Page and component animations
DatabaseMongoDB + Mongoose8.19.3Data persistence
AuthNextAuth5.0.0-betaSession management
Cryptobcrypt + JWT6.0 / 9.0.2Password hashing and token signing
StorageCloudinary2.8.0Image upload and CDN delivery
FormsReact Hook Form + Zod7.62 / 4.1.5Form state and validation
StateZustand5.0.8Client-side global state
DnDdnd-kit6.3.1Drag-and-drop sorting
CarouselSwiper + Embla12.0 / 8.6Hero banners and sliders
Rich TextSunEditor React3.6.1WYSIWYG editor
Cachenode-cache5.1.2Server-side in-memory cache
TablesTanStack Table8.21.3Admin data tables
ToastsReact Hot Toast2.6.0Notifications
IconsLucide + React Icons0.543 / 5.5Icon library
Themenext-themes0.4.6Dark / light mode

Pages & Routes

Public Routes

PathPageDescription
/Home (Layout 1)Hero banners, popular items, story, offer, reservation CTA
/home2Home (Layout 2)Alternative homepage layout
/menuMenuCategory-filtered product menu
/galleryGalleryMasonry photo gallery with pagination
/locationsLocationsOutlet branches with contacts and map links
/reserve-tableReserve TableBooking form with outlet selector and date picker
/our-storyOur StoryCompany story, values, chef section
/policyPrivacy PolicyContent editable from admin settings
/termsTerms & ConditionsContent editable from admin settings
/documentationDocumentationThis page

Admin Routes

PathDescription
/admin/loginAdmin authentication
/admin/dashboardOverview statistics
/admin/dashboard/productsProduct management
/admin/dashboard/categoriesCategory management
/admin/dashboard/bannerBanner management
/admin/dashboard/galleryGallery management
/admin/dashboard/outletsOutlet/location management
/admin/dashboard/chefChef profile management
/admin/dashboard/reserveReservation management
/admin/dashboard/shopShop showcase editor
/admin/dashboard/storyStory showcase editor
/admin/dashboard/offerOffer showcase editor
/admin/dashboard/reservationReservation CTA editor
/admin/dashboard/settingsAll site settings

Admin Login

Access the admin panel at /admin/login. After login, a JWT token is issued (30-day expiry). All dashboard routes redirect unauthenticated visitors to login.

Default credentials: admin@example.com / ChangeMe123! — change immediately after first login.

Products

Manage menu items at /admin/dashboard/products.

FieldTypeDescription
NameTextProduct display name
Short DescriptionTextBrief description shown on product cards
PriceNumberProduct price
CategorySelectLinked menu category
ImageFileProduct image (uploaded to Cloudinary)
StatusToggleActive / inactive visibility on the menu
FeaturedToggleAppears in the featured products section
Most LovedToggleAppears in the most-loved section
NewToggleAppears in the new arrivals section

Categories

Categories group products into menu sections. Manage at /admin/dashboard/categories.

FieldTypeDescription
NameTextDisplay name (e.g. "Hot Coffee")
SlugTextURL-safe identifier (e.g. "hot-coffee"), used in menu filter
StatusToggleShow or hide in the public menu
PositionDrag & dropDisplay order

Banners

Hero slider banners managed at /admin/dashboard/banner. Supports 4 visual themes.

FieldTypeDescription
TaglineTextSmall label above the heading
HeadingTextMain banner headline
Short DescriptionTextSupporting text
ImageFileBanner background image
ThemeSelect 1–4Visual layout variant for this slide
StatusToggleShow / hide the slide
PositionDrag & dropSlide order in the carousel

Outlets

Manage branch locations at /admin/dashboard/outlets. Each outlet is selectable during table reservations.

FieldTypeDescription
NameTextBranch name
LocationTextAddress
Google Map LinkURLGoogle Maps link displayed on the locations page
PhonePhoneContact number with international dial code
ImageFileOutlet photo
StatusToggleAvailable for reservations and public display

Chefs

Showcase your team at /admin/dashboard/chef.

FieldTypeDescription
NameTextFull name
TaglineTextRole or specialty (e.g. "Head Pastry Chef")
GenderSelectmale / female / other
PhonePhoneInternal contact (not shown publicly)
ImageFilePortrait photo
StatusTogglePublic visibility
PositionDrag & dropDisplay order

Reservations

View and manage reservation requests at /admin/dashboard/reserve.

ColumnDescription
Guest NameCustomer full name
EmailCustomer email address
PhoneContact number
OutletSelected branch
Date & TimeRequested reservation datetime
GuestsNumber of people
ReasonPurpose of visit
Statuspending / confirmed / cancelled
The system rejects a new reservation if the same email already has a pending booking.

Showcase Sections

The four homepage showcase blocks are fully editable:

SectionRouteEditable Fields
Shop Showcase/admin/dashboard/shopHeading, description, coffee lovers count, tagline, 3 images
Story Showcase/admin/dashboard/storyHeading, description, story text, tagline, 3 images, values list
Offer Showcase/admin/dashboard/offerHeading, tagline, deadline date, image, linked products
Reservation CTA/admin/dashboard/reservationCTA text, heading, tagline, dark image, light image

Settings

All site-wide configuration is at /admin/dashboard/settings, organized into tabs:

TabConfigurable Fields
GeneralCompany name, address, phone, email, social links (Facebook, Instagram, Twitter, YouTube), logo, favicon, home layout
Business HoursOpen/close times per day, closed-day toggle for all 7 days
Page BannersHero images for Menu, Gallery, Locations, and Reserve Table pages
CloudinaryCloud name, API key, API secret, upload folder, URL base
SEO MetadataTitle, app name, description, keywords, Open Graph image
Terms & PolicyRich-text content for Terms of Service and Privacy Policy
Change PasswordUpdate admin account password

Authentication

All admin endpoints require a Bearer token in the Authorization header:

Authorization: Bearer <jwt_token>

Obtain the token from POST /api/admin/auth/login. Every API response follows this shape:

{
  "success": true,
  "code": 200,
  "message": "Operation successful",
  "data": { }
}
Auth Endpoints
POST /api/admin/auth/login Public

Authenticate admin user. Returns a JWT token valid for 30 days.

Request Body

{
  "email": "admin@example.com",
  "password": "yourPassword"
}

Response

{
  "success": true,
  "code": 200,
  "message": "Login successful",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}
GET /api/admin/auth/me Auth Required

Returns the authenticated admin's profile object.

POST /api/admin/auth/password Auth Required

Change admin password.

Request Body

{
  "oldPassword": "currentPassword",
  "newPassword": "newSecurePassword",
  "confirmPassword": "newSecurePassword"
}

Banners API

Public
GET/api/bannerPublic

Returns all active banners sorted by position.

Admin
GET/api/admin/bannerAuth Required

Paginated list of all banners. Query params: page, limit, search.

POST/api/admin/bannerAuth Required

Create a new banner. Body is multipart/form-data.

Fields: tagline, heading, shortDesc, theme (1–4), image (file)

GET/api/admin/banner/[id]Auth Required

Get a single banner by ID.

PUT/api/admin/banner/[id]Auth Required

Update banner. Optionally replace the image. Body is multipart/form-data.

DELETE/api/admin/banner/[id]Auth Required

Delete banner and remove its image from Cloudinary.

POST/api/admin/banner/sortAuth Required

Update display order.

{
  "sortedIds": [
    "64f1a2b3c4d5e6f7a8b9c0d1",
    "64f1a2b3c4d5e6f7a8b9c0d2",
    "64f1a2b3c4d5e6f7a8b9c0d3"
  ]
}
PATCH/api/admin/banner/status/[id]Auth Required

Toggle banner active status.

{
  "status": true
}

Categories API

Admin
GET/api/admin/categoryAuth Required

Paginated categories with product counts. Query: page, limit, search.

POST/api/admin/categoryAuth Required

Create a category.

{
  "name": "Hot Coffee",
  "slug": "hot-coffee"
}
PUT/api/admin/category/[id]Auth Required

Update category name and/or slug.

DELETE/api/admin/category/[id]Auth Required

Delete category. Also removes associated products.

POST/api/admin/category/sortAuth Required

Reorder categories.

{
  "sortedIds": [
    "64f1a2b3c4d5e6f7a8b9c0d1",
    "64f1a2b3c4d5e6f7a8b9c0d2"
  ]
}
PATCH/api/admin/category/status/[id]Auth Required

Toggle category visibility.

{
  "status": false
}

Products API

Public
GET/api/product/featuredPublic

Get featured products. Query: limit.

GET/api/product/most-lovedPublic

Get most-loved products. Query: limit.

GET/api/product/newPublic

Get new arrival products. Query: limit.

Admin
GET/api/admin/productAuth Required

Paginated products. Query: page, limit, search, tag (featured | mostLoved).

POST/api/admin/productAuth Required

Create a product. Body is multipart/form-data.

Fields: name, shortDesc, price, category (ID), status, mostLoved, featured, new, image (file)

PUT/api/admin/product/[id]Auth Required

Update product fields. Optionally replace image.

DELETE/api/admin/product/[id]Auth Required

Delete product and its Cloudinary image.

PATCH/api/admin/product/status/[id]Auth Required

Toggle product active status.

{
  "status": true
}

Chefs API

GET/api/chefPublic

Get all active chefs sorted by position.

GET/api/admin/chefAuth Required

All chefs for admin management.

POST/api/admin/chefAuth Required

Create chef profile. Body is multipart/form-data. Fields: name, tagline, gender, dialCode, phone, image (file).

PUT/api/admin/chef/[id]Auth Required

Update chef details or replace image.

DELETE/api/admin/chef/[id]Auth Required

Delete chef and remove image from Cloudinary.

POST/api/admin/chef/sortAuth Required

Reorder chefs.

{
  "sortedIds": [
    "64f1a2b3c4d5e6f7a8b9c0d1",
    "64f1a2b3c4d5e6f7a8b9c0d2"
  ]
}

Outlets API

GET/api/outlet/allPublic

Get all active outlets with contact info and image URLs.

GET/api/admin/outletAuth Required

All outlets for admin management.

POST/api/admin/outletAuth Required

Create outlet. Body is multipart/form-data. Fields: name, location, googleMapLink, dialCode, phone, image (file).

PUT/api/admin/outlet/[id]Auth Required

Update outlet details or replace image.

DELETE/api/admin/outlet/[id]Auth Required

Delete outlet and its Cloudinary image.

PATCH/api/admin/outlet/status/[id]Auth Required

Toggle outlet active status.

{
  "status": false
}

Menu API

GET/api/menuPublic

All active categories with their products. Response is server-cached (cache key: MENU). Cache invalidates automatically on product/category changes.

GET/api/menu/[slug]Public

Products for a specific category by its slug. Param: slug.

Reservations API

POST/api/reserve-tablePublic

Submit a table reservation request. Validates outlet exists. Rejects if the same email already has a pending booking.

Request Body

{
  "outlet": "64f1a2b3c4d5e6f7a8b9c0d1",
  "reason": "Birthday Celebration",
  "name": "John Doe",
  "email": "john@example.com",
  "dialCode": "+1",
  "phone": "5551234567",
  "reservedAt": "2024-12-25T19:00:00.000Z",
  "numOfPeople": 4,
  "message": "Please arrange a cake"
}
GET/api/admin/reserve-tableAuth Required

Paginated reservations. Query: page, limit, search, status (pending | confirmed | cancelled).

PATCH/api/admin/reserve-table/[id]Auth Required

Update reservation status. Accepted values: "pending", "confirmed", "cancelled".

{
  "status": "confirmed"
}

Settings API

GET/api/settingsPublic

Returns full site settings (general info, business hours, metadata, etc.). Used by the frontend to render dynamic content.

POST/api/admin/settings/generalAuth Required

Update general settings. Body is multipart/form-data — includes companyName, companyPhone, companyAddress, supportEmail, facebook, instagram, twitter, youtube, homeView, logo and favicon (files).

POST/api/admin/settings/business-hourAuth Required

Update business hours for all 7 days. openTime and closeTime are minutes from midnight (e.g. 540 = 9:00 AM, 1320 = 10:00 PM).

{
  "businessHours": [
    { "dayOfWeek": 0, "openTime": 540, "closeTime": 1320, "isClosed": false },
    { "dayOfWeek": 1, "openTime": 540, "closeTime": 1320, "isClosed": false },
    { "dayOfWeek": 2, "openTime": 540, "closeTime": 1320, "isClosed": false },
    { "dayOfWeek": 3, "openTime": 540, "closeTime": 1320, "isClosed": false },
    { "dayOfWeek": 4, "openTime": 540, "closeTime": 1320, "isClosed": false },
    { "dayOfWeek": 5, "openTime": 540, "closeTime": 1320, "isClosed": false },
    { "dayOfWeek": 6, "openTime": 540, "closeTime": 1320, "isClosed": true }
  ]
}
POST/api/admin/settings/page-bannerAuth Required

Update page hero images. Body is multipart/form-data with files: menu, location, gallery, reserveTable.

POST/api/admin/settings/cloudinaryAuth Required

Update Cloudinary credentials.

{
  "cloudName": "your-cloud-name",
  "apiKey": "123456789012345",
  "apiSecret": "abcdefghijklmnopqrstuvwxyz",
  "folder": "cafe-shop",
  "secureUrlBase": "https://res.cloudinary.com"
}
POST/api/admin/settings/metadataAuth Required

Update SEO metadata.

{
  "title": "Cafe Shop — Best Coffee in Town",
  "applicationName": "Cafe Shop",
  "description": "Artisan coffee and handcrafted pastries.",
  "keywords": ["cafe", "coffee", "pastries", "restaurant"],
  "openGraphImage": "https://res.cloudinary.com/your-cloud/image/upload/og.jpg"
}
POST/api/admin/settings/termsAuth Required

Update Terms and Privacy Policy. Both fields accept HTML generated by the rich-text editor.

{
  "terms": "<h2>Terms of Service</h2><p>...</p>",
  "policy": "<h2>Privacy Policy</h2><p>...</p>"
}

Showcase API

Public
GET/api/showcase/shop-showcasePublic

Get shop/cafe highlight section data.

GET/api/showcase/story-showcasePublic

Get company story section including values list.

GET/api/showcase/offer-showcasePublic

Get special offer section with linked products and deadline.

GET/api/showcase/reservation-showcasePublic

Get reservation CTA data including current business hours.

Admin
POST/api/admin/shop-showcaseAuth Required

Update shop showcase. Body is multipart/form-data. Fields: heading, shortDesc, coffeeLovers, tagline, imageOne, imageTwo, imageThree (files).

POST/api/admin/story-showcaseAuth Required

Update story showcase. Body is multipart/form-data. Fields: heading, shortDesc, story, tagline, values (JSON string), 3 image files.

POST/api/admin/offer-showcaseAuth Required

Update offer showcase. Body is multipart/form-data. Fields: heading, tagline, deadline (ISO date), image (file), products (comma-separated IDs).

POST/api/admin/reservation-showcaseAuth Required

Update reservation CTA. Body is multipart/form-data. Fields: cta, heading, tagline, darkImage, lightImage (files).

Dashboard & Cache

GET/api/admin/dashboardAuth Required

Overview statistics.

{
  "success": true,
  "data": {
    "totalProducts": 48,
    "totalCategories": 6,
    "totalOutlets": 3,
    "reservations": {
      "total": 120,
      "pending": 14,
      "confirmed": 98,
      "cancelled": 8
    }
  }
}
GET/api/cachePublic

Returns current cache statistics (keys, hit rates, memory usage).

DELETE/api/cacheAuth Required

Flush all server-side cache. Forces fresh data on the next request.

Database Schemas

MongoDB collections and their Mongoose field definitions.

UserCollection: users
{
  name:      String,   // required
  email:     String,   // required, unique
  password:  String,   // required, bcrypt hashed
  role:      String,   // "admin" | "user"
  createdAt: Date,
  updatedAt: Date
}
CategoryCollection: categories
{
  name:      String,   // required
  slug:      String,   // required, unique
  status:    Boolean,  // default: true
  position:  Number,   // default: 0
  createdAt: Date,
  updatedAt: Date
}
// Index: { slug: 1, position: 1 }
ProductCollection: products
{
  name:      String,   // required
  shortDesc: String,   // required
  price:     Number,   // required
  image:     String,   // Cloudinary public_id
  category:  ObjectId, // ref: Category
  status:    Boolean,  // default: true
  mostLoved: Boolean,  // default: false
  featured:  Boolean,  // default: false
  new:       Boolean,  // default: false
  createdAt: Date,
  updatedAt: Date
}
// Index: { category: 1 }
BannerCollection: banners
{
  tagline:   String,   // required
  heading:   String,   // required
  shortDesc: String,   // required
  image:     String,   // Cloudinary public_id
  position:  Number,   // default: 0
  theme:     Number,   // 1 | 2 | 3 | 4, default: 1
  status:    Boolean,  // default: true
  createdAt: Date,
  updatedAt: Date
}
// Index: { position: 1 }
GalleryCollection: galleries
{
  image:      String,  // Cloudinary public_id
  tagline:    String,  // required
  capturedBy: String,  // required
  position:   Number,  // default: 0
  status:     Boolean, // default: true
  featured:   Boolean, // default: false
  createdAt:  Date,
  updatedAt:  Date
}
ChefCollection: chefs
{
  image:     String,   // Cloudinary public_id
  name:      String,   // required
  tagline:   String,   // required
  gender:    String,   // "male" | "female" | "other"
  dialCode:  String,   // required
  phone:     String,   // required
  position:  Number,   // default: 0
  status:    Boolean,  // default: true
  createdAt: Date,
  updatedAt: Date
}
// Index: { position: 1 }
OutletCollection: outlets
{
  name:          String,  // required
  location:      String,  // required
  googleMapLink: String,  // required
  dialCode:      String,  // required
  phone:         String,  // required
  image:         String,  // Cloudinary public_id
  status:        Boolean, // default: true
  position:      Number,  // default: 0
  createdAt:     Date,
  updatedAt:     Date
}
// Index: { position: 1 }
ReserveTableCollection: reserve_tables
{
  outlet:      ObjectId, // ref: Outlet
  reason:      String,   // required
  name:        String,   // required
  email:       String,   // required
  dialCode:    String,   // required
  phone:       String,   // required
  reservedAt:  Date,     // required
  numOfPeople: Number,   // required
  message:     String,   // optional
  status:      String,   // "pending" | "confirmed" | "cancelled", default: "pending"
  createdAt:   Date,
  updatedAt:   Date
}
SettingsCollection: settings
{
  general: {
    companyName:    String,
    companyDialCode: String,
    companyPhone:   String,
    companyAddress: String,
    logo:           String,  // Cloudinary public_id
    favicon:        String,  // Cloudinary public_id
    supportEmail:   String,
    ownerName:      String,
    ownerEmail:     String,
    facebook:       String,
    instagram:      String,
    twitter:        String,
    youtube:        String,
    homeView:       String   // "1" | "2"
  },
  pageBanner: {
    menu:         String,    // Cloudinary public_id
    location:     String,
    gallery:      String,
    reserveTable: String
  },
  cloudinary: {
    cloudName:     String,
    apiKey:        String,
    apiSecret:     String,
    folder:        String,
    secureUrlBase: String
  },
  metadata: {
    title:           String,
    applicationName: String,
    description:     String,
    keywords:        [String],
    openGraphImage:  String
  },
  termsPolicy: {
    terms:  String,          // HTML content
    policy: String           // HTML content
  },
  businessHours: [           // 7 entries, one per day
    {
      dayOfWeek: Number,     // 0 = Sunday … 6 = Saturday
      openTime:  Number,     // minutes from midnight (e.g. 540 = 9:00 AM)
      closeTime: Number,     // minutes from midnight (e.g. 1320 = 10:00 PM)
      isClosed:  Boolean
    }
  ]
}
ShowcaseCollection: showcases
{
  shopShowcase: {
    heading:      String,
    shortDesc:    String,
    coffeeLovers: Number,
    tagline:      String,
    imageOne:     String,    // Cloudinary public_id
    imageTwo:     String,
    imageThree:   String
  },
  storyShowcase: {
    heading:        String,
    shortDesc:      String,
    story:          String,
    tagline:        String,
    imageOne:       String,
    imageTwo:       String,
    imageThree:     String,
    valueShortDesc: String,
    values: [
      { title: String, shortDesc: String, icon: String }
    ]
  },
  offerShowcase: {
    heading:  String,
    tagline:  String,
    deadline: Date,
    image:    String,
    products: [ObjectId]     // ref: Product
  },
  reservationShowcase: {
    cta:       String,
    heading:   String,
    tagline:   String,
    darkImage: String,
    lightImage: String
  }
}

Deployment

Vercel (Recommended)

1

Push to GitHub

Push the project to a GitHub, GitLab, or Bitbucket repository.

2

Import in Vercel

Go to vercel.com → New Project → import your repository.

3

Add Environment Variables

Project Settings → Environment Variables → add MONGODB_URI, NEXTAUTH_SECRET, NEXTAUTH_URL, NEXT_PUBLIC_BASE_URL, AUTH_TRUST_HOST=true.

4

Deploy

Click Deploy. Subsequent pushes to the main branch trigger automatic deployments.

Self-Hosted

npm run build
npm start
The project sets a 20 MB body limit for server actions. Ensure any upstream proxy (Nginx, etc.) allows the same limit.

Customization

Brand Color

/* app/globals.css */
:root {
  --primary: #c8a97e; /* change to your brand color */
}

Switch Homepage Layout

Admin → Settings → General → set Home View to 1 or 2. No code changes needed.

Adding a New Feature

  1. Create a Mongoose model in model/YourModel.ts
  2. Add API routes in app/api/admin/your-resource/route.ts
  3. Create server actions in actions/your-resource/
  4. Build the admin page at app/(private)/admin/dashboard/your-page/page.tsx

Flushing Cache

DELETE /api/cache
Authorization: Bearer <token>

Support

Open a support ticket via the item's comments section. Please include:

Common Issues

ProblemSolution
Image upload failsVerify Cloudinary credentials are saved in Admin → Settings → Cloudinary
Database connection errorCheck MongoDB Atlas network access and confirm MONGODB_URI is correct
Admin login fails after deployEnsure NEXTAUTH_SECRET and NEXTAUTH_URL are set in production environment variables
Images not displayingCheck that the Cloudinary secureUrlBase and folder fields match your account

Cafe Shop Template · Documentation v1.0 · Built with Next.js & MongoDB