Skip to content

webimpactuw/ortegas-cleaning

Repository files navigation

Ortega's House Cleaning

A production-style marketing and booking website for Ortega's House Cleaning, a family-owned house cleaning business serving the Seattle/Tacoma area.

This project was built through Web Impact, a University of Washington club that partners with small local businesses to create free websites while giving club members real client project experience. Ortega's Cleaning did not have an established digital presence, so this site gives the business a public home for services, trust-building content, reviews, contact information, and booking requests.

The live product is more than a static marketing site. Customers can submit booking requests, receive a booking ID by email, use that ID to view their booking later, cancel a booking, and send quote/contact messages. The owner receives email notifications for new bookings, cancellations, and contact messages.

Table Of Contents

What The Site Does

The website has four main responsibilities:

  1. Present Ortega's House Cleaning as a trustworthy local cleaning business.
  2. Explain service offerings, service areas, company background, reviews, and gallery photos.
  3. Accept booking requests and store them in MongoDB.
  4. Send transactional email notifications through EmailJS.

Core customer flows:

  • A visitor lands on the homepage, reviews services and areas served, then clicks a booking CTA.
  • A customer submits the booking form on /book.
  • The booking is saved in MongoDB.
  • The customer receives a confirmation email containing the booking ID.
  • The owner receives a new booking email with the full booking details.
  • The customer can return to /book, enter their booking ID, and view the booking page.
  • The customer can cancel from the booking page.
  • Cancellation updates the MongoDB document and sends cancellation emails to both customer and owner.
  • A visitor can submit the contact form on /contact.
  • Contact messages are emailed to the owner, and the customer receives a confirmation email.

Tech Stack

This is a Next.js App Router project.

Category Technology
Framework Next.js 16.1.1
UI React 19.2.3, React DOM 19.2.3
Styling Tailwind CSS v4, global CSS variables in app/globals.css
Animation Framer Motion
Database MongoDB with Mongoose
Email EmailJS Node SDK
CMS Sanity Studio and next-sanity
Maps Leaflet and React Leaflet
Package manager npm, with package-lock.json committed
Linting ESLint with Next core web vitals config

There is currently no automated test script.

Project Structure

.
|-- app/
|   |-- api/
|   |   |-- book/
|   |   |   |-- route.js
|   |   |   `-- [id]/
|   |   |       |-- route.js
|   |   |       `-- cancel/route.js
|   |   `-- contact/route.js
|   |-- about/page.js
|   |-- book/
|   |   |-- page.js
|   |   `-- [id]/
|   |       |-- page.js
|   |       `-- cancel/page.js
|   |-- contact/page.js
|   |-- gallery/page.js
|   |-- reviews/page.js
|   |-- services/page.js
|   |-- studio/[[...tool]]/page.jsx
|   |-- layout.js
|   |-- page.js
|   `-- globals.css
|-- components/
|   |-- booking/
|   |-- buttons/
|   |-- contact/
|   |-- gallery/
|   `-- shared site components
|-- public/
|   |-- gallery/
|   `-- images and SVG assets
|-- sanity/
|   |-- lib/
|   |-- schemaTypes/
|   `-- desk.js
|-- sanity.config.js
|-- sanity.cli.js
|-- package.json
`-- README.md

Important organization notes:

  • app/ contains the App Router pages and API route handlers.
  • components/ contains reusable UI components, grouped by feature where useful.
  • components/booking/ contains the booking form, booking lookup box, layout wrappers, field wrapper, and terms content.
  • components/contact/ contains the quote/contact form, FAQ item, and contact banner.
  • components/gallery/ contains the carousel, gallery images, pips, arrows, statistics, and gallery section components.
  • app/lib/contactInfo.js centralizes owner contact environment variables for server-rendered components.
  • app/lib/email/sendEmail.js builds all booking, cancellation, and contact email HTML.
  • sanity/ contains the Sanity client, schema, image helpers, live helpers, and studio desk structure.

