mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
a2f723ca2d
Phase 1 of the functional design plan. - Move the old site into legacy/, unchanged, for reference. It still builds from there until cutover. - Add packages/schema: the one definition of all 20 note types, with field titles and descriptions, note and vault checks, and JSON Schema export so later editing tools and the MCP server share it. - Add vault-kit: Obsidian templates generated from the schema, and the Site guide for 24.01_PersonalWebsite. - Add tools/migrate: turns the legacy JSON, collections, store data and page text into 545 Site folder notes and 84 images, and checks the result with the schema. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
152 lines
6.1 KiB
Markdown
152 lines
6.1 KiB
Markdown
# 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.
|
|
|
|
### Available print colors (deck boxes)
|
|
|
|
The deck-box product has a `colors` array — this drives the **Available print
|
|
colors** swatch grid on `/store/deck-boxes` and the color dropdown in the order
|
|
form. Each entry is `{ name, hex, inStock }`:
|
|
|
|
```ts
|
|
colors: [
|
|
{ name: 'Midnight Black', hex: '#1b1730', inStock: true },
|
|
{ name: 'Nebula Teal', hex: '#4f8c84', inStock: false }, // shows "Out of stock"
|
|
],
|
|
```
|
|
|
|
- **Add a color** → append a new `{ name, hex, inStock }` entry.
|
|
- **Mark a color sold out** → set `inStock: false`. It shows an "Out of stock"
|
|
label on the grid and drops out of the order-form dropdown automatically.
|
|
- **Remove a color** → delete the entry.
|
|
|
|
Only in-stock colors are offered in the order form; the chosen color is included
|
|
in the order email. Colors are optional — products with no `colors` array (e.g.
|
|
proxy cards) simply don't show the grid or the dropdown.
|
|
|
|
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`
|