@tokenoftrust/storefront-runner 1.3.2 → 1.3.4-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/storefront/astro.config.mjs +50 -0
- package/apps/storefront/env.d.ts +24 -8
- package/apps/storefront/src/components/admin/{AdminFoxyCartSettingsPanel.astro → AdminCheckoutSettingsPanel.astro} +50 -51
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
- package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +613 -0
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +3 -3
- package/apps/storefront/src/components/chrome/SiteHeader.astro +3 -4
- package/apps/storefront/src/components/commerce/{FoxySidecartStyles.astro → CartDrawerStyles.astro} +13 -14
- package/apps/storefront/src/components/commerce/CheckoutLoader.astro +21 -0
- package/apps/storefront/src/components/islands/VariantSelector.tsx +101 -20
- package/apps/storefront/src/config/adminTargetTenant.ts +2 -2
- package/apps/storefront/src/config/devTenantSeed.ts +65 -16
- package/apps/storefront/src/config/resolver.ts +48 -1
- package/apps/storefront/src/layouts/Layout.astro +14 -12
- package/apps/storefront/src/lib/admin/README.md +2 -2
- package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +5 -5
- package/apps/storefront/src/lib/admin/orderClient.ts +20 -20
- package/apps/storefront/src/lib/admin/services/mock/{foxyCartSettings.ts → checkoutSettings.ts} +21 -22
- package/apps/storefront/src/lib/admin/services/mock/index.ts +3 -2
- package/apps/storefront/src/lib/admin/services/mock/settings.ts +1 -1
- package/apps/storefront/src/lib/admin/services/mock/subscriptions.ts +398 -0
- package/apps/storefront/src/lib/admin/services/orders.ts +21 -21
- package/apps/storefront/src/lib/admin/viewmodels/subscriptions.ts +106 -0
- package/apps/storefront/src/lib/auth/loginGate.ts +14 -3
- package/apps/storefront/src/lib/{foxyCommerce.ts → checkoutCommerce.ts} +20 -18
- package/apps/storefront/src/lib/commerce/{foxyCartSettings.ts → checkoutSettings.ts} +65 -66
- package/apps/storefront/src/lib/dev/activityIngestToken.ts +9 -0
- package/apps/storefront/src/lib/dev/activityStore.ts +12 -0
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +23 -4
- package/apps/storefront/src/lib/dev/runtimeStore.ts +136 -7
- package/apps/storefront/src/lib/edgeCache.ts +7 -6
- package/apps/storefront/src/lib/health.ts +53 -0
- package/apps/storefront/src/lib/privacy/emailHint.ts +33 -0
- package/apps/storefront/src/lib/rawMarketingHtml.ts +1 -0
- package/apps/storefront/src/lib/sections.ts +19 -23
- package/apps/storefront/src/lib/securityHeaders.ts +1 -1
- package/apps/storefront/src/lib/storyblok/content-model.ts +3 -2
- package/apps/storefront/src/middleware/index.ts +29 -12
- package/apps/storefront/src/pages/[...slug].astro +5 -6
- package/apps/storefront/src/pages/admin.astro +10 -3
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +4 -4
- package/apps/storefront/src/pages/api/admin/orders.ts +5 -5
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +14 -7
- package/apps/storefront/src/pages/api/checkout-style.css.ts +5 -5
- package/apps/storefront/src/pages/auth/login.astro +4 -1
- package/apps/storefront/src/pages/auth/magic.astro +263 -43
- package/apps/storefront/src/pages/capabilities.astro +1 -1
- package/apps/storefront/src/pages/cockpit.astro +2365 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +19 -0
- package/apps/storefront/src/pages/index.astro +23 -29
- package/apps/storefront/src/pages/products/[handle].astro +50 -20
- package/package.json +1 -1
- package/packages/public-runtime/src/capabilities.ts +97 -9
- package/packages/public-runtime/src/{foxy-orders.ts → checkout-orders.ts} +28 -27
- package/packages/public-runtime/src/checkout-style-publish.ts +25 -25
- package/packages/public-runtime/src/checkout-style.ts +13 -13
- package/packages/public-runtime/src/{foxy.ts → checkout.ts} +111 -34
- package/packages/public-runtime/src/csp.ts +11 -11
- package/packages/public-runtime/src/customization-scripts.ts +28 -0
- package/packages/public-runtime/src/extension-contract.ts +73 -0
- package/packages/public-runtime/src/index.ts +4 -2
- package/packages/public-runtime/src/product.ts +57 -0
- package/packages/public-runtime/src/raw-html-policy.ts +35 -0
- package/packages/public-runtime/src/tenant.ts +14 -11
- package/scripts/dev/checkout-watch.mjs +28 -0
- package/scripts/dev/file-browser.mjs +5 -1
- package/apps/storefront/src/components/commerce/FoxyLoader.astro +0 -23
- package/apps/storefront/src/pages/api/dev/activity.ts +0 -124
- package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +0 -73
- package/apps/storefront/src/pages/api/dev/diff.ts +0 -68
- package/apps/storefront/src/pages/api/dev/file.ts +0 -57
- package/apps/storefront/src/pages/api/dev/locate.ts +0 -125
- package/apps/storefront/src/pages/api/dev/publish.ts +0 -128
- package/apps/storefront/src/pages/api/dev/tree.ts +0 -70
- package/apps/storefront/src/pages/api/dev/write.ts +0 -116
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* SHELL CONTRACT — every AdminXTab.astro component mounted below must preserve:
|
|
14
14
|
* - A `<section id="{tab}" data-admin-panel data-agent-scope="{tab}">` root,
|
|
15
|
-
* where {tab} is one of: orders, products, customers, fulfillment,
|
|
15
|
+
* where {tab} is one of: orders, products, subscriptions, customers, fulfillment,
|
|
16
16
|
* reporting, ai-workflows, settings. The shell's hash router shows/hides
|
|
17
17
|
* panels by matching `html[data-admin-tab]` to this id, and the AI Ask
|
|
18
18
|
* envelope reads `data-agent-scope` to label its active context.
|
|
@@ -35,6 +35,7 @@ import AdminFulfillmentTab from "../components/admin/AdminFulfillmentTab.astro";
|
|
|
35
35
|
import AdminAIWorkflowsTab from "../components/admin/AdminAIWorkflowsTab.astro";
|
|
36
36
|
import OrdersWorkspace from "../components/admin/orders/OrdersWorkspace.astro";
|
|
37
37
|
import AdminProductsTab from "../components/admin/AdminProductsTab.astro";
|
|
38
|
+
import AdminSubscriptionsTab from "../components/admin/AdminSubscriptionsTab.astro";
|
|
38
39
|
import AdminReportingTab from "../components/admin/AdminReportingTab.astro";
|
|
39
40
|
import AdminSettingsTab from "../components/admin/AdminSettingsTab.astro";
|
|
40
41
|
import { readViewerSession } from "@/lib/auth/route";
|
|
@@ -79,7 +80,7 @@ const { envelope: tenantEnvelope, isStaff, pickerTenants, deniedTarget } = admin
|
|
|
79
80
|
<title>{tenantLabel} Admin</title>
|
|
80
81
|
<script is:inline nonce={cspNonce}>
|
|
81
82
|
(() => {
|
|
82
|
-
const adminTabs = ["orders", "products", "customers", "fulfillment", "reporting", "ai-workflows", "settings"];
|
|
83
|
+
const adminTabs = ["orders", "products", "subscriptions", "customers", "fulfillment", "reporting", "ai-workflows", "settings"];
|
|
83
84
|
const canViewInternalGuide = document.documentElement.dataset.internalAdminGuide === "true";
|
|
84
85
|
const defaultTab = canViewInternalGuide ? "orders" : "settings";
|
|
85
86
|
const hash = window.location.hash.slice(1);
|
|
@@ -502,6 +503,7 @@ const { envelope: tenantEnvelope, isStaff, pickerTenants, deniedTarget } = admin
|
|
|
502
503
|
|
|
503
504
|
html.admin-tabs-ready[data-admin-mode="work-area"][data-admin-tab="orders"] #orders,
|
|
504
505
|
html.admin-tabs-ready[data-admin-mode="work-area"][data-admin-tab="products"] #products,
|
|
506
|
+
html.admin-tabs-ready[data-admin-mode="work-area"][data-admin-tab="subscriptions"] #subscriptions,
|
|
505
507
|
html.admin-tabs-ready[data-admin-mode="work-area"][data-admin-tab="customers"] #customers,
|
|
506
508
|
html.admin-tabs-ready[data-admin-mode="work-area"][data-admin-tab="fulfillment"] #fulfillment,
|
|
507
509
|
html.admin-tabs-ready[data-admin-mode="work-area"][data-admin-tab="reporting"] #reporting,
|
|
@@ -1126,6 +1128,7 @@ const { envelope: tenantEnvelope, isStaff, pickerTenants, deniedTarget } = admin
|
|
|
1126
1128
|
<nav class="nav">
|
|
1127
1129
|
<a href="#orders" data-admin-tab-link><svg><use href="#i-file"></use></svg><span>Orders</span></a>
|
|
1128
1130
|
<a href="#products" data-admin-tab-link><svg><use href="#i-box"></use></svg><span>Products</span></a>
|
|
1131
|
+
<a href="#subscriptions" data-admin-tab-link><svg><use href="#i-refresh"></use></svg><span>Subscriptions</span></a>
|
|
1129
1132
|
<a href="#customers" data-admin-tab-link><svg><use href="#i-users"></use></svg><span>Customers</span></a>
|
|
1130
1133
|
<a href="#fulfillment" data-admin-tab-link><svg><use href="#i-truck"></use></svg><span>Fulfillment</span></a>
|
|
1131
1134
|
<a href="#reporting" data-admin-tab-link><svg><use href="#i-chart"></use></svg><span>Reporting</span></a>
|
|
@@ -1151,6 +1154,7 @@ const { envelope: tenantEnvelope, isStaff, pickerTenants, deniedTarget } = admin
|
|
|
1151
1154
|
<div class="tabs mobile-tabbar" role="tablist" aria-label="Admin work areas">
|
|
1152
1155
|
<a class="tab" href="#orders" role="tab" aria-controls="orders" aria-selected="false" data-admin-tab-link>Orders</a>
|
|
1153
1156
|
<a class="tab" href="#products" role="tab" aria-controls="products" aria-selected="false" data-admin-tab-link>Products</a>
|
|
1157
|
+
<a class="tab" href="#subscriptions" role="tab" aria-controls="subscriptions" aria-selected="false" data-admin-tab-link>Subscriptions</a>
|
|
1154
1158
|
<a class="tab" href="#customers" role="tab" aria-controls="customers" aria-selected="false" data-admin-tab-link>Customers</a>
|
|
1155
1159
|
<a class="tab" href="#fulfillment" role="tab" aria-controls="fulfillment" aria-selected="false" data-admin-tab-link>Fulfillment</a>
|
|
1156
1160
|
<a class="tab" href="#reporting" role="tab" aria-controls="reporting" aria-selected="false" data-admin-tab-link>Reporting</a>
|
|
@@ -1197,6 +1201,7 @@ const { envelope: tenantEnvelope, isStaff, pickerTenants, deniedTarget } = admin
|
|
|
1197
1201
|
<div class="tabs admin-tabs" role="tablist" aria-label="Admin work areas">
|
|
1198
1202
|
<a id="admin-tab-orders" class="tab active" href="#orders" role="tab" aria-controls="orders" aria-selected="true" data-admin-tab-link>Orders</a>
|
|
1199
1203
|
<a id="admin-tab-products" class="tab" href="#products" role="tab" aria-controls="products" aria-selected="false" data-admin-tab-link>Products</a>
|
|
1204
|
+
<a id="admin-tab-subscriptions" class="tab" href="#subscriptions" role="tab" aria-controls="subscriptions" aria-selected="false" data-admin-tab-link>Subscriptions</a>
|
|
1200
1205
|
<a id="admin-tab-customers" class="tab" href="#customers" role="tab" aria-controls="customers" aria-selected="false" data-admin-tab-link>Customers</a>
|
|
1201
1206
|
<a id="admin-tab-fulfillment" class="tab" href="#fulfillment" role="tab" aria-controls="fulfillment" aria-selected="false" data-admin-tab-link>Fulfillment</a>
|
|
1202
1207
|
<a id="admin-tab-reporting" class="tab" href="#reporting" role="tab" aria-controls="reporting" aria-selected="false" data-admin-tab-link>Reporting</a>
|
|
@@ -1321,6 +1326,7 @@ const { envelope: tenantEnvelope, isStaff, pickerTenants, deniedTarget } = admin
|
|
|
1321
1326
|
|
|
1322
1327
|
<OrdersWorkspace envelope={tenantEnvelope} />
|
|
1323
1328
|
<AdminProductsTab envelope={tenantEnvelope} />
|
|
1329
|
+
<AdminSubscriptionsTab envelope={tenantEnvelope} />
|
|
1324
1330
|
<AdminCustomersTab envelope={tenantEnvelope} />
|
|
1325
1331
|
<AdminFulfillmentTab envelope={tenantEnvelope} />
|
|
1326
1332
|
<AdminReportingTab envelope={tenantEnvelope} />
|
|
@@ -1441,7 +1447,7 @@ const { envelope: tenantEnvelope, isStaff, pickerTenants, deniedTarget } = admin
|
|
|
1441
1447
|
|
|
1442
1448
|
<script is:inline nonce={cspNonce}>
|
|
1443
1449
|
(() => {
|
|
1444
|
-
const tabIds = ["orders", "products", "customers", "fulfillment", "reporting", "ai-workflows", "settings"];
|
|
1450
|
+
const tabIds = ["orders", "products", "subscriptions", "customers", "fulfillment", "reporting", "ai-workflows", "settings"];
|
|
1445
1451
|
const tabSet = new Set(tabIds);
|
|
1446
1452
|
const tabHashPattern = new RegExp(`^#(${tabIds.join("|")})$`);
|
|
1447
1453
|
const canViewInternalGuide = document.documentElement.dataset.internalAdminGuide === "true";
|
|
@@ -1449,6 +1455,7 @@ const { envelope: tenantEnvelope, isStaff, pickerTenants, deniedTarget } = admin
|
|
|
1449
1455
|
const askContexts = {
|
|
1450
1456
|
orders: "Orders",
|
|
1451
1457
|
products: "Products",
|
|
1458
|
+
subscriptions: "Subscriptions",
|
|
1452
1459
|
customers: "Customers",
|
|
1453
1460
|
fulfillment: "Fulfillment",
|
|
1454
1461
|
reporting: "Reporting",
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `GET /api/admin/orders/:id` — one order's full detail for the Orders tab (U2).
|
|
3
3
|
*
|
|
4
|
-
* Server-side proxy to
|
|
5
|
-
* resolved tenant + env. Bearer stays server-side; response is the
|
|
4
|
+
* Server-side proxy to checkout `GET /commerce/orders/:orderId` for the
|
|
5
|
+
* resolved tenant + env. Bearer stays server-side; response is the CheckoutOrderDetail
|
|
6
6
|
* DTO. `verificationDetail` is empty until U4 fills it — consumers tolerate that.
|
|
7
7
|
* Read-only (scope-locks).
|
|
8
8
|
*/
|
|
9
9
|
import type { APIContext } from "astro";
|
|
10
10
|
import { readEnv } from "@/lib/env";
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
createCheckoutOrderClient,
|
|
13
13
|
orderApiBaseUrl,
|
|
14
14
|
OrderApiError,
|
|
15
15
|
} from "@/lib/admin/orderClient";
|
|
@@ -37,7 +37,7 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
37
37
|
const bearer = await readEnv("FOXY_ORDER_API_KEY");
|
|
38
38
|
if (!bearer) return jsonError("not_provisioned", 503);
|
|
39
39
|
|
|
40
|
-
const client =
|
|
40
|
+
const client = createCheckoutOrderClient({
|
|
41
41
|
baseUrl: orderApiBaseUrl(customizationEnv),
|
|
42
42
|
appDomain: tenant.appDomain,
|
|
43
43
|
bearer,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `GET /api/admin/orders` — internal admin route backing the Orders tab (U2).
|
|
3
3
|
*
|
|
4
|
-
* Server-side proxy to
|
|
4
|
+
* Server-side proxy to the checkout order READ API for the resolved tenant +
|
|
5
5
|
* environment. The per-tenant bearer key stays on the Worker (never in the
|
|
6
6
|
* response); the browser only ever sees order DTO JSON. Read-only (scope-locks).
|
|
7
7
|
* U3 consumes this and maps the DTO → OrdersViewModel.
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
* host/approver layer) — same posture as `api/evidence.json.ts`; no new gate here.
|
|
11
11
|
*/
|
|
12
12
|
import type { APIContext } from "astro";
|
|
13
|
-
import type {
|
|
13
|
+
import type { CheckoutOrderListFilters } from "@tot/public-runtime";
|
|
14
14
|
import { readEnv } from "@/lib/env";
|
|
15
15
|
import {
|
|
16
|
-
|
|
16
|
+
createCheckoutOrderClient,
|
|
17
17
|
orderApiBaseUrl,
|
|
18
18
|
OrderApiError,
|
|
19
19
|
} from "@/lib/admin/orderClient";
|
|
@@ -46,7 +46,7 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
46
46
|
const url = new URL(context.request.url);
|
|
47
47
|
const q = url.searchParams;
|
|
48
48
|
const limitRaw = q.get("limit");
|
|
49
|
-
const filters:
|
|
49
|
+
const filters: CheckoutOrderListFilters = {
|
|
50
50
|
queue: q.get("queue") ?? undefined,
|
|
51
51
|
status: q.get("status") ?? undefined,
|
|
52
52
|
from: q.get("from") ?? undefined,
|
|
@@ -55,7 +55,7 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
55
55
|
cursor: q.get("cursor") ?? undefined,
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
const client =
|
|
58
|
+
const client = createCheckoutOrderClient({
|
|
59
59
|
baseUrl: orderApiBaseUrl(customizationEnv),
|
|
60
60
|
appDomain: tenant.appDomain,
|
|
61
61
|
bearer,
|
|
@@ -24,16 +24,19 @@ import {
|
|
|
24
24
|
sessionFromFederatedClaims,
|
|
25
25
|
} from "@/lib/auth/identityToken";
|
|
26
26
|
import { createSession, DEFAULT_SESSION_TTL_SECONDS } from "@/lib/auth/session";
|
|
27
|
+
import { normalizeEmailHint } from "@/lib/privacy/emailHint";
|
|
27
28
|
|
|
28
29
|
export const prerender = false;
|
|
29
30
|
|
|
30
31
|
/** Back to the landing page carrying an error (no token in the URL on failure). */
|
|
31
32
|
function magicRedirect(
|
|
32
33
|
context: APIContext,
|
|
33
|
-
params: { error?: string; next?: string },
|
|
34
|
+
params: { error?: string; next?: string; tenant?: string; emailHint?: string },
|
|
34
35
|
): Response {
|
|
35
36
|
const q = new URLSearchParams();
|
|
36
37
|
if (params.error) q.set("error", params.error);
|
|
38
|
+
if (params.tenant) q.set("tenant", params.tenant);
|
|
39
|
+
if (params.emailHint) q.set("email_hint", params.emailHint);
|
|
37
40
|
if (params.next && params.next !== "/dashboard") q.set("next", params.next);
|
|
38
41
|
const qs = q.toString();
|
|
39
42
|
return context.redirect(`/auth/magic${qs ? `?${qs}` : ""}`, 303);
|
|
@@ -42,19 +45,23 @@ function magicRedirect(
|
|
|
42
45
|
export async function POST(context: APIContext): Promise<Response> {
|
|
43
46
|
let token = "";
|
|
44
47
|
let next = "/dashboard";
|
|
48
|
+
let tenant = "";
|
|
49
|
+
let emailHint = "";
|
|
45
50
|
try {
|
|
46
51
|
const form = await context.request.formData();
|
|
47
52
|
token = String(form.get("token") ?? "").trim();
|
|
53
|
+
tenant = String(form.get("tenant") ?? "").trim().toLowerCase();
|
|
54
|
+
emailHint = normalizeEmailHint(String(form.get("email_hint") ?? "")) ?? "";
|
|
48
55
|
next = safeNext(String(form.get("next") ?? "/dashboard"));
|
|
49
56
|
} catch {
|
|
50
57
|
return magicRedirect(context, { error: "invalid", next });
|
|
51
58
|
}
|
|
52
|
-
if (!token) return magicRedirect(context, { error: "missing", next });
|
|
59
|
+
if (!token) return magicRedirect(context, { error: "missing", tenant, emailHint, next });
|
|
53
60
|
|
|
54
61
|
const client = await getAccessClient();
|
|
55
62
|
const store = await getSessionStore();
|
|
56
63
|
if (!client || !store) {
|
|
57
|
-
return magicRedirect(context, { error: "unconfigured", next });
|
|
64
|
+
return magicRedirect(context, { error: "unconfigured", tenant, emailHint, next });
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
// Consume the token (single-use; burned server-side by tot20).
|
|
@@ -62,19 +69,19 @@ export async function POST(context: APIContext): Promise<Response> {
|
|
|
62
69
|
if (!result.ok) {
|
|
63
70
|
// reason ∈ invalid|expired|error (tot20's "used" normalizes to invalid).
|
|
64
71
|
const error = result.reason === "expired" ? "expired" : "invalid";
|
|
65
|
-
return magicRedirect(context, { error, next });
|
|
72
|
+
return magicRedirect(context, { error, tenant, emailHint, next });
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
// Prove authorization offline — fail closed with no flat-JSON fallback.
|
|
69
76
|
const idToken = result.id_token;
|
|
70
77
|
if (!idToken) {
|
|
71
78
|
console.error("auth/magic-exchange: no id_token on exchange; failing closed");
|
|
72
|
-
return magicRedirect(context, { error: "verify_failed", next });
|
|
79
|
+
return magicRedirect(context, { error: "verify_failed", tenant, emailHint, next });
|
|
73
80
|
}
|
|
74
81
|
const verifier = await getFederatedVerifier();
|
|
75
82
|
if (!verifier) {
|
|
76
83
|
console.error("auth/magic-exchange: federated verifier unconfigured");
|
|
77
|
-
return magicRedirect(context, { error: "unconfigured", next });
|
|
84
|
+
return magicRedirect(context, { error: "unconfigured", tenant, emailHint, next });
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
let derived;
|
|
@@ -85,7 +92,7 @@ export async function POST(context: APIContext): Promise<Response> {
|
|
|
85
92
|
console.error("auth/magic-exchange: id_token verification failed; failing closed", {
|
|
86
93
|
error: err instanceof Error ? err.message : String(err),
|
|
87
94
|
});
|
|
88
|
-
return magicRedirect(context, { error: "verify_failed", next });
|
|
95
|
+
return magicRedirect(context, { error: "verify_failed", tenant, emailHint, next });
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `GET /api/checkout-style.css` — the stable, per-tenant storefront URL that hosts
|
|
3
3
|
* the published checkout styling as `text/css`. This URL is what U11's publisher
|
|
4
|
-
* points
|
|
5
|
-
*
|
|
4
|
+
* points hosted checkout's `content_url` at and what checkout fetches server-side
|
|
5
|
+
* when it (re)caches the checkout template.
|
|
6
6
|
*
|
|
7
|
-
* The tenant + environment are self-identifying from the request Host (
|
|
7
|
+
* The tenant + environment are self-identifying from the request Host (checkout's
|
|
8
8
|
* fetch carries no session): the middleware resolves `locals.tenant` +
|
|
9
9
|
* `locals.customizationEnv`, and we serve exactly that tenant's published style
|
|
10
10
|
* for that env (live on prod hosts, test behind a preview token). Falls back to
|
|
11
11
|
* the neutral DEFAULT_CHECKOUT_STYLE tokens when nothing is published, so the URL
|
|
12
12
|
* is always a valid, non-empty stylesheet.
|
|
13
13
|
*
|
|
14
|
-
* ASCII-safety (
|
|
14
|
+
* ASCII-safety (checkout cache is not Unicode-safe) is enforced inside
|
|
15
15
|
* buildCheckoutStyleCss. No secrets, no cross-service call — read-only from KV.
|
|
16
16
|
*/
|
|
17
17
|
import type { APIContext } from "astro";
|
|
@@ -34,7 +34,7 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
34
34
|
return new Response(css, {
|
|
35
35
|
headers: {
|
|
36
36
|
"content-type": "text/css; charset=utf-8",
|
|
37
|
-
//
|
|
37
|
+
// Checkout caches on its side via the theme refresh; keep the origin fresh so
|
|
38
38
|
// a re-cache always pulls the latest published styling.
|
|
39
39
|
"cache-control": "no-store",
|
|
40
40
|
"x-robots-tag": "noindex, nofollow",
|
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
* (tot20 sends/verifies the code, the storefront mints the session). No secrets
|
|
11
11
|
* reach the browser. Inline style carries the per-request CSP nonce.
|
|
12
12
|
*/
|
|
13
|
+
import { normalizeEmailHint } from "@/lib/privacy/emailHint";
|
|
14
|
+
|
|
13
15
|
export const prerender = false;
|
|
14
16
|
|
|
15
17
|
const cspNonce = Astro.locals.cspNonce;
|
|
16
18
|
const url = new URL(Astro.request.url);
|
|
17
19
|
const sent = url.searchParams.get("sent") === "1";
|
|
18
20
|
const email = (url.searchParams.get("email") ?? "").trim();
|
|
21
|
+
const emailHint = normalizeEmailHint(url.searchParams.get("email_hint")) ?? "";
|
|
19
22
|
const errorCode = url.searchParams.get("error");
|
|
20
23
|
const next = (() => {
|
|
21
24
|
const n = url.searchParams.get("next") ?? "/";
|
|
@@ -132,7 +135,7 @@ if (devMode) {
|
|
|
132
135
|
) : (
|
|
133
136
|
<>
|
|
134
137
|
<h1>Sign in</h1>
|
|
135
|
-
<p class="sub"><span class="who">Token of Trust© Regulated Storefront</span> is in early access. Enter your email — if you have access, we'll send a one-time code
|
|
138
|
+
<p class="sub"><span class="who">Token of Trust© Regulated Storefront</span> is in early access. Enter your email — if you have access, we'll send a one-time code.{emailHint ? <> Use the invite email hinted as <span class="who">{emailHint}</span>.</> : null}</p>
|
|
136
139
|
<form method="post" action="/api/auth/send">
|
|
137
140
|
<input type="hidden" name="next" value={next} />
|
|
138
141
|
<label for="email">Work email</label>
|