Files
greatbear/README.md
T
tanshu e3444dbc60 Support decimal rupee prices (paise after the point)
PB number fields are float64 with onlyInt unset, so storage already
accepted decimals — verified 449.5 round-trips via API with no migration.
Cards now format fractional prices as ₹449.50 (whole rupees stay ₹449);
docs and type comments updated.
2026-09-03 18:42:07 +05:30

201 lines
9.0 KiB
Markdown

# The Great Bear — Microbrewery Website
Full-stack site for The Great Bear microbrewery (SCO 32, Sector 26, Chandigarh), built from the
Stitch "Industrial Hospitality" design.
| Layer | Tech |
| --------- | ----------------------------------------------------------- |
| Frontend | [TanStack Start](https://tanstack.com/start) (React 19, Vite 8, Tailwind CSS 4, SSR) |
| Backend | [PocketBase](https://pocketbase.io) v0.40 — CMS content, form intake, admin UI |
| Runtime | Single Docker image running both services |
| Proxy | Caddy (automatic HTTPS) on the target host |
| Deploys | `deploy.sh [tag]` (Makefile buildx push) → Ansible playbook |
## Pages
- `/` — hero, stats, featured brews, experiences, testimonials, quick table booking
- `/our-brews` — flagship banner, core lineup, seasonal releases, tasting-flight builder
- `/menu` — filterable gastropub menu with beer pairings
- `/visit` — reservation form, contact info, map
All structured content (brews, menu, testimonials, stats, contact info) is served from
PocketBase and editable in the admin UI. If PocketBase is unreachable, the site falls back to
bundled seed content so it never renders empty.
## Repository layout
```
app/ TanStack Start application
src/routes/ Pages (/, /our-brews, /menu, /visit)
src/components/ Shared UI + client forms
src/lib/ Server functions & PocketBase client
src/data/content.json Single source of truth for seed + fallback content
public/images/ Design assets (downloaded from the Stitch export)
pb/migrations/ PocketBase schema (snapshot migration, auto-applied)
scripts/
seed.mjs Idempotent content seeder (create-if-missing by uid)
create-collections.sh Dev helper used to build the schema migration
ansible/ Deployment playbook (roles: network, greatbear, caddy)
Makefile Multi-arch buildx targets (build-production / build-check)
Dockerfile Multi-stage image build (deps → builder → pb-downloader → runner)
docker-entrypoint.sh Container supervisor: PocketBase + site server
deploy.sh Build & push image, then run the Ansible deploy
```
## Local development
Frontend only (renders from bundled seed content; forms return errors):
```bash
cd app
npm install
npm run dev # http://localhost:3000
```
Full stack with PocketBase (recommended — from the repo root):
```bash
# Terminal 1 — PocketBase on :8090 (data in ./pb_data, schema auto-applied
# from pb/migrations on first boot)
./.tmp-pb/pocketbase serve --http=127.0.0.1:8090 --dir=./pb_data --migrationsDir=./pb/migrations
# Terminal 2 — one-time setup: create a local admin and seed the content
./.tmp-pb/pocketbase superuser upsert dev@greatbear.in devpass12345 --dir=./pb_data --migrationsDir=./pb/migrations
PB_URL=http://127.0.0.1:8090 PB_SUPERUSER_EMAIL=dev@greatbear.in PB_SUPERUSER_PASSWORD=devpass12345 \
node scripts/seed.mjs
# Terminal 2 — the site (talks to PocketBase on 127.0.0.1:8090 automatically)
cd app && npm run dev
```
- PocketBase admin UI: `http://127.0.0.1:8090/_/` (dev@greatbear.in) — edits show up on the
site on the next page load
- Dev data lives in `./pb_data` (git-ignored); delete it for a factory reset
- The PB binary in `.tmp-pb/` was fetched during setup — if it's missing, grab a release from
https://github.com/pocketbase/pocketbase/releases (or run PocketBase however you like; only
the port matters)
`npm run build` produces `.output/server/index.mjs`, started with `npm start`. To run the
**production image** locally instead:
```bash
docker build -t greatbear:dev .
docker run --rm -p 3000:3000 -p 8090:8090 \
-e PB_SUPERUSER_EMAIL=dev@greatbear.in -e PB_SUPERUSER_PASSWORD=devpass12345 \
-v greatbear_dev_data:/app/pb_data greatbear:dev
```
## Deploying
The image is always built from the **committed state of the git remote** — commit and push
before deploying. Registry credentials: `docker login registry.tanshu.com` (once per machine).
```bash
./deploy.sh # build + push :latest, then deploy that tag via Ansible
./deploy.sh v1.2.3 # same, with an explicit version tag
```
Or run the pieces yourself:
```bash
make build-production TAG=v1.2.3 # multi-arch (amd64+arm64) buildx build & push
make build-check # multi-arch compile check (no push)
make build-check-local # build the committed local tree (git archive) and load it
cd ansible && ansible-playbook playbook.yml -e "tag=v1.2.3"
```
### Ansible deployment
Requirements on the controller: `ansible` + `community.docker` collection (see
`ansible/requirements.yml`). Requirements on the host: Docker with a Caddy **container** named
`caddy` (config at `/var/lib/caddy/conf/Caddyfile`).
```bash
cd ansible
ansible-galaxy install -r requirements.yml
ansible-playbook playbook.yml # targets inventory host `monoco` — edit playbook.yml if needed
```
All deployment variables live in `ansible/vars/default.yml` (registry, tag, domains,
PocketBase admin credentials). The playbook:
1. Ensures the `greatbear_net` Docker network exists with the Caddy container attached.
2. Pulls `registry.tanshu.com/tanshu/greatbear:{tag}`, uploads `/var/lib/greatbear/.env`,
and (re)creates the `greatbear` container on that network with
`/var/lib/greatbear/pb_data` bind-mounted to `/app/pb_data`, then waits for its healthcheck.
3. Inserts a managed snippet into the shared Caddyfile and reloads Caddy via
`docker exec`:
- `www.greatbear.in` (+ apex `greatbear.in`) — frontend on port 3000, PocketBase
API/admin proxied under `/api/*` and `/_/*`
- `admin.greatbear.in` — redirects to the PocketBase admin UI
No host ports are published; Caddy reaches the containers over the Docker network.
DNS records for all three hosts must point at the server; Caddy obtains TLS certificates
automatically on first request.
## PocketBase admin
- URL: `https://admin.greatbear.in/_/`
- First container start bootstraps the superuser from `PB_SUPERUSER_EMAIL` /
`PB_SUPERUSER_PASSWORD` (templated from `pb_admin_email` / `pb_admin_password` in
`ansible/vars/default.yml`, idempotent).
Content collections: `brews`, `menu_categories`, `menu_items`, `experiences`, `testimonials`,
`stats`, `pairings`, `settings`. Reservation/contact submissions land in `reservations`
(public create only — readable by admins).
The seed only creates records that are missing (matched by `uid`), so edits made in the admin
are never overwritten by redeploys. Schema changes belong in `pb/migrations/`.
## Updating the food menu
The food menu is managed in code: `app/src/data/menu-data.mjs` (11 categories, ~137 items,
single source for the PocketBase sync **and** the site's offline fallback).
```bash
# 1. Edit app/src/data/menu-data.mjs, commit & push, deploy (if the image changed)
# 2. Sync the menu into PocketBase — from anywhere with admin credentials:
PB_URL=https://admin.greatbear.in \
PB_SUPERUSER_EMAIL=admin@greatbear.in \
PB_SUPERUSER_PASSWORD=... \
node scripts/update-menu.mjs
```
The script **replaces** all `menu_categories` / `menu_items` records (deletes old, inserts
new) and touches nothing else.
### `menu_items` schema (POS-oriented)
- `uid` — stable slug identity
- `category` — slug → `menu_categories`
- `name` / `variant` — variants are **separate products** whenever their price differs
("Penne Arrabiata" + variant "Veg" ₹589 / "Chicken" ₹639); the POS can group by `name`
- `description`
- `price` — number, rupees with optional paise after the decimal point
(449 = ₹449, 449.5 = ₹449.50)
- `diet` — select: `veg` / `nonveg` / `mixed` (a bool can't be null in PocketBase, so mixed
items like Caesar Salad use `mixed`; the site renders no badge for it)
- `pairing` — optional beer pairing
- `tag` — badge chip; also carries same-price choice lists (e.g. "Malai / Angara")
- `image` — optional URL
- `sku` — unique POS code, `GB-<CAT>-<NNN>` (e.g. GB-MNC-014)
- `available` — false hides the item from the site (86'd) without deleting it
- `tax_category` — default `food-5-gst`; adjust per item for POS GST mapping
Admin edits to menu records are overwritten by the next sync — treat `menu-data.mjs` as the
menu's source of truth.
## Container environment
| Variable | Default | Purpose |
| ----------------------- | ------------------------ | -------------------------------------- |
| `PORT` | `3000` | Site server port |
| `HOST` | `0.0.0.0` | Site server bind address |
| `PB_URL` | `http://127.0.0.1:8090` | PocketBase URL used by the site server |
| `PB_DATA_DIR` | `/app/pb_data` | PocketBase data directory (volume) |
| `PB_SUPERUSER_EMAIL` | — | Initial admin (bootstrapped at start) |
| `PB_SUPERUSER_PASSWORD` | — | Initial admin password |