# 🎡 Spin to Win — Café Cream Bar

A mobile-first **spin-to-win reward flow** built to match the provided designs:
landing → lead capture → OTP verification → animated wheel spin → reward reveal → confirmation.

- **Frontend** — Next.js (App Router) + React, CSS Modules, `next/font`. Reusable component library.
  - Public spin page at `/` and an **admin panel at `/admin`**.
- **Backend** — Node.js + Express REST API (lead capture, OTP, server-decided spin
  outcome) backed by **MySQL** (via `mysql2`).

```
Spin wheel/
├── package.json          # root scripts (run both apps together)
├── backend/              # Node.js + Express API
│   ├── server.js
│   ├── .env.example      # MySQL connection config
│   └── src/
│       ├── mysql.js      # connection pool, schema creation, seeding
│       ├── db.js         # async data access (settings/coupons/segments/users/spins)
│       └── store.js      # in-memory OTP sessions
└── frontend/             # Next.js app
    ├── app/              # layout, globals, page (flow), admin/ (admin panel)
    ├── components/       # reusable UI + components/admin/* + components/screens/*
    └── lib/api.js        # API client (public + admin)
```

## Prerequisites

A running **MySQL** server (8.x recommended). The app creates its database, tables
and seed data automatically on first boot — you only need the server + credentials.

```bash
# macOS (Homebrew)
brew install mysql && brew services start mysql

# or Docker
docker run --name spin-mysql -e MYSQL_ROOT_PASSWORD= -e MYSQL_ALLOW_EMPTY_PASSWORD=1 \
  -p 3306:3306 -d mysql:8
```

## Getting started

```bash
# from the project root
npm install            # installs root tooling (concurrently)
npm run install:all    # installs backend + frontend deps too

# configure the DB connection
cp backend/.env.example backend/.env   # then edit credentials if needed

npm run dev            # runs backend (:4000) and frontend (:3000) together
```

On first run the backend creates the `spin_to_win` database, all tables, and seeds
default branding / coupons / segments. You'll see `Seeded default …` in the log.

Then open the **spin page at http://localhost:3000** and the **admin panel at
http://localhost:3000/admin**.

Run them separately if you prefer:

```bash
npm run dev:backend    # http://localhost:4000
npm run dev:frontend   # http://localhost:3000
```

## How the flow works

1. **Landing** — tap the golden hub or *Spin the Wheel Now*.
2. **Lead form** — name + 10-digit mobile + marketing consent → `POST /api/lead`.
3. **OTP** — 4-digit code (the demo prints it to the backend console and shows a
   "Demo code" hint in the UI) → `POST /api/verify`.
4. **Spin** — the **server** picks the winning segment (`POST /api/spin`) and the
   wheel animates to land on it. The outcome cannot be guessed/forged client-side.
5. **You won** modal → **All set** confirmation with the redeemable coupon code.

## Admin panel (`/admin`)

A control center for the whole campaign. Everything here drives the public page live.

1. **Branding** — brand name, tagline, logo (text or uploaded image), and theme +
   accent colors (with presets). These re-skin the public page's logo, headline
   gradient and CTAs.
2. **Wheel & Segments** — set the wheel **size** (slider), add/remove segments, pick
   each segment's **color**, attach the **coupon** it pays out, and set its **win weight**.
   A live percentage shows each segment's real chance — this is how you *control the
   user's winning prediction*. Includes a wheel preview + test spin.
3. **Coupons** — create/edit/enable/disable/delete rewards (title, product, code
   prefix, validity). The coupon you attach to a segment is exactly what that segment
   awards, with a generated code.
4. **Users & Spins** — every registered user (name, mobile, spin count, last reward,
   marketing opt-in) and every spin (user, **spin time**, **won coupon**, code, and
   editable **coupon status**: active / redeemed / expired).

## API

### Public

| Method | Path           | Purpose                                          |
| ------ | -------------- | ------------------------------------------------ |
| GET    | `/api/health`  | Health check                                     |
| GET    | `/api/config`  | Branding settings + wheel segments (public view) |
| POST   | `/api/lead`    | Capture lead (creates a user), "send" OTP         |
| POST   | `/api/verify`  | Verify OTP code                                  |
| POST   | `/api/resend`  | Re-issue OTP                                     |
| POST   | `/api/spin`    | Weighted draw, records the spin, returns coupon  |

### Admin (`/api/admin/*`)

| Method | Path             | Purpose                                  |
| ------ | ---------------- | ---------------------------------------- |
| GET/PUT| `/settings`      | Read / update branding + wheel size      |
| GET/POST | `/coupons`     | List / create coupons                    |
| PUT/DELETE | `/coupons/:id` | Update / delete a coupon              |
| GET/PUT| `/segments`      | List / replace wheel segments            |
| GET    | `/users`         | Registered users + spin summary          |
| GET    | `/spins`         | All spin records                         |
| PATCH  | `/spins/:id`     | Update a coupon's status                 |
| GET    | `/stats`         | Dashboard counters                       |

### Configuration

- All campaign data (settings, segments, coupons, users, spins) lives in **MySQL**.
  Tables: `settings`, `coupons`, `segments`, `users`, `spins`.
- DB connection is read from `backend/.env` (`DB_HOST`, `DB_PORT`, `DB_USER`,
  `DB_PASSWORD`, `DB_NAME`). See `backend/.env.example`.
- To reset to defaults: `DROP DATABASE spin_to_win;` (it re-creates + re-seeds on the
  next boot), or just empty the `settings` table.
- The frontend reads the API base from `NEXT_PUBLIC_API_BASE` (defaults to
  `http://localhost:4000`).

## Notes for production

Data is stored in MySQL, but OTP sessions are kept in an in-memory store and the OTP is
returned in the response for demo convenience. The `/api/admin/*` routes have **no auth**.
For production: **put the admin API behind authentication**, move OTP sessions to a
shared store (e.g. Redis) if you run multiple instances, send the OTP via an SMS
provider, rate-limit `/api/lead` & `/api/verify`, and stop returning `devOtp`.