Route Architecture

Pages

Route File Purpose
/ app/page.js Homepage with navbar, hero, service preview, areas served, featured reviews, and CTA banner.
/about app/about/page.js Business story, 20+ years of service messaging, core values, and booking CTA.
/services app/services/page.js Residential, commercial, deep cleaning, recurring services, service areas, and value props.
/gallery app/gallery/page.js Photo carousel and gallery statistics using local images in public/gallery.
/contact app/contact/page.js Owner phone/email, quote form, and FAQ.
/reviews app/reviews/page.js Sanity-powered review list and Google Maps review link.
/book app/book/page.js Booking form, terms and conditions, and existing booking lookup.
/book/[id] app/book/[id]/page.js Server-rendered booking confirmation/details page for a MongoDB booking ID.
/book/[id]/cancel app/book/[id]/cancel/page.js Cancellation confirmation page.
/studio/[[...tool]] app/studio/[[...tool]]/page.jsx Embedded Sanity Studio.

API Routes

Route Method File Purpose
/api/book POST app/api/book/route.js Save a booking to MongoDB and email customer/owner.
/api/book/[id] GET app/api/book/[id]/route.js Return booking JSON if found and not cancelled.
/api/book/[id]/cancel POST app/api/book/[id]/cancel/route.js Mark a booking cancelled and email customer/owner.
/api/contact POST app/api/contact/route.js Validate a contact message and email customer/owner.

The current UI does not rely on GET /api/book/[id] for booking lookup. components/booking/BookingLookup.js navigates directly to /book/[id], and app/book/[id]/page.js queries MongoDB server-side.

Data And Email Flows

Booking Model

The booking schema is currently defined inline in each MongoDB-using route/page:

{
  name: String,
  email: String,
  phone: String,
  address: String,
  serviceLocation: String,
  frequency: String,
  status: String
}

Expected values from the booking form:

  • name: customer full name.
  • email: customer email address.
  • phone: customer phone number.
  • address: service address.
  • serviceLocation: "Home" or "Office".
  • frequency: "One Time", "Weekly", "Biweekly", "Monthly", "Move In", or "Move Out".
  • status: starts as "Active" and changes to "cancelled" when cancelled.

MongoDB creates the _id. That _id is the booking ID emailed to the customer.

Booking Submission

Main files:

  • components/booking/BookingForm.js
  • app/api/book/route.js
  • app/lib/email/sendEmail.js

Flow:

  1. The customer fills out name, phone, email, address, serviceLocation, and frequency.
  2. Client-side validation checks required fields, email format, and 10-digit phone number.
  3. The client posts the form JSON to POST /api/book.
  4. The API route connects to MongoDB using MONGO_URI.
  5. The API creates and saves a Booking document.
  6. sendCustomerEmail(booking, "booking") sends the customer confirmation email.
  7. sendOwnerEmail(booking, "booking") sends the owner notification email.
  8. The API returns { id, message: "Saved" } with status 201.
  9. The client redirects to /book/[id].

The customer email includes the booking ID, service location, frequency, and owner contact email. The owner email includes the booking ID, status, name, email, phone, address, service location, and frequency.

Booking Lookup

Main files:

  • components/booking/BookingLookup.js
  • app/book/[id]/page.js

Flow:

  1. The customer enters a booking ID on /book.
  2. The lookup component trims the ID and navigates to /book/[id].
  3. The booking page connects to MongoDB and runs Booking.findById(id).lean().
  4. If the booking does not exist or has status === "cancelled", the page renders a not-found/cancelled message.
  5. If the booking exists, the page shows the customer name, service location, frequency, preparation instructions, contact tag, and cancel button.

Booking Cancellation

Main files:

  • components/buttons/CancelBooking.js
  • app/api/book/[id]/cancel/route.js
  • app/book/[id]/cancel/page.js
  • app/lib/email/sendEmail.js

