> ## Documentation Index
> Fetch the complete documentation index at: https://docs.theroyalglow.in/llms.txt
> Use this file to discover all available pages before exploring further.

# FAQ

> Quick answers to common questions about the Royal Glow platform — for developers, operators, and business stakeholders.

# Frequently Asked Questions

<Info>
  Answers are grouped by audience. Click any question to expand it.
</Info>

## For everyone

<AccordionGroup>
  <Accordion title="What is this platform?">
    It's the complete digital operations system for Royal Glow Salon & Spa — covering
    the customer website, online booking, admin portal, billing, memberships, loyalty
    programme, CRM, and all the automation behind the scenes.
  </Accordion>

  <Accordion title="Who built this?">
    Single developer project. The entire platform — 38 database tables, 104 pages, 35
    APIs, 19 background jobs — was built by one developer using AI-assisted
    development tools.
  </Accordion>

  <Accordion title="How much does it cost to run?">
    ₹0/month at launch. Every external service (Cloudflare, Neon, Ably, Upstash,
    Resend, PostHog, Sentry, BetterStack) runs on a free tier generous enough for a
    salon at this scale.
  </Accordion>

  <Accordion title="What happens when the business grows and free tiers aren't enough?">
    Each service has a clear upgrade path. The first upgrades needed would be Sentry
    ($26/mo) and BetterStack ($24/mo) — triggered by growth, not required at launch.
  </Accordion>
</AccordionGroup>

## For business stakeholders

<AccordionGroup>
  <Accordion title="How do customers book appointments?">
    Customers click "Book Now" on the homepage, which opens a 4-step booking dialog.
    They can also book via the Google Maps listing (which uses a special link that
    tracks the source) or by scanning the in-store QR code.
  </Accordion>

  <Accordion title="What happens after a customer books?">
    The booking is created with status "Pending". The receptionist sees it in the
    admin portal, assigns a staff member, and confirms it. The customer gets a
    confirmation notification. 15 minutes before the appointment, they get a reminder.
  </Accordion>

  <Accordion title="How does the SPA membership work?">
    A customer buys a membership (Silver: 8 hrs/₹10k, Gold: 15 hrs/₹15k, Platinum:
    custom). Each SPA session deducts hours from their balance. The membership expires
    after 90 days regardless of remaining hours. Reminders are sent at 30, 7, and 1
    day before expiry.
  </Accordion>

  <Accordion title="How does the loyalty programme work?">
    Customers earn 1 gem per ₹100 spent on services (floor, not rounded). Gems expire
    after 365 days. They can be redeemed against specific catalogue services — not as
    a general discount. Gems cannot be combined with offers.
  </Accordion>

  <Accordion title="How are Meta/Instagram ad leads handled?">
    Customers who click a Meta ad land on `/book` — a simple 3-field form (name,
    phone, service interest). This creates a lead in the CRM. The receptionist follows
    up via WhatsApp (AiSensy). When the lead books, the lead is marked as converted.
  </Accordion>

  <Accordion title="How are invoices generated?">
    When a receptionist marks a booking as completed, the system automatically
    generates a GST-compliant invoice (18% inclusive, SAC 999721), creates a PDF,
    uploads it to Cloudflare R2, and emails it to the customer via Resend — all within
    the same API request.
  </Accordion>

  <Accordion title="Can the platform handle multiple branches?">
    Yes. The database is designed for multi-branch from day one. Each booking,
    invoice, and staff member is scoped to a branch. A second branch is already in the
    seed data with status "Opens Soon".
  </Accordion>
</AccordionGroup>

## For developers

