System Design
The system is split across two repositories. The front end is a React 18 single-page application built with Vite 4 and TypeScript 5, styled with Tailwind CSS and PostCSS/Autoprefixer. Routing is handled by react-router-dom v6, iconography by lucide-react, and destination and package carousels by Swiper. It is a static build (tsc && vite build) deployed to Vercel — a vercel.json handles SPA rewrites, and .env.production carries the API base URL so the same bundle can point at a local or deployed backend. Keeping the front end fully static means the marketing surface stays fast and cheap to serve, with all dynamic content arriving over HTTP from the Django API.
The backend is a Django 5.2 project organised into focused apps that map cleanly onto the domain: destinations for the catalogue, blogs for editorial content, book_services for the booking and enquiry models, apis as the HTTP surface that the SPA consumes, and two dedicated integration apps — google_calender and google_sheet — that own all outbound traffic to Google. Isolating the integrations in their own apps rather than scattering API calls through view code means the Google credential handling, client construction and error paths live in one place, and the booking models stay ignorant of how they are ultimately persisted downstream. django-cors-headers allows the Vercel-hosted origin to call the API; whitenoise serves the admin and template static assets; Pillow backs image fields on destinations and blog posts.
Google access is server-to-server via a service account rather than per-user OAuth, so no staff member has to hold a session for a booking to sync. The stack uses google-api-python-client with google-auth and google-auth-oauthlib for the Calendar API, and gspread as a higher-level client for Sheets — a sensible split, since Sheets work is row append and read, while Calendar needs the full resource model. Credentials live in a credentials/ directory, with configuration loaded from the environment via python-dotenv. Deployment is containerised: a Dockerfile and docker-compose.yml bring up the app behind an nginx reverse proxy, entrypoint.sh runs migrations and collects static files on boot, gunicorn serves WSGI, and a .gitlab-ci.yml drives the pipeline. Operational detail is documented in-repo (RUN_MIGRATIONS.md, HERO_SECTION_API.md), and one-off management scripts — seed_enquiry.py, update_enquiry_colors.py — seed and backfill the enquiry data used by the admin.