Flow:

  1. The customer opens the cancel modal from /book/[id].
  2. On confirmation, the client sends POST /api/book/[id]/cancel.
  3. The API connects to MongoDB and finds the booking by ID.
  4. If the booking does not exist, the API returns 404.
  5. If found, the API sets status to "cancelled" and saves.
  6. Cancellation emails are sent to the customer and owner.
  7. The client redirects to /book/[id]/cancel.

The customer cancellation email includes the booking ID and a warning to contact the business if they did not request the cancellation. The owner cancellation email includes the full booking details.

Contact Form

Main files:

  • components/contact/ContactForm.js
  • app/api/contact/route.js
  • app/lib/email/sendEmail.js

Flow:

  1. The visitor submits name, email, serviceType, and message.
  2. Client-side validation checks required fields and email format.
  3. The API trims fields and repeats required-field/email validation server-side.
  4. The owner receives a "New Contact Message" email with the submitted details.
  5. The customer receives a confirmation email.
  6. No contact messages are stored in MongoDB.

Email Implementation

app/lib/email/sendEmail.js uses @emailjs/nodejs and one dynamic EmailJS template. The template must support these variables:

Template Variable Meaning
to_email Recipient email address.
subject Email subject line.
reply_to Reply-to address.
html_message Full HTML email body generated by the app.

The email helper builds six types of messages:

  • Customer booking confirmation.
  • Owner booking notification.
  • Customer cancellation confirmation.
  • Owner cancellation notice.
  • Customer contact acknowledgement.
  • Owner contact message notification.

User-provided values are passed through escapeHtml() before being interpolated into email HTML.

Environment Variables

Create .env.local for local development. Environment files are intentionally ignored by .gitignore.

NEXT_PUBLIC_SANITY_PROJECT_ID=
NEXT_PUBLIC_SANITY_DATASET=
NEXT_PUBLIC_SANITY_API_VERSION=2026-04-01

MONGO_URI=

EMAILJS_SERVICE_ID=
EMAILJS_TEMPLATE_ID=
EMAILJS_PUBLIC_KEY=
EMAILJS_PRIVATE_KEY=

OWNER_EMAIL=
EMAILJS_OWNER_EMAIL=
OWNER_PHONE=
Variable Required Used By Notes
MONGO_URI Yes for booking features Booking API routes and booking detail page MongoDB connection string.
EMAILJS_SERVICE_ID Yes for email app/lib/email/sendEmail.js EmailJS service ID.
EMAILJS_TEMPLATE_ID Yes for email app/lib/email/sendEmail.js The dynamic template ID. The code explicitly throws if missing.
EMAILJS_PUBLIC_KEY Yes for email app/lib/email/sendEmail.js EmailJS public key.
EMAILJS_PRIVATE_KEY Yes for email app/lib/email/sendEmail.js EmailJS private key for server-side sending.
OWNER_EMAIL Yes for owner emails and display app/lib/contactInfo.js, email helper, footer/contact pages Preferred owner email variable.
EMAILJS_OWNER_EMAIL Optional fallback app/lib/contactInfo.js Used only if OWNER_EMAIL is not set.
OWNER_PHONE Yes for phone display app/lib/contactInfo.js, footer/contact pages Used for display and tel: links.
NEXT_PUBLIC_SANITY_PROJECT_ID Yes for reviews/studio Sanity config and client Public Sanity project ID.
NEXT_PUBLIC_SANITY_DATASET Yes for reviews/studio Sanity config and client Usually production.
NEXT_PUBLIC_SANITY_API_VERSION Optional Sanity config and client Defaults to 2026-04-01.

Never commit real credentials. If any credentials ever appear in source or commit history, rotate them before deploying or sharing the repository.

Local Development

Prerequisites

  • Node.js compatible with Next.js 16 and Sanity 5. Node 20.19+ or 22.12+ is recommended.
  • npm.
  • A MongoDB database.
  • An EmailJS account with a dynamic HTML template.
  • A Sanity project and dataset if you want reviews and Studio to work.

