mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
Store base
This commit is contained in:
+129
@@ -0,0 +1,129 @@
|
||||
# Store (`store.gregkilmer.com`)
|
||||
|
||||
A small storefront for Greg's **proxy MTG card** and **3D-printed deck box /
|
||||
counter** services. It lives at `/store` inside the same app, reuses the main
|
||||
site's aesthetic, and is reachable directly or via the `store.gregkilmer.com`
|
||||
subdomain. It takes **no payment** — the order form only emails a request — and
|
||||
the form is **hard-locked** until you choose to accept orders.
|
||||
|
||||
## Pages
|
||||
|
||||
| Route | What it is |
|
||||
| ------------------ | ------------------------------------------------ |
|
||||
| `/store` | Storefront: intro, products, live slots, policies |
|
||||
| `/store/cards` | Proxy MTG cards specs + pricing |
|
||||
| `/store/deck-boxes`| 3D-printed deck boxes & counters specs + pricing |
|
||||
| `/store/order` | Order request form (gated) or "closed" notice |
|
||||
|
||||
The store uses its own nav (`StoreNav.astro`) and has **no links back to the
|
||||
main site**. The two sites are intentionally not connected from the UI.
|
||||
|
||||
## Opening and closing orders
|
||||
|
||||
Everything customer-facing about availability is driven by **one file**:
|
||||
|
||||
```
|
||||
src/data/store-availability.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"ordersOpen": false,
|
||||
"lastUpdated": "2026-08-04",
|
||||
"turnaround": "Estimated turnaround: ~2 weeks once a list is confirmed.",
|
||||
"maxNote": "I take a limited number of orders at a time...",
|
||||
"slots": {
|
||||
"cards": { "open": 0, "total": 5 },
|
||||
"deckBoxes": { "open": 0, "total": 4 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **`ordersOpen`** is the master switch.
|
||||
- `false` → the order page shows a "closed" notice and renders **no form**.
|
||||
- `true` → the order form appears.
|
||||
- **`slots.cards` / `slots.deckBoxes`** drive the "X of Y open" meters on the
|
||||
storefront and order page. Set `open` to 0 to show a category as Closed.
|
||||
- **`turnaround`**, **`maxNote`**, and **`lastUpdated`** are shown verbatim.
|
||||
|
||||
Edit the file, commit, and redeploy. That's it.
|
||||
|
||||
### The lock is enforced in two places
|
||||
|
||||
1. **UI**: `/store/order` reads the file at render and hides the form when
|
||||
`ordersOpen` is false.
|
||||
2. **Server**: `/api/order` reads the file again and returns **403** if
|
||||
`ordersOpen` is false — *before* doing any validation or delivery. So even a
|
||||
direct POST can't bypass the lock.
|
||||
|
||||
## Editing products, specs, and pricing
|
||||
|
||||
Those live in version-controlled TypeScript (not the availability JSON):
|
||||
|
||||
```
|
||||
src/data/store-products.ts
|
||||
```
|
||||
|
||||
Each product has an `icon`, `title`, `summary`, example `image`, a `specs` list
|
||||
(size, thickness, finishes, white back, etc.), and `pricing` tiers. Search for
|
||||
`[Greg to fill]` to find the placeholder prices and concrete specs to replace.
|
||||
|
||||
The shared **requirements / policies** list is also in this file (`requirements`)
|
||||
and is rendered on the storefront, the order page, and required as a checkbox on
|
||||
the order form.
|
||||
|
||||
## Order delivery (email)
|
||||
|
||||
Order requests are emailed using the **same SMTP/webhook pipeline as the contact
|
||||
form** (see `docs/DEPLOYMENT.md`). The only addition is the inbox:
|
||||
|
||||
```
|
||||
ORDER_TO=orders@gregkilmer.com
|
||||
```
|
||||
|
||||
If `ORDER_TO` is blank, it falls back to `CONTACT_TO`. Configure `SMTP_*` vars
|
||||
as usual; if neither SMTP nor `CONTACT_WEBHOOK_URL` is set, `/api/order` returns
|
||||
503 (delivery not configured) so it's visibly a config gap.
|
||||
|
||||
Rate limiting reuses `CONTACT_RATE_LIMIT` / `CONTACT_RATE_WINDOW_MIN` unless you
|
||||
set `ORDER_RATE_LIMIT` / `ORDER_RATE_WINDOW_MIN` to give orders a separate budget.
|
||||
|
||||
Bot protection reuses the Cloudflare **Turnstile** keys (`TURNSTILE_SITE_KEY` /
|
||||
`TURNSTILE_SECRET_KEY`). Leave both blank to skip Turnstile (e.g. local dev).
|
||||
|
||||
## Pointing `store.gregkilmer.com` at the store
|
||||
|
||||
The store is part of the same app — no separate server. The subdomain is purely
|
||||
a reverse-proxy concern (Cosmos / Traefik / Nginx Proxy Manager):
|
||||
|
||||
1. Create a DNS record for `store.gregkilmer.com`.
|
||||
2. In your reverse proxy, point `store.gregkilmer.com` at the same container on
|
||||
port 4321, with TLS.
|
||||
3. (Optional) Add a redirect or rewrite so `store.gregkilmer.com/` maps to
|
||||
`/store`. The store pages are reachable at `/store*` on the main domain too,
|
||||
so a redirect isn't strictly required — visitors who know the path can reach
|
||||
it either way.
|
||||
|
||||
No code change is needed to enable the subdomain.
|
||||
|
||||
## Search visibility
|
||||
|
||||
Store pages are **`noindex` by default** (set in `StoreLayout.astro`). When
|
||||
you're ready for the store to be indexed, change `noindex` to `false` there (or
|
||||
override it per page via the `noindex` prop).
|
||||
|
||||
## Files added / changed
|
||||
|
||||
- `src/data/store-availability.json` — **the file you edit**
|
||||
- `src/data/store-availability.ts` — typed reader (used by pages + API)
|
||||
- `src/data/store-products.ts` — products, specs, pricing, requirements
|
||||
- `src/components/store/` — `StoreNav`, `ProductCard`, `PricingTable`,
|
||||
`SlotMeter`, `RequirementList`, `OrderForm.tsx`
|
||||
- `src/layouts/StoreLayout.astro` — store wrapper (surface, store nav, noindex)
|
||||
- `src/pages/store/` — `index`, `cards`, `deck-boxes`, `order`
|
||||
- `src/pages/api/order.ts` — order endpoint with hard gate
|
||||
- `src/layouts/BaseLayout.astro` — added optional `storeNav` prop (renders
|
||||
`StoreNav` instead of the main `NavBar` on store pages)
|
||||
- `src/components/nav/MobileMenu.tsx` — added store icon paths
|
||||
- `src/lib/env.ts` — added `orderTo()` helper
|
||||
- `.env.example` — added `ORDER_TO`
|
||||
Reference in New Issue
Block a user