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.
Features
Everything included out of the box.
Requirements
| Requirement | Details |
|---|---|
| Node.js | v18.x or higher (LTS recommended) |
| Package manager | npm, yarn, or pnpm (latest) |
| MongoDB | Atlas (cloud) or self-hosted v6+ |
| Cloudinary | Free tier is sufficient for development |
| Git | Any recent version |
Installation
Extract and open the project folder
cd cafe-shop
Install dependencies
npm install
Create environment file
cp .env.example .env
Fill in your MongoDB URI, NextAuth secret, and base URL. See Environment Variables.
Start the development server
npm run dev
App runs at http://localhost:3000. Turbopack is enabled for fast HMR.
Log into the admin panel
Go to http://localhost:3000/admin/login and sign in with the default credentials:
| Field | Default Value |
|---|---|
admin@example.com | |
| Password | ChangeMe123! |
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+srv://user:pass@cluster.mongodb.net/cafe-shop
openssl rand -base64 32"true" when deploying behind a proxy (Vercel, Nginx)..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:
- Default admin account:
admin@example.com/ChangeMe123! - Default business hours: 9:00 AM – 10:00 PM for all 7 days
- Blank settings document ready for configuration
MongoDB Atlas Quick Setup
Create a Free Cluster
Sign up at mongodb.com/atlas and create an M0 free cluster.
Add a Database User
Security → Database Access → add a user with readWrite privileges.
Whitelist Network Access
Security → Network Access → add 0.0.0.0/0 for development, or your server IP for production.
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.
Create a Free Account
Sign up at cloudinary.com. The free tier includes 25 GB storage and 25 GB/month bandwidth.
Get Your Credentials
From the Cloudinary dashboard, copy your Cloud Name, API Key, and API Secret.
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
Tech Stack
| Category | Technology | Version | Purpose |
|---|---|---|---|
| Framework | Next.js | 16.2.3 | App Router, server actions, API routes |
| Language | TypeScript | 5 | Type safety |
| UI | React | 19.1.0 | Component rendering |
| Styling | Tailwind CSS | 4 | Utility-first CSS |
| Components | shadcn/ui + Radix UI | latest | Accessible UI primitives |
| Animation | Framer Motion | 12.23 | Page and component animations |
| Database | MongoDB + Mongoose | 8.19.3 | Data persistence |
| Auth | NextAuth | 5.0.0-beta | Session management |
| Crypto | bcrypt + JWT | 6.0 / 9.0.2 | Password hashing and token signing |
| Storage | Cloudinary | 2.8.0 | Image upload and CDN delivery |
| Forms | React Hook Form + Zod | 7.62 / 4.1.5 | Form state and validation |
| State | Zustand | 5.0.8 | Client-side global state |
| DnD | dnd-kit | 6.3.1 | Drag-and-drop sorting |
| Carousel | Swiper + Embla | 12.0 / 8.6 | Hero banners and sliders |
| Rich Text | SunEditor React | 3.6.1 | WYSIWYG editor |
| Cache | node-cache | 5.1.2 | Server-side in-memory cache |
| Tables | TanStack Table | 8.21.3 | Admin data tables |
| Toasts | React Hot Toast | 2.6.0 | Notifications |
| Icons | Lucide + React Icons | 0.543 / 5.5 | Icon library |
| Theme | next-themes | 0.4.6 | Dark / light mode |
Pages & Routes
Public Routes
| Path | Page | Description |
|---|---|---|
/ | Home (Layout 1) | Hero banners, popular items, story, offer, reservation CTA |
/home2 | Home (Layout 2) | Alternative homepage layout |
/menu | Menu | Category-filtered product menu |
/gallery | Gallery | Masonry photo gallery with pagination |
/locations | Locations | Outlet branches with contacts and map links |
/reserve-table | Reserve Table | Booking form with outlet selector and date picker |
/our-story | Our Story | Company story, values, chef section |
/policy | Privacy Policy | Content editable from admin settings |
/terms | Terms & Conditions | Content editable from admin settings |
/documentation | Documentation | This page |
Admin Routes
| Path | Description |
|---|---|
/admin/login | Admin authentication |
/admin/dashboard | Overview statistics |
/admin/dashboard/products | Product management |
/admin/dashboard/categories | Category management |
/admin/dashboard/banner | Banner management |
/admin/dashboard/gallery | Gallery management |
/admin/dashboard/outlets | Outlet/location management |
/admin/dashboard/chef | Chef profile management |
/admin/dashboard/reserve | Reservation management |
/admin/dashboard/shop | Shop showcase editor |
/admin/dashboard/story | Story showcase editor |
/admin/dashboard/offer | Offer showcase editor |
/admin/dashboard/reservation | Reservation CTA editor |
/admin/dashboard/settings | All 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.
admin@example.com / ChangeMe123! — change immediately after first login.Products
Manage menu items at /admin/dashboard/products.
| Field | Type | Description |
|---|---|---|
| Name | Text | Product display name |
| Short Description | Text | Brief description shown on product cards |
| Price | Number | Product price |
| Category | Select | Linked menu category |
| Image | File | Product image (uploaded to Cloudinary) |
| Status | Toggle | Active / inactive visibility on the menu |
| Featured | Toggle | Appears in the featured products section |
| Most Loved | Toggle | Appears in the most-loved section |
| New | Toggle | Appears in the new arrivals section |
Categories
Categories group products into menu sections. Manage at /admin/dashboard/categories.
| Field | Type | Description |
|---|---|---|
| Name | Text | Display name (e.g. "Hot Coffee") |
| Slug | Text | URL-safe identifier (e.g. "hot-coffee"), used in menu filter |
| Status | Toggle | Show or hide in the public menu |
| Position | Drag & drop | Display order |
Banners
Hero slider banners managed at /admin/dashboard/banner. Supports 4 visual themes.
| Field | Type | Description |
|---|---|---|
| Tagline | Text | Small label above the heading |
| Heading | Text | Main banner headline |
| Short Description | Text | Supporting text |
| Image | File | Banner background image |
| Theme | Select 1–4 | Visual layout variant for this slide |
| Status | Toggle | Show / hide the slide |
| Position | Drag & drop | Slide order in the carousel |
Gallery
Manage photos at /admin/dashboard/gallery.
| Field | Type | Description |
|---|---|---|
| Image | File | Gallery photo |
| Tagline | Text | Caption or mood tag |
| Captured By | Text | Photographer credit |
| Featured | Toggle | Show in the featured gallery block on the homepage |
| Status | Toggle | Public visibility |
| Position | Drag & drop | Display order |
Outlets
Manage branch locations at /admin/dashboard/outlets. Each outlet is selectable during table reservations.
| Field | Type | Description |
|---|---|---|
| Name | Text | Branch name |
| Location | Text | Address |
| Google Map Link | URL | Google Maps link displayed on the locations page |
| Phone | Phone | Contact number with international dial code |
| Image | File | Outlet photo |
| Status | Toggle | Available for reservations and public display |
Chefs
Showcase your team at /admin/dashboard/chef.
| Field | Type | Description |
|---|---|---|
| Name | Text | Full name |
| Tagline | Text | Role or specialty (e.g. "Head Pastry Chef") |
| Gender | Select | male / female / other |
| Phone | Phone | Internal contact (not shown publicly) |
| Image | File | Portrait photo |
| Status | Toggle | Public visibility |
| Position | Drag & drop | Display order |
Reservations
View and manage reservation requests at /admin/dashboard/reserve.
| Column | Description |
|---|---|
| Guest Name | Customer full name |
| Customer email address | |
| Phone | Contact number |
| Outlet | Selected branch |
| Date & Time | Requested reservation datetime |
| Guests | Number of people |
| Reason | Purpose of visit |
| Status | pending / confirmed / cancelled |
Showcase Sections
The four homepage showcase blocks are fully editable:
| Section | Route | Editable Fields |
|---|---|---|
| Shop Showcase | /admin/dashboard/shop | Heading, description, coffee lovers count, tagline, 3 images |
| Story Showcase | /admin/dashboard/story | Heading, description, story text, tagline, 3 images, values list |
| Offer Showcase | /admin/dashboard/offer | Heading, tagline, deadline date, image, linked products |
| Reservation CTA | /admin/dashboard/reservation | CTA text, heading, tagline, dark image, light image |
Settings
All site-wide configuration is at /admin/dashboard/settings, organized into tabs:
| Tab | Configurable Fields |
|---|---|
| General | Company name, address, phone, email, social links (Facebook, Instagram, Twitter, YouTube), logo, favicon, home layout |
| Business Hours | Open/close times per day, closed-day toggle for all 7 days |
| Page Banners | Hero images for Menu, Gallery, Locations, and Reserve Table pages |
| Cloudinary | Cloud name, API key, API secret, upload folder, URL base |
| SEO Metadata | Title, app name, description, keywords, Open Graph image |
| Terms & Policy | Rich-text content for Terms of Service and Privacy Policy |
| Change Password | Update 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": { }
}
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..."
}
}
Returns the authenticated admin's profile object.
Change admin password.
Request Body
{
"oldPassword": "currentPassword",
"newPassword": "newSecurePassword",
"confirmPassword": "newSecurePassword"
}
Banners API
Returns all active banners sorted by position.
Paginated list of all banners. Query params: page, limit, search.
Create a new banner. Body is multipart/form-data.
Fields: tagline, heading, shortDesc, theme (1–4), image (file)
Get a single banner by ID.
Update banner. Optionally replace the image. Body is multipart/form-data.
Delete banner and remove its image from Cloudinary.
Update display order.
{
"sortedIds": [
"64f1a2b3c4d5e6f7a8b9c0d1",
"64f1a2b3c4d5e6f7a8b9c0d2",
"64f1a2b3c4d5e6f7a8b9c0d3"
]
}
Toggle banner active status.
{
"status": true
}
Categories API
Paginated categories with product counts. Query: page, limit, search.
Create a category.
{
"name": "Hot Coffee",
"slug": "hot-coffee"
}
Update category name and/or slug.
Delete category. Also removes associated products.
Reorder categories.
{
"sortedIds": [
"64f1a2b3c4d5e6f7a8b9c0d1",
"64f1a2b3c4d5e6f7a8b9c0d2"
]
}
Toggle category visibility.
{
"status": false
}
Products API
Get featured products. Query: limit.
Get most-loved products. Query: limit.
Get new arrival products. Query: limit.
Paginated products. Query: page, limit, search, tag (featured | mostLoved).
Create a product. Body is multipart/form-data.
Fields: name, shortDesc, price, category (ID), status, mostLoved, featured, new, image (file)
Update product fields. Optionally replace image.
Delete product and its Cloudinary image.
Toggle product active status.
{
"status": true
}
Gallery API
Paginated gallery items (active only). Query: page, limit.
Featured gallery items for homepage display. Query: limit.
All gallery items with pagination.
Upload gallery image. Body is multipart/form-data. Fields: tagline, capturedBy, image (file).
Update gallery item metadata or replace image.
Delete gallery item and its Cloudinary image.
Update gallery display order.
{
"sortedIds": [
"64f1a2b3c4d5e6f7a8b9c0d1",
"64f1a2b3c4d5e6f7a8b9c0d2"
]
}
Toggle gallery item visibility or featured flag.
Chefs API
Get all active chefs sorted by position.
All chefs for admin management.
Create chef profile. Body is multipart/form-data. Fields: name, tagline, gender, dialCode, phone, image (file).
Update chef details or replace image.
Delete chef and remove image from Cloudinary.
Reorder chefs.
{
"sortedIds": [
"64f1a2b3c4d5e6f7a8b9c0d1",
"64f1a2b3c4d5e6f7a8b9c0d2"
]
}
Outlets API
Get all active outlets with contact info and image URLs.
All outlets for admin management.
Create outlet. Body is multipart/form-data. Fields: name, location, googleMapLink, dialCode, phone, image (file).
Update outlet details or replace image.
Delete outlet and its Cloudinary image.
Toggle outlet active status.
{
"status": false
}
Reservations API
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"
}
Paginated reservations. Query: page, limit, search, status (pending | confirmed | cancelled).
Update reservation status. Accepted values: "pending", "confirmed", "cancelled".
{
"status": "confirmed"
}
Settings API
Returns full site settings (general info, business hours, metadata, etc.). Used by the frontend to render dynamic content.
Update general settings. Body is multipart/form-data — includes companyName, companyPhone, companyAddress, supportEmail, facebook, instagram, twitter, youtube, homeView, logo and favicon (files).
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 }
]
}
Update page hero images. Body is multipart/form-data with files: menu, location, gallery, reserveTable.
Update Cloudinary credentials.
{
"cloudName": "your-cloud-name",
"apiKey": "123456789012345",
"apiSecret": "abcdefghijklmnopqrstuvwxyz",
"folder": "cafe-shop",
"secureUrlBase": "https://res.cloudinary.com"
}
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"
}
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
Get shop/cafe highlight section data.
Get company story section including values list.
Get special offer section with linked products and deadline.
Get reservation CTA data including current business hours.
Update shop showcase. Body is multipart/form-data. Fields: heading, shortDesc, coffeeLovers, tagline, imageOne, imageTwo, imageThree (files).
Update story showcase. Body is multipart/form-data. Fields: heading, shortDesc, story, tagline, values (JSON string), 3 image files.
Update offer showcase. Body is multipart/form-data. Fields: heading, tagline, deadline (ISO date), image (file), products (comma-separated IDs).
Update reservation CTA. Body is multipart/form-data. Fields: cta, heading, tagline, darkImage, lightImage (files).
Dashboard & Cache
Overview statistics.
{
"success": true,
"data": {
"totalProducts": 48,
"totalCategories": 6,
"totalOutlets": 3,
"reservations": {
"total": 120,
"pending": 14,
"confirmed": 98,
"cancelled": 8
}
}
}
Returns current cache statistics (keys, hit rates, memory usage).
Flush all server-side cache. Forces fresh data on the next request.
Database Schemas
MongoDB collections and their Mongoose field definitions.
{
name: String, // required
email: String, // required, unique
password: String, // required, bcrypt hashed
role: String, // "admin" | "user"
createdAt: Date,
updatedAt: Date
}
{
name: String, // required
slug: String, // required, unique
status: Boolean, // default: true
position: Number, // default: 0
createdAt: Date,
updatedAt: Date
}
// Index: { slug: 1, position: 1 }
{
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 }
{
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 }
{
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
}
{
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 }
{
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 }
{
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
}
{
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
}
]
}
{
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)
Push to GitHub
Push the project to a GitHub, GitLab, or Bitbucket repository.
Import in Vercel
Go to vercel.com → New Project → import your repository.
Add Environment Variables
Project Settings → Environment Variables → add MONGODB_URI, NEXTAUTH_SECRET, NEXTAUTH_URL, NEXT_PUBLIC_BASE_URL, AUTH_TRUST_HOST=true.
Deploy
Click Deploy. Subsequent pushes to the main branch trigger automatic deployments.
Self-Hosted
npm run build npm start
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
- Create a Mongoose model in
model/YourModel.ts - Add API routes in
app/api/admin/your-resource/route.ts - Create server actions in
actions/your-resource/ - 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:
- A clear description of the issue
- Steps to reproduce
- Error messages from the browser console or terminal
- Your Node.js version:
node -v
Common Issues
| Problem | Solution |
|---|---|
| Image upload fails | Verify Cloudinary credentials are saved in Admin → Settings → Cloudinary |
| Database connection error | Check MongoDB Atlas network access and confirm MONGODB_URI is correct |
| Admin login fails after deploy | Ensure NEXTAUTH_SECRET and NEXTAUTH_URL are set in production environment variables |
| Images not displaying | Check that the Cloudinary secureUrlBase and folder fields match your account |
Cafe Shop Template · Documentation v1.0 · Built with Next.js & MongoDB