Install Dependencies

npm ci

Use npm ci because package-lock.json is committed.

Configure Environment

Create .env.local in the project root and fill in the variables from Environment Variables.

Run The Development Server

npm run dev

Open http://localhost:3000.

Sanity Studio is mounted at http://localhost:3000/studio.

Available Scripts

npm run dev      # Start the Next.js development server
npm run build    # Create a production build
npm run start    # Start the production server after building
npm run lint     # Run ESLint

There is no npm test script yet.

External Services

MongoDB

MongoDB stores bookings only. Contact form messages are not persisted.

To set up MongoDB:

  1. Create a MongoDB Atlas cluster or local MongoDB database.
  2. Create a database user with read/write access.
  3. Add the connection string to MONGO_URI.
  4. Make sure the deployment environment can reach the database.

The code does not specify a collection name. Mongoose will use the Booking model and its default collection naming.

EmailJS

EmailJS sends all customer and owner emails.

To set up EmailJS:

  1. Create an EmailJS service.
  2. Create one template that renders html_message as HTML.
  3. Add template variables for to_email, subject, reply_to, and html_message.
  4. Copy the service ID, template ID, public key, and private key into environment variables.
  5. Test booking, cancellation, and contact flows in development before deploying.

Sanity

Sanity stores customer reviews.

Important files:

  • sanity.config.js
  • sanity.cli.js
  • sanity/schemaTypes/reviewType.js
  • sanity/lib/client.js
  • app/reviews/page.js
  • components/WhatOurClientsSay.js

The review schema contains:

  • reviewer: required string.
  • reviewText: required text.
  • numStars: required integer from 1 to 5.
  • featuredOnHomepage: boolean; homepage shows up to 3 featured reviews.

The Studio runs inside the Next.js app at /studio.

Maps

The homepage service area map uses React Leaflet and OpenStreetMap tiles. Marker images are loaded from the Leaflet CDN at runtime. The map is dynamically imported because Leaflet depends on browser APIs and cannot render during server-side rendering.

The service area list is hard-coded in components/AreasWeServeSection.js.

Deployment

The repository has no custom deployment config such as vercel.json, Dockerfile, Netlify config, or GitHub Actions workflow. The simplest deployment target is Vercel because this is a Next.js project.

Production deployment needs:

  1. Install dependencies with npm.
  2. Run npm run build.
  3. Run npm run start or deploy through a Next-compatible hosting platform.
  4. Provide all required environment variables in the hosting provider.
  5. Confirm the host supports server-side route handlers, MongoDB outbound connections, and EmailJS outbound requests.

Recommended production checks:

npm run lint
npm run build

After deploy, manually test:

  • Homepage loads.
  • Navigation works on desktop and mobile.
  • /services, /gallery, /about, /contact, /reviews, and /book load.
  • Contact form sends owner and customer emails.
  • Booking form saves to MongoDB and sends owner/customer emails.
  • Booking ID opens /book/[id].
  • Cancellation updates the booking status and sends cancellation emails.
  • Sanity reviews load on homepage and /reviews.
  • /studio opens for authorized Sanity users.

Maintenance Notes

Content Updates

  • Business copy is mostly hard-coded in page/component files.
  • Services are defined in components/ServicesHomePage.js and components/ServicesPageCards.js.
  • Service areas are defined in components/AreasWeServeSection.js.
  • FAQs are defined in app/contact/page.js.
  • Terms and conditions are defined in components/booking/TermsConditions.js.
  • Gallery images are imported manually in components/gallery/GalleryMain.js; adding/removing images requires updating imports, numPips, and rendered GalleryImage elements.
  • Reviews are managed through Sanity Studio.
  • Owner email and phone should be changed through environment variables, not hard-coded.
  • Footer address currently uses placeholder text and should be updated before production use.

Code Organization

