mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
Phase 6: sync jobs, inquiry and order forms
- Sync scheduler in the content service with a per-job cache (sync_cache), sync_runs reporting and Healthchecks pings. Jobs: Archidekt, BoardGameGeek, cover art (Open Library, TMDB, AniList), Suwayomi and Kavita reading progress, Bambuddy filament and Homebox material stock. A note's own value always wins over a synced one. - PersonalReader: updates only Library notes with a matching anilistId, changes to Greg's notes are held for review; Reading series with no note show as suggestions on the admin Overview. - /api/inquiries and /api/orders: Turnstile, rate limits, a saved copy in the content service (intake token) plus email over SMTP. Orders are re-checked against the live notes: accepted only when the category's ordersOpen is true and the line has capacity. - Admin Inbox for saved submissions. Options' inStock is optional so synced stock can fill it. All settings from process.env; .env.example per service. - Sources could not be reached from the build environment; parsers are tested against fixtures labelled as not live. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
---
|
||||
/**
|
||||
* The confirmation after an order request. /api/orders sets the short-lived
|
||||
* `gk-order` cookie (URL-encoded JSON: name, email, line, rows) before it
|
||||
* redirects here; the page shows it once and clears it. Without the cookie
|
||||
* the page still reads correctly.
|
||||
*/
|
||||
import Button from "@gk/ui/components/Button.astro";
|
||||
import PageShell from "@gk/ui/components/PageShell.astro";
|
||||
import Star from "@gk/ui/components/Star.astro";
|
||||
import TickRing from "@gk/ui/components/TickRing.astro";
|
||||
import TickScale from "@gk/ui/components/TickScale.astro";
|
||||
import Breadcrumb from "../../../components/store/Breadcrumb.astro";
|
||||
import { parseOrderSent } from "../../../lib/forms.ts";
|
||||
import { site } from "../../../lib/page.ts";
|
||||
import { storeState } from "../../../lib/store.ts";
|
||||
|
||||
const sent = parseOrderSent(Astro.cookies.get("gk-order")?.value);
|
||||
if (sent) Astro.cookies.delete("gk-order", { path: "/" });
|
||||
Astro.response.headers.set("Cache-Control", "private, no-store");
|
||||
|
||||
const s = await site().catch(() => null);
|
||||
const replyTime = s?.settings()?.replyTime ?? "a few days";
|
||||
const line = sent?.lineSlug && s ? storeState(s).lines.find((l) => l.slug === sent.lineSlug) : undefined;
|
||||
const turnaround = line && s ? storeState(s).category(line.data.category).turnaround : undefined;
|
||||
const rows = [
|
||||
...(sent?.line ? [{ label: "Line", value: sent.line }] : []),
|
||||
...(sent?.rows ?? []),
|
||||
...(sent?.email ? [{ label: "Reply to", value: sent.email }] : []),
|
||||
];
|
||||
const firstName = sent?.name?.split(/\s+/)[0];
|
||||
const steps = [
|
||||
{ title: "I check the request", text: "The details, the options and my queue." },
|
||||
{ title: "I confirm with you", text: "The total and the timing, by email. No payment is taken through the site." },
|
||||
{ title: "I start once you say yes", text: turnaround ?? "Nothing is made until we agree." },
|
||||
];
|
||||
---
|
||||
<PageShell title="Order request sent" family="store" current="store" description="Your order request is on its way.">
|
||||
<meta slot="head" name="robots" content="noindex" />
|
||||
<section class="intro" aria-labelledby="sent-h1">
|
||||
<Breadcrumb here="Request sent" />
|
||||
<h1 id="sent-h1">Your request is in</h1>
|
||||
<p class="lede">Thanks{firstName ? `, ${firstName}` : ""}. I read every order request myself and reply within {replyTime}.</p>
|
||||
</section>
|
||||
|
||||
<div class="layout">
|
||||
<div class="main">
|
||||
<ol class="steps" aria-label="What happens next">
|
||||
{
|
||||
steps.map((st, i) => (
|
||||
<li>
|
||||
<TickRing size={36}>{i + 1}</TickRing>
|
||||
<span class="step-text">
|
||||
<span class="step-title">{st.title}</span>
|
||||
<span class="step-detail">{st.text}</span>
|
||||
</span>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
<div class="actions">
|
||||
<Button href="/store">Back to the Store</Button>
|
||||
<Button variant="secondary" href="/inquiries">Something custom? Start an inquiry</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
rows.length > 0 && (
|
||||
<aside class="sent" aria-labelledby="sent-heading">
|
||||
<h2 id="sent-heading" class="kicker">
|
||||
<Star size={11} />
|
||||
What you sent
|
||||
</h2>
|
||||
<TickScale width={150} height={10} count={30} class="ticks" />
|
||||
<dl>
|
||||
{rows.map((r) => (
|
||||
<div class="row">
|
||||
<dt>{r.label}</dt>
|
||||
<dd>{r.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</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 480px;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
.steps {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
border-bottom: 1px solid var(--gk-lines);
|
||||
}
|
||||
.steps li {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
padding: 14px 0;
|
||||
border-top: 1px solid var(--gk-lines);
|
||||
font-size: 13px;
|
||||
}
|
||||
.step-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
.step-title {
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
}
|
||||
.step-detail {
|
||||
font-size: 14px;
|
||||
color: var(--gk-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.sent {
|
||||
flex: 1 1 300px;
|
||||
max-width: 420px;
|
||||
min-width: 0;
|
||||
padding: 24px;
|
||||
border-radius: var(--gk-radius);
|
||||
background: var(--gk-midnight);
|
||||
border: 1px solid rgba(201, 162, 74, 0.55);
|
||||
}
|
||||
.kicker {
|
||||
margin: 0 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: var(--gk-font-body);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.3em;
|
||||
font-weight: 600;
|
||||
color: var(--gk-gold-bright);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.sent :global(.ticks) {
|
||||
margin: -4px 0 2px;
|
||||
}
|
||||
dl {
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 10px 0;
|
||||
border-top: 1px solid var(--gk-lines);
|
||||
font-size: 14px;
|
||||
}
|
||||
dt {
|
||||
color: var(--gk-muted);
|
||||
}
|
||||
dd {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.sent {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user