<AccordionGroup>
  <Accordion title="How do I run the project locally?">
    See [Getting Started](/docs/getting-started). Short version: `bun install`, copy
    `.env.example` to `.env.local`, fill in the keys, `bun run dev --filter=@rgss/web`.
  </Accordion>

  <Accordion title="Do I need every environment variable to run locally?">
    No. Every external integration is a guarded extension point — with a key absent it
    no-ops and logs. Set `SKIP_ENV_VALIDATION=1` to bypass build-time validation while
    getting started. You only need `DATABASE_URL` and `BETTER_AUTH_SECRET` for core
    functionality.
  </Accordion>

  <Accordion title="Why is money stored as integers?">
    To avoid floating-point errors. ₹1,000.00 is stored as `100000` (paise). All
    calculations happen in integers. Rounding only happens at display time.
  </Accordion>

  <Accordion title="Why are all primary keys text (not auto-increment integers)?">
    `nanoid()` generates random text IDs that prevent enumeration attacks (a user
    can't guess `booking/1`, `booking/2`, etc.) and work across distributed systems
    without coordination.
  </Accordion>

  <Accordion title="Why does the booking dialog open over the homepage instead of navigating to /book?">
    `/book` is the Meta ad lead capture page — a distraction-free 3-field form for
    people who clicked an Instagram ad. The booking dialog is for customers already on
    the website. Mixing these two flows would confuse both audiences and break
    attribution tracking.
  </Accordion>

  <Accordion title="How do I add a new API route?">
    <Steps>
      <Step>Create the file in `apps/web/src/app/api/`</Step>
      <Step>Wrap the handler with `withErrorHandler`</Step>
      <Step>Validate input with a Zod schema from `packages/types/`</Step>
      <Step>Call business logic from `packages/business/`</Step>
      <Step>Return `apiSuccess(data)` or throw `AppError`</Step>
    </Steps>
  </Accordion>

  <Accordion title="How do I add a new background job?">
    <Steps>
      <Step>Create the route in `apps/admin/src/app/api/jobs/`</Step>
      <Step>Verify the QStash signature using `verifyQStashSignature`</Step>
      <Step>Do the work, ping the BetterStack heartbeat on success</Step>
      <Step>Schedule it in QStash (all jobs run as QStash HTTP routes)</Step>
      <Step>Add the heartbeat URL to the environment variables</Step>
    </Steps>
  </Accordion>

  <Accordion title="Why is there no packages/db import in packages/business?">
    `packages/business` can import from `packages/types` and `packages/errors`. What
    it cannot import is `packages/db`, any framework code, or UI components. This keeps
    business logic pure and testable.
  </Accordion>

  <Accordion title="How do I run just the unit tests?">
    `bun run test:unit` — runs Vitest excluding integration tests and E2E.
  </Accordion>

  <Accordion title="How do I check if my changes break the build?">
    `bun run typecheck && bun run lint && bun run test:unit` — the three gates that run
    on every PR to `dev`.
  </Accordion>

  <Accordion title="What's the difference between APP_ENV and NODE_ENV?">
    `NODE_ENV` is always `development` or `production` (Next.js convention). `APP_ENV`
    is `dev`, `test`, `pprd`, or `prod` — our four deployment environments. Use
    `APP_ENV` for environment-specific logic like data seeding guards.
  </Accordion>

  <Accordion title="How do I add a new database table?">
    <Steps>
      <Step>Add the schema in `packages/db/src/schema/`</Step>
      <Step>Add relations in `packages/db/src/schema/relations/`</Step>
      <Step>Export from `packages/db/src/schema/index.ts`</Step>
      <Step>Run `bunx drizzle-kit generate` to create the migration</Step>
      <Step>Apply with `bunx drizzle-kit migrate`, then add seed data if needed</Step>
    </Steps>
  </Accordion>

  <Accordion title="Why does the docs site use Mintlify instead of a self-hosted docs app?">
    Mintlify hosts the site itself at no cost and ships built-in search, so we run no
    infrastructure of our own for the docs. The previous self-hosted docs app was
    removed to cut infrastructure spend. Content stays as MDX in the repo, with
    navigation and redirects in `docs/docs.json`.
  </Accordion>
</AccordionGroup>

## For operations

<AccordionGroup>
  <Accordion title="How do I know if the site is down?">
    BetterStack monitors all key endpoints and sends alerts via Slack and email. The
    public status page is at `status.theroyalglow.in`.
  </Accordion>

  <Accordion title="How do I know if a scheduled job failed?">
    BetterStack heartbeat monitors alert if a job doesn't ping within its expected
    window. Check the BetterStack dashboard for heartbeat status.
  </Accordion>

  <Accordion title="How do I roll back a bad deployment?">
    Cloudflare Workers: run `wrangler rollback` (or open Workers & Pages →
    Deployments) and promote the last known-good deployment. Takes \~30 seconds.
  </Accordion>

  <Accordion title="How do I restore the database from backup?">
    Weekly backups are stored in Cloudflare R2 (`backups/weekly/`). Download the
    backup, restore to a new Neon branch, verify the data, then swap the connection
    string. See [Operations](/docs/operations) for the full procedure.
  </Accordion>

  <Accordion title="How do I add a new staff member?">
    Sign in to the admin portal at `/users` (requires Owner role). Create the user and
    assign the appropriate role. The staff member signs in with their Google account.
  </Accordion>

  <Accordion title="How do I update service prices?">
    Sign in to the admin portal at `/services` (requires Manager role). Edit the
    service price. The new price applies to all future bookings. Existing bookings and
    invoices are unaffected (prices are snapshotted at booking time).
  </Accordion>
</AccordionGroup>


## Related topics

- [SEO & AI Visibility](/content/docs/seo.md)
- [Pages & Routes](/content/docs/pages-and-routes.md)
- [Overview](/content/docs/index.md)
- [API Reference](/content/docs/api-reference.md)
- [Business Overview](/content/docs/product/business-overview.md)