The booking schema and MongoDB connection helper are duplicated across:

  • app/api/book/route.js
  • app/api/book/[id]/route.js
  • app/api/book/[id]/cancel/route.js
  • app/book/[id]/page.js

A future cleanup should move the schema/model and connectDB() into shared server-only modules, such as:

  • app/lib/db/connect.js
  • app/lib/models/Booking.js

That would reduce drift and make schema changes safer.

Metadata And SEO

app/layout.js sets the title to Ortega's Cleaning, but the description is still the default create-next-app text. Update the metadata before public launch.

Suggested metadata:

export const metadata = {
  title: "Ortega's House Cleaning",
  description: "Family-owned residential and commercial cleaning services serving King County and Pierce County.",
};

Images

Gallery JPG files are stored in public/gallery. Several are large, so image compression may improve performance. Because the gallery imports local images through next/image, Next can optimize rendering, but smaller source assets will still help.

Styling

Global brand colors and theme tokens live in app/globals.css. Most layout/styling uses Tailwind utility classes directly in components.

Known Limitations And Risks

These are current implementation details that future maintainers should understand.

  • Booking validation is mostly client-side. POST /api/book trusts the request body and saves it directly.
  • The booking schema has no required fields, enums, indexes, timestamps, or data retention fields.
  • Email sending happens after the booking is saved. If saving succeeds but email fails, the API returns an error even though a booking exists.
  • Contact emails are not stored anywhere. If email delivery fails, the message is lost.
  • Anyone with a valid booking ID can view the booking detail page and cancel the booking.
  • GET /api/book/[id] returns the full booking document, including customer PII.
  • Cancellation is not idempotent. Repeated cancellation requests can re-send cancellation emails.
  • There is no rate limiting, CAPTCHA, spam prevention, CSRF protection, or authentication for form endpoints.
  • Invalid MongoDB ObjectId values are not consistently handled and may produce generic server errors.
  • There is no .env.example file.
  • There are no automated tests.
  • The project has no documented privacy policy, data retention policy, admin dashboard, or owner-side booking management UI.
  • Audit the source and git history for accidental credentials before making the repository public.

Recreating The Project From Scratch

To recreate this project from scratch, build these pieces in order:

  1. Create a Next.js App Router app with JavaScript, npm, ESLint, Tailwind CSS, and a root app/layout.js.
  2. Add a global layout that renders page content and a persistent footer.
  3. Create marketing pages for homepage, about, services, gallery, contact, reviews, and booking.
  4. Build reusable components for navbar, footer, service cards, CTAs, review cards, gallery carousel, contact form, booking form, booking lookup, and cancel modal.
  5. Add local image assets under public/ and gallery images under public/gallery/.
  6. Add MongoDB and Mongoose.
  7. Define a Booking model with customer contact fields, service fields, and status.
  8. Create POST /api/book to save a booking and return the MongoDB ID.
  9. Create /book/[id] to read a booking by ID and show a customer-friendly confirmation page.
  10. Create POST /api/book/[id]/cancel to mark a booking cancelled.
  11. Add EmailJS server-side sending with one dynamic HTML template.
  12. Send customer and owner emails for booking, cancellation, and contact flows.
  13. Create POST /api/contact with server-side validation and email notifications.
  14. Add Sanity, define a review schema, mount Studio at /studio, and fetch reviews in the homepage/reviews pages.
  15. Add React Leaflet for the areas-served map, using dynamic imports to avoid SSR issues.
  16. Add all required environment variables locally and in production.
  17. Run lint/build checks and manually test all user flows.

Handoff Summary

This project is a client-facing Web Impact website for a real local cleaning business. The main maintenance responsibility is keeping public business content accurate while protecting customer data in the booking flow. Before a long-term production launch, prioritize server-side booking validation, removal/rotation of any accidental secrets, shared MongoDB model utilities, better booking access controls, metadata updates, and a small automated test suite for the booking/contact APIs.

Releases

No releases published

Packages

 
 
 

Contributors