A production-ready, secure booking system evolving into a modular Business Management Suite. Built with HTML5, Vanilla JavaScript (ES Modules), Tailwind CSS, Firebase Authentication, and Firestore.
Designed for easy cloning and rebranding for client projects. No frameworks (React, Vue, Angular, etc.).
- Architecture Overview
- Quick Start
- Firebase Setup (Step by Step)
- Configuration
- Git Workflow
- Netlify Deployment Guide
- Security Decisions
- GDPR Compliance
- Accessibility
- Extensibility Guide
- Project Structure
booking-system/
├── public/ # Deployed to Netlify
│ ├── index.html # Hero landing page (new) — link to booking.html
│ ├── booking.html # Public booking form (moved from index.html)
│ ├── admin.html # Admin dashboard
│ ├── privacy.html # GDPR privacy policy
│ └── _redirects # Netlify SPA redirects
├── src/
│ ├── css/
│ │ └── style.css # Custom styles + CSS vars
│ ├── js/
│ │ ├── config.js # Firebase + branding config
│ │ ├── validation.js # Input validation + sanitisation
│ │ ├── firestore.js # Firestore CRUD abstraction
│ │ ├── auth.js # Firebase Auth (admin only)
│ │ ├── booking.js # Public booking form logic
│ │ ├── admin.js # Admin dashboard logic
│ │ ├── ui.js # Shared UI helpers
│ │ └── components/ # Reusable UI component library
│ │ ├── index.js # Barrel export
│ │ ├── modal.js # Modal dialog with focus trapping
│ │ ├── confirm-dialog.js # Promise-based confirmation dialog
│ │ ├── toast.js # Stackable toast notifications
│ │ ├── table.js # Data table with search/sort/pagination
│ │ ├── status-badge.js # Status badge renderer
│ │ ├── loading-spinner.js # Overlay and inline spinners
│ │ └── currency-formatter.js # Locale-aware currency formatting
├── firebase/
│ ├── firestore.rules # Production security rules (7 collections)
│ └── firestore.indexes.json # Composite indexes
├── netlify/
│ └── netlify.toml # Deployment config
├── .env.example # Environment variable template
├── .gitignore
└── package.json
| Collection | Purpose | Access |
|---|---|---|
bookings |
Customer booking requests | Public create, admin CRUD |
customers |
Customer profiles (created from accepted bookings) | Admin only |
quotes |
Quotes with line items, VAT, totals | Admin only |
invoices |
Invoices with payment tracking | Admin only |
staff |
Staff profiles | Admin only |
rota |
Shift scheduling and holidays | Admin only |
settings |
Global business configuration | Admin only |
- Never trust client input — validation runs on client AND Firestore rules
- No
innerHTML— all user data rendered viatextContent(prevents XSS) - Least privilege — public = create only, admin = read/update/delete
- CSP headers — Content Security Policy via Netlify
- No secrets in Git — Firebase config is public, but admin UID and API keys are documented
git clone https://github.com/YOUR_USERNAME/booking-system.git
cd booking-system2. Set up Firebase (see Firebase Setup)
Edit src/js/config.js with your Firebase project details and admin UID.
Just open public/index.html in your browser, or run:
npx serve public- Go to console.firebase.google.com
- Click "Add project"
- Name it (e.g.,
booking-system-client1) - Disable Google Analytics (optional)
- Click "Create project"
- In the Firebase console, go to Authentication → Sign-in method
- Click "Email/Password" → Enable → Save
- Go to the Users tab
- Click "Add user"
- Enter your admin email and a strong password
- IMPORTANT: After creating the user, copy their User UID (you'll need this for config.js and Firestore rules)
- Go to Firestore Database → Create database
- Choose a location (e.g.,
eur3for Europe) - Choose "Start in test mode" (we'll update rules later)
- Click "Create"
- Go to Project Settings (⚙️ icon) → General
- Under "Your apps", click "Circular Button </>" → "Web"
- Register the app (nickname:
booking-system) - Copy the
firebaseConfigobject — you'll need these values
- Go to Firestore → database → Rules
- Delete the existing rules
- Copy the contents of
firebase/firestore.rules - REPLACE
YOUR_ADMIN_UID_HEREwith your actual admin UID (from Step 2) - Click "Publish"
Open src/js/config.js and replace all the YOUR_* placeholders:
export const firebaseConfig = {
apiKey: 'YOUR_API_KEY',
authDomain: 'YOUR_PROJECT.firebaseapp.com',
projectId: 'YOUR_PROJECT_ID',
storageBucket: 'YOUR_PROJECT.appspot.com',
messagingSenderId: 'YOUR_SENDER_ID',
appId: 'YOUR_APP_ID',
};
export const ADMIN_UID = 'YOUR_ADMIN_UID';Edit the business object in config.js:
export const business = {
name: 'Your Business Name',
tagline: 'Your tagline here',
email: 'contact@yourbusiness.com',
phone: '+44 1234 567890',
address: 'Your address',
// ... opening hours, etc.
};For booking notifications, sign up at EmailJS (free tier):
- Create an EmailJS account
- Add an email service (Gmail, Outlook, etc.)
- Create an email template with variables:
{{name}},{{email}},{{phone}},{{date}},{{time}},{{notes}},{{submittedAt}} - Get your Service ID, Template ID, and Public Key
- Update these in
config.jsunderemailConfig
main ← Stable source of truth
└── netlify ← Production deployment branch (deployed to Netlify)
└── business-suite-v2 ← Active development feature branch
Important: All development for the Business Management Suite v2 happens exclusively on the business-suite-v2 branch. The main and netlify branches remain untouched until the feature set is complete and ready for production.
# 1. Ensure you're on the development branch
git checkout business-suite-v2
# 2. Make your changes, then build and test locally
npm run build
# 3. Stage and commit
git add .
git commit -m "feat: description of your change"
# 4. Push to remote
git push -u origin business-suite-v2| Type | Purpose | Example |
|---|---|---|
feat: |
New feature | feat: add phone number validation |
fix: |
Bug fix | fix: correct date validation for leap years |
chore: |
Maintenance | chore: update firebase config |
docs: |
Documentation | docs: add deployment guide |
security: |
Security fix | security: sanitise user input in admin.js |
refactor: |
Code restructuring | refactor: extract validation module |
- Create a new repository on GitHub (do NOT initialise with README)
- Push your code:
git remote add origin https://github.com/YOUR_USERNAME/booking-system.git
git branch -M main
git push -u origin main- Go to app.netlify.com
- Click "Add new site" → "Import an existing project"
- Click "Deploy with GitHub"
- Authorise Netlify to access your GitHub account
- Search for your
booking-systemrepository - Click on it
On the configuration page:
- Branch to deploy:
main - Base directory: Leave blank
- Build command: Leave blank (this is a static site)
- Publish directory:
public(this is critical — must match) - Click "Deploy site"
- After deployment, go to Site settings → Environment variables
- Click "Add environment variable"
- Add each variable (or set them in
src/js/config.jsdirectly for simplicity)
If you're using config.js with hardcoded values (simpler), you can skip this step.
- Go to Site settings → Domain management
- Click "Add custom domain"
- Follow the DNS configuration steps
- Netlify will give you a URL like
https://random-name-123456.netlify.app - Open this URL in your browser
- You should see the Booking-System page with the purple gradient header
- Test the booking form
- Visit
/admin— you should see the login page
- If you get a blank page: Check the browser console for errors. Most likely the Firebase config values in
config.jsneed updating. - If Firestore permission errors: Make sure you published the Firestore rules (Firebase Console → Firestore → Rules) with your admin UID.
- If login doesn't work: Verify you created the user in Firebase Authentication → Users tab.
| Problem | Likely Fix |
|---|---|
| Blank page | Check console for JS errors; update Firebase config |
| "Permission denied" | Update Firestore rules with your admin UID |
| Can't log in | Create admin user in Firebase Auth → Users |
| Forms not submitting | Check Firestore rules allow create |
| 404 on admin page | _redirects file missing from public/ folder |
| Styles broken | Internet access required for Tailwind CDN |
| Decision | Rationale |
|---|---|
No innerHTML |
Prevents all forms of XSS (stored, reflected, DOM-based) |
| Firestore rules enforce types | Server-side validation even if client is compromised |
| Admin identified by UID | Simple, no custom claims setup needed |
| CSP headers | Blocks inline scripts, restricts external resources |
| Consent timestamped | Audit trail for GDPR compliance |
| Data retention expiry | Automated identification of records for GDPR deletion |
| No secrets in client code | Firebase config is intentionally public; auth secrets stay server-side |
| EmailJS for notifications | Free tier, no SMTP credentials exposed in client |
This system includes the following GDPR features:
- ✅ Consent checkbox with mandatory agreement before submission
- ✅ Consent timestamp stored with each booking
- ✅ Privacy notice beside the booking form
- ✅ Privacy Policy page (
/privacy.html) with full data processing information - ✅ Data retention expiry (12 months) stored in each document
- ✅
getExpiredBookings()function to identify records due for deletion - ✅ Admin deletion capability for right-to-erasure requests
- ✅ No unnecessary personal data stored (no IP addresses, no cookies)
- ✅ Data stored in EU region (Firestore location setting)
- User submits booking with explicit consent
- Consent timestamp and 12-month retention expiry stored
- Admin can view, manage, and delete data
- After 12 months, records flagged for deletion via
getExpiredBookings()
This template follows WCAG 2.2 AA guidelines:
- ✅ Semantic HTML (
<nav>,<main>,<section>,<form>,<table>) - ✅ ARIA labels and descriptions
- ✅ Keyboard navigation with visible focus indicators
- ✅ Skip-to-content link
- ✅ Screen reader friendly error messages (
aria-live="polite") - ✅ High contrast colour combinations
- ✅ Responsive design (no horizontal scroll)
- ✅ Error messages associated with inputs via
aria-describedby
The modular structure makes it easy to add features without major refactoring.
- Create
src/js/payment.js - Import Stripe/PayPal SDK in
index.html - Call payment function after booking submission in
booking.js
- Create
src/js/calendar.js - Use the Google Calendar API
- Add a "Add to Calendar" button in the admin booking details modal
- Use Twilio or a similar service
- Create a Firebase Cloud Function triggered on booking create
- Send SMS with appointment details
- Add a
stafffield to the booking document - Add a staff selection dropdown to the booking form
- Filter available times by staff member's schedule
- Add a
servicefield to the booking document - Create a services collection in Firestore
- Add a service selection step to the booking form
- Vary slot duration based on selected service
- Create a
settingscollection in Firestore - Store opening hours and holiday dates
- Fetch and apply in the booking form's date/time generation
- Enable additional Firebase Auth providers (Google, etc.)
- Create a
customerscollection linked to auth UID - Pre-fill booking form with customer data
The landing page (public/index.html) includes multiple image placeholders marked with <!-- IMAGE PLACEHOLDER --> comments. Create an images/ folder inside public/ to store your media.
public/
├── images/
│ ├── hero-bg.jpg # Hero background (recommended: 1920x1080)
│ ├── service-consultation.jpg
│ ├── service-appointments.jpg
│ └── service-support.jpg
├── index.html # Landing page (hero + service cards)
├── booking.html # Booking form
├── admin.html # Admin dashboard
└── privacy.html # Privacy policy
Location in index.html |
Comment tag | What to do |
|---|---|---|
| Hero section | <!-- IMAGE PLACEHOLDER: Replace the gradient above with a background image --> |
Replace style="background: var(--accent-gradient);" with a background image URL. See the example comment directly above. |
| Service Card 1 (Consultation) | <!-- IMAGE PLACEHOLDER: Replace the gradient below with a service image --> |
Replace the <div class="h-48" style="..."> with an <img> tag (example commented in the code) |
| Service Card 2 (Appointments) | Same as above | Same as above |
| Service Card 3 (Follow-Up Support) | Same as above | Same as above |
Replace the <section> style attribute with:
<section style="background: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url('images/hero-bg.jpg'); background-size: cover; background-position: center;" aria-label="Hero banner">Replace each gradient <div> with:
<img src="images/service-consultation.jpg" alt="Consultation service" class="w-full h-48 object-cover">booking-system/
├── .env.example
├── .gitignore
├── package.json
├── README.md
├── firebase/
│ ├── firestore.rules
│ └── firestore.indexes.json
├── netlify/
│ └── netlify.toml
├── scripts/
│ └── build.js
├── public/
│ ├── index.html
│ ├── admin.html
│ ├── privacy.html
│ └── _redirects
└── src/
├── css/
│ └── style.css
└── js/
├── config.js
├── validation.js
├── firestore.js
├── auth.js
├── booking.js
├── admin.js
├── ui.js
└── components/
├── index.js
├── modal.js
├── confirm-dialog.js
├── toast.js
├── table.js
├── status-badge.js
├── loading-spinner.js
└── currency-formatter.js
MIT — free for commercial and personal use.