Back to Projects

WanderStay

Next.jsTypeScriptReactPrismaMongoDBNextAuth.jsZustandTailwind CSSLeafletCloudinaryReact Hook FormReact Date RangeAxiosbcrypt
IMAGE — Home page with category bar (Beach, Modern, Pool, etc.) and property listing grid
IMAGE — Search modal step 1 — country selection dropdown with interactive Leaflet map
IMAGE — Search modal step 2 — date range picker calendar for check-in/check-out
IMAGE — Search modal step 3 — guest, room, and bathroom counter inputs
IMAGE — Listing detail page with hero image, property info, map, and reservation sidebar
IMAGE — Reservation calendar on listing detail showing disabled (booked) dates and total price
IMAGE — Rent your home modal step 1 — category selection grid (15 categories with icons)
IMAGE — Rent your home modal step 4 — Cloudinary image upload with preview
IMAGE — Rent your home modal step 6 — price per night input
IMAGE — My Trips page showing user's reservations with cancel button
IMAGE — My Properties page showing user's listed properties with delete button
IMAGE — Favorites page showing heart-toggled favorite listings
IMAGE — Login modal with email/password and Google OAuth button
IMAGE — User menu dropdown showing navigation links and sign out
IMAGE — Mobile responsive view with adapted grid and navbar
VIDEO
VIDEO
VIDEO
VIDEO
VIDEO
VIDEO
VIDEO
VIDEO
VIDEO
VIDEO
Home & Listings1 / 25

Overview

A full-stack Airbnb-inspired accommodation platform where users can list properties, search with advanced filters (location, dates, guests), make reservations with date-range calendars, manage favorites, and explore locations on interactive Leaflet maps — built with Next.js 13, Prisma with MongoDB, NextAuth.js, and Cloudinary.

Key Features

Search & Discovery
  • Multi-step search modal with location, date range, and guest/room/bathroom filters
  • 15 property categories (Beach, Modern, Pool, Castle, etc.) with icon-based filtering
  • 6-step property listing creation modal with category, location, details, image, and pricing
  • Country selection with world-countries data, flags, and map preview
  • Empty state components with contextual messaging and reset filters
Listings & Maps
  • Interactive Leaflet maps showing property locations with dynamic centering
  • Cloudinary image uploads with preview for property photos
  • My Reservations page — view and cancel guest bookings as a property host
  • My Properties page — manage and delete your own listings
Reservations & Trips
  • Date range calendar with disabled dates for already-booked periods
  • Reservation system with automatic total price calculation based on nights
  • My Trips page — view and cancel reservations as a guest
Auth & Favorites
  • Favorites system with heart toggle stored as ObjectId array on User document
  • Authentication with email/password (bcrypt) and Google OAuth via NextAuth.js
  • Responsive grid layout adapting from mobile to desktop

Architecture Overview

How the system is structured from frontend to external services.

01

Frontend

Next.js 13React 18Tailwind CSSLeaflet MapsReact Hook FormReact Date Range

Server-rendered pages with client-side interactivity. Multi-step modals (search + rent) managed via Zustand. Interactive Leaflet maps for location selection and display. Date range calendar with booked-date exclusion.

02

Backend & API

Next.js API RoutesNextAuth.jsbcryptAxios

RESTful API routes for listings, reservations, and favorites CRUD. NextAuth.js handles credentials and Google OAuth with JWT sessions. Server actions fetch data with complex Prisma queries including date-range overlap detection.

03

Database

Prisma ORMMongoDB

MongoDB document store with Prisma ORM. Four core models: User, Listing, Reservation, Account. Favorites stored as ObjectId array on User document (no join table). Date overlap queries use Prisma NOT + OR conditions.

04

External Services

CloudinaryGoogle OAuthOpenStreetMap

Cloudinary for property image uploads with next-cloudinary widget. Google OAuth via NextAuth provider. OpenStreetMap tiles for Leaflet map rendering with country-based centering.

Data Model

Core entities and how they relate to each other.

User

Has many Accounts, Listings, and Reservations

User accounts with credentials and OAuth support

id (ObjectId)nameemail (unique)emailVerified (DateTime)hashedPasswordimagefavoriteIds (String[] @db.ObjectId)createdAtupdatedAt

Account

Belongs to User

OAuth provider links (Google) managed by NextAuth Prisma Adapter

iduserId → UsertypeproviderproviderAccountIdaccess_tokenrefresh_token

Listing

Belongs to User (owner). Has many Reservations

Property listings with location, pricing, and category

id (ObjectId)titledescriptionimageSrc (Cloudinary URL)categoryroomCountbathroomCountguestCountlocationValue (country code)price (Int)userId → User

Reservation

Belongs to User (guest) and Listing. Join between guests and properties

Guest bookings with date range and total price

