Files
com.gregkilmer.www/services/site/src/pages/store/order.astro
T
Claude 151bed230a Match the site to the approved designs
Denser star field, the page nebula, a favicon package, the filament banner
without its box, the About portrait, Kashi and tea photos and Scorpius, the
Store's own header, a header logo glow, a resume PDF that fills its page,
one divider on the Library and Shelf, star-chart art for coverless
favorites, Shelf Players, Play time and blog filters with a Show all fold,
make kind pills on craft pages, and full-width closing bands.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
2026-10-12 03:33:15 +00:00

198 lines
6.2 KiB
Plaintext

---
/**
* The order request form: lines that can take an order now, the chosen
* line's options, quantity, details, notes, contact and the terms. When no
* category takes orders, or every open line is full, a notice shows instead.
*/
import Button from "@gk/ui/components/Button.astro";
import Notice from "@gk/ui/components/Notice.astro";
import PageShell from "@gk/ui/components/PageShell.astro";
import Star from "@gk/ui/components/Star.astro";
import TickScale from "@gk/ui/components/TickScale.astro";
import Breadcrumb from "../../components/store/Breadcrumb.astro";
import LineStatus from "../../components/store/LineStatus.astro";
import OrderForm from "../../components/store/OrderForm.astro";
import { site } from "../../lib/page.ts";
import { formatDate } from "../../lib/site.ts";
import { canOrder, channelOf, storeNav, storeState } from "../../lib/store.ts";
import { errorMessages, parseDraft, parseErrorCode } from "../../lib/forms.ts";
import { turnstileSiteKey } from "../../lib/forms-server.ts";
const s = await site();
const st = storeState(s);
// A post that was turned down comes back with its reason and the answers given.
const draft = parseDraft(Astro.cookies.get("gk-order-draft")?.value);
if (draft) {
Astro.cookies.delete("gk-order-draft", { path: "/" });
Astro.response.headers.set("Cache-Control", "private, no-store");
}
const errorCode = draft?.code ?? parseErrorCode(Astro.url.searchParams.get("error"));
const turnstileKey = turnstileSiteKey();
const selected = draft?.values.line ?? Astro.url.searchParams.get("line") ?? undefined;
const orderable = st.lines.filter((l) => canOrder(l, st));
const selling = st.lines.filter((l) => channelOf(l) === "here");
const shown = orderable.length ? orderable : selling;
const turnarounds = [...new Set(st.groups.map(([c]) => st.category(c).turnaround).filter((t): t is string => !!t))];
const reopens = st.closed.filter((c) => c.reopens).map((c) => `${c.name} reopens ${formatDate(c.reopens)}.`);
---
<PageShell title="Order request" family="store" current="store" store={storeNav(st, "/store/order")} description="Send an order request for something from the Store.">
{turnstileKey && <script slot="head" src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer is:inline></script>}
<section class="intro" aria-labelledby="order-h1">
<Breadcrumb here="Order request" />
<h1 id="order-h1">Order request</h1>
<p class="lede">Send a request and I will confirm the details and the total with you. No payment is taken through the site.</p>
</section>
<div class="layout">
<div class="main">
{errorCode && (
<div class="problem">
<Notice role="alert" title={errorMessages[errorCode]}>
{draft && Object.keys(draft.errors).length > 0 ? "The marked answers are below." : ""}
</Notice>
</div>
)}
{
orderable.length > 0 ? (
<OrderForm lines={orderable} selected={selected} storeTerms={st.terms} values={draft?.values} errors={draft?.errors} turnstileKey={turnstileKey} />
) : (
<div class="closed">
{st.anyOpen ? (
<Notice title="All order slots are taken right now.">Slots reopen as current orders finish.</Notice>
) : (
<Notice title="Orders currently closed.">{reopens.join(" ")}</Notice>
)}
<p>{st.settings?.capacityNote}</p>
<div class="actions">
<Button href="/store">Back to the Store</Button>
<Button variant="secondary" href="/inquiries">Something custom? Start an inquiry</Button>
</div>
</div>
)
}
</div>
<aside class="side" aria-label="Order status">
{shown.map((l) => <LineStatus line={l} />)}
{
(turnarounds.length > 0 || st.settings?.capacityNote) && (
<div class="know">
<span class="know-head">
<Star size={11} />
<span>Good to know</span>
</span>
<TickScale width={160} height={10} count={24} />
{turnarounds.map((t) => <span>{t}</span>)}
{orderable.length > 0 && st.settings?.capacityNote && <span>{st.settings.capacityNote}</span>}
</div>
)
}
{orderable.length > 0 && <a href="/inquiries" class="gk-text inq">Something custom? Start an inquiry</a>}
</aside>
</div>
</PageShell>
<style>
.intro {
padding: 36px 0 8px;
display: flex;
flex-direction: column;
gap: 10px;
}
h1 {
margin: 0;
font-size: clamp(32px, 5vw, 44px);
letter-spacing: 0.05em;
}
.lede {
margin: 0;
font-size: 16px;
color: var(--gk-soft);
max-width: 64ch;
line-height: 1.6;
}
.layout {
padding: 16px 0 64px;
display: flex;
flex-wrap: wrap;
gap: 32px;
align-items: flex-start;
}
.main {
flex: 3 1 600px;
min-width: 0;
}
.problem {
margin-bottom: 16px;
}
.closed {
display: flex;
flex-direction: column;
gap: 16px;
padding: 32px;
border-radius: var(--gk-radius);
background: var(--gk-midnight);
border: 1px solid rgba(201, 162, 74, 0.55);
}
.closed :global(.gk-note) {
font-size: 15px;
}
.closed p {
margin: 0;
font-size: 15px;
line-height: 1.6;
color: var(--gk-soft);
}
.closed p:empty {
display: none;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.side {
flex: 1 1 280px;
max-width: 360px;
min-width: 0;
display: flex;
flex-direction: column;
gap: 14px;
}
.know {
display: flex;
flex-direction: column;
gap: 8px;
padding: 18px 20px;
border-radius: var(--gk-radius);
background: var(--gk-midnight);
border: 1px solid var(--gk-lines);
font-size: 14px;
line-height: 1.6;
color: var(--gk-soft);
}
.know-head {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
letter-spacing: 0.3em;
font-weight: 600;
color: var(--gk-gold-bright);
text-transform: uppercase;
}
.inq {
font-size: 14px;
font-weight: 600;
align-self: flex-start;
}
@media (max-width: 700px) {
.side {
max-width: none;
}
.closed {
padding: 22px 16px;
}
}
</style>