Files
com.gregkilmer.www/services/site/src/pages/store/order.astro
T
Claude 7aa2910b12 Add light mode that follows the device setting
Dark stays the default. A device set to light gets the picks from the
light motif board: the Dawn chart sky (dawn wash over faint star chart
lines, atlas marks in the hero), deep gold line seals, Midnight, violet and
gold dust, the deep gold to teal filament and the morning sky placeholder.
Large standalone seals (404, Inquiries, Inquiry sent, the blog's lead) are
gold foil in both modes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
2026-10-18 18:54:56 +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(var(--gk-gold-rgb), 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>