id (ObjectId)userId → User (guest)listingId → ListingstartDateendDatetotalPrice (Int)createdAt

Key Flows

Step-by-step walkthrough of the main user and system flows.

Property Listing Creation (6-Step Modal)

  1. 1User clicks 'Airbnb your home' in the navbar menu
  2. 2RentModal opens with step 1: select category from 15 options (Beach, Modern, Pool, etc.)
  3. 3Step 2: select country from dropdown — Leaflet map centers on selected location
  4. 4Step 3: set guest count, room count, bathroom count with counter inputs
  5. 5Step 4: upload property image via Cloudinary CldUploadWidget → returns secure_url
  6. 6Step 5: enter title and description for the listing
  7. 7Step 6: set price per night → submit POSTs to /api/listings with all form data
  8. 8Server creates Listing document in MongoDB with userId from session → redirects to home

Search & Filter Flow

  1. 1User clicks the search bar in the navbar to open SearchModal
  2. 2Step 1: select location via CountrySelect dropdown — map updates to show country
  3. 3Step 2: pick date range (check-in / check-out) using react-date-range calendar
  4. 4Step 3: set minimum guests, rooms, and bathrooms with counter inputs
  5. 5Modal builds URL query string with query-string library and navigates
  6. 6Home page getListings() server action reads searchParams and builds Prisma query
  7. 7Date filter uses NOT + OR to exclude listings with overlapping reservations
  8. 8Results render as ListingCard grid — empty state shown if no matches

Reservation Booking Flow

  1. 1Guest navigates to listing detail page (/listings/[listingId])
  2. 2Existing reservations fetched and their date ranges marked as disabled on the calendar
  3. 3Guest selects check-in and check-out dates on the calendar widget
  4. 4Total price auto-calculates: (number of nights) × (price per night) using date-fns differenceInCalendarDays
  5. 5Guest clicks 'Reserve' → POST /api/reservations with listingId, startDate, endDate, totalPrice
  6. 6Server creates Reservation document linked to guest userId and listingId
  7. 7Guest redirected to /trips page showing their reservation with cancel option

Challenges I Solved

01

Date Range Overlap Detection for Availability

The Problem

When searching for available listings, you need to exclude properties that already have reservations overlapping with the requested dates. A simple date comparison isn't enough — you need to detect all types of overlap (partial, full, enclosing).

How I Solved It

Prisma's NOT + OR query filters out listings with conflicting reservations. The overlap condition checks if any existing reservation overlaps the requested date range (reservation.startDate <= requestedEndDate AND reservation.endDate >= requestedStartDate). If true, that listing is excluded from results.

02

Multi-Step Modal State with React Hook Form

The Problem

The Rent modal has 6 steps (category → location → details → image → description → price), each building on previous selections. Managing form state across steps while keeping validation and allowing back navigation is complex.

How I Solved It

Used a single React Hook Form instance with a step counter. Each step conditionally renders its inputs while the form state persists across all steps. Used form.setValue() for programmatic updates (e.g., when selecting a category or uploading an image) and form.watch() to read values reactively for the map and preview. Back/Next buttons increment/decrement the step counter, and the form only submits on the final step.

03

Leaflet Map SSR Compatibility in Next.js

The Problem

Leaflet depends on the window object for map rendering, which doesn't exist during server-side rendering in Next.js. Importing Leaflet in a server-rendered component causes 'window is not defined' errors.

How I Solved It

Used Next.js dynamic import with { ssr: false } to load the Map component only on the client side. The map component is wrapped in a useMemo that re-renders when the center coordinates change (when the user selects a new country). Custom marker icons are configured with explicit iconUrl paths to fix the default icon loading issue in Next.js's module bundling.

04

Dual Authorization for Reservation Cancellation

The Problem

Both the guest who made the reservation AND the property owner should be able to cancel a reservation, but they access it from different pages (/trips vs /reservations). The API needs to verify the requester is one of these two authorized parties.

How I Solved It

The DELETE route uses Prisma's findUnique with an OR condition — it only returns the reservation if the requesting user is either the listing owner (listing.userId) or the guest who made the reservation (reservation.userId). If findUnique returns null, the route returns 400.

Summary

WanderStay is a full-stack Airbnb-inspired platform built with Next.js 13, Prisma, and MongoDB. It features a multi-step search modal with country selection (world-countries data + Leaflet maps), date range filtering with overlap detection to ensure availability, and guest/room/bathroom counters. Property owners can list homes through a 6-step modal covering category, location, details, Cloudinary image upload, and pricing. The reservation system calculates total price from night count, disables booked dates on the calendar, and supports cancellation by both guests and hosts. Favorites use MongoDB's document model with an ObjectId array on the User document. Authentication combines email/password (bcrypt) with Google OAuth via NextAuth.js using JWT sessions.