@tokenoftrust/storefront-runner 1.3.2 → 1.3.4-rc.2
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 +59 -0
- package/apps/storefront/env.d.ts +24 -8
- package/apps/storefront/package.json +1 -1
- 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 +193 -22
- 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 +6 -2
- 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/pnpm-workspace.yaml +1 -0
- package/scripts/dev/checkout-watch.mjs +9 -0
- package/scripts/dev/file-browser.mjs +5 -1
- package/scripts/dev/transient-files.mjs +34 -0
- package/scripts/tot-dev.mjs +16 -3
- 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
|
@@ -9,33 +9,66 @@
|
|
|
9
9
|
* the token before the user ever clicked. So: NEVER exchange on GET. The token rides
|
|
10
10
|
* in a hidden form field and leaves only via the POST.
|
|
11
11
|
*/
|
|
12
|
+
import { normalizeEmailHint, redactEmailForHint } from "@/lib/privacy/emailHint";
|
|
13
|
+
|
|
12
14
|
export const prerender = false;
|
|
13
15
|
|
|
14
16
|
const cspNonce = Astro.locals.cspNonce;
|
|
15
17
|
const url = new URL(Astro.request.url);
|
|
16
18
|
const token = url.searchParams.get("token") ?? "";
|
|
17
|
-
const
|
|
19
|
+
const legacyEmail = (url.searchParams.get("email") ?? "").trim(); // legacy display hint only
|
|
20
|
+
const emailHint =
|
|
21
|
+
normalizeEmailHint(url.searchParams.get("email_hint")) ??
|
|
22
|
+
redactEmailForHint(legacyEmail) ??
|
|
23
|
+
"";
|
|
24
|
+
if (url.searchParams.has("email")) {
|
|
25
|
+
const canonical = new URLSearchParams(url.searchParams);
|
|
26
|
+
canonical.delete("email");
|
|
27
|
+
if (emailHint) canonical.set("email_hint", emailHint);
|
|
28
|
+
return Astro.redirect(
|
|
29
|
+
`/auth/magic${canonical.toString() ? `?${canonical.toString()}` : ""}`,
|
|
30
|
+
302,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
const tenant = (url.searchParams.get("tenant") ?? "").trim().toLowerCase();
|
|
34
|
+
// Destination context for the confirm card. appDomain always rides in `tenant`; the
|
|
35
|
+
// friendly title is optional. Display-only and bounded — Astro escapes on render, so
|
|
36
|
+
// this is XSS-safe; the cap just keeps a hostile/overlong value from breaking layout.
|
|
37
|
+
const appDomain = tenant;
|
|
38
|
+
const appTitle = (url.searchParams.get("app_title") ?? "").trim().slice(0, 80);
|
|
39
|
+
const appLabel = appTitle || appDomain; // primary destination name to show, if any
|
|
18
40
|
const errorCode = url.searchParams.get("error");
|
|
41
|
+
const resendStatus = url.searchParams.get("resend");
|
|
19
42
|
const next = (() => {
|
|
20
43
|
const n = url.searchParams.get("next") ?? "/dashboard";
|
|
21
44
|
return n.startsWith("/") && !n.startsWith("//") ? n : "/dashboard";
|
|
22
45
|
})();
|
|
23
46
|
|
|
24
47
|
const ERROR_MESSAGES: Record<string, string> = {
|
|
25
|
-
invalid: "This sign-in link isn't valid or has already been used.
|
|
26
|
-
expired: "This sign-in link has expired.
|
|
27
|
-
used: "This sign-in link was already used.
|
|
48
|
+
invalid: "This sign-in link isn't valid or has already been used.",
|
|
49
|
+
expired: "This sign-in link has expired.",
|
|
50
|
+
used: "This sign-in link was already used.",
|
|
28
51
|
missing: "This sign-in link is missing its token.",
|
|
29
52
|
unconfigured: "Sign-in isn't available right now. Please try again shortly.",
|
|
30
|
-
verify_failed: "We couldn't complete sign-in.
|
|
53
|
+
verify_failed: "We couldn't complete sign-in.",
|
|
54
|
+
};
|
|
55
|
+
const RESEND_MESSAGES: Record<string, string> = {
|
|
56
|
+
invalid: "Enter the email address from your invite and try again.",
|
|
57
|
+
unavailable: "We couldn't resend a link right now. Please try again shortly.",
|
|
31
58
|
};
|
|
32
59
|
// On GET with a token → confirm card. On GET with ?error → error card. No token, no
|
|
33
60
|
// error → treat as a broken link.
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
61
|
+
const resendMessage = resendStatus ? (RESEND_MESSAGES[resendStatus] ?? null) : null;
|
|
62
|
+
const errorMessage = resendStatus === "sent"
|
|
63
|
+
? null
|
|
64
|
+
: resendMessage
|
|
65
|
+
? resendMessage
|
|
66
|
+
: errorCode
|
|
67
|
+
? (ERROR_MESSAGES[errorCode] ?? "Something went wrong.")
|
|
68
|
+
: token
|
|
69
|
+
? null
|
|
70
|
+
: ERROR_MESSAGES.missing;
|
|
71
|
+
const showResendForm = !!errorMessage && !!tenant && resendStatus !== "sent";
|
|
39
72
|
---
|
|
40
73
|
|
|
41
74
|
<html lang="en">
|
|
@@ -44,60 +77,247 @@ const errorMessage = errorCode
|
|
|
44
77
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
45
78
|
<meta name="robots" content="noindex, nofollow" />
|
|
46
79
|
<title>Confirm sign in — Regulated Storefront by Token of Trust©</title>
|
|
80
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
81
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
82
|
+
<link
|
|
83
|
+
rel="stylesheet"
|
|
84
|
+
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@600;700;800&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@500;600&display=swap"
|
|
85
|
+
/>
|
|
47
86
|
<style nonce={cspNonce}>
|
|
48
|
-
|
|
87
|
+
/* Signed-in developer cockpit design system — the sign-in door matches the
|
|
88
|
+
room it opens into (apps/storefront/src/pages/cockpit.astro). Theme via the
|
|
89
|
+
token triple: :root default light, @media dark, and [data-theme] overrides. */
|
|
90
|
+
:root {
|
|
91
|
+
--bg: #f7f9fc; --surface: #ffffff;
|
|
92
|
+
--ink: #0a2540; --ink-2: #33415b; --muted: #55637a; --faint: #8593a8;
|
|
93
|
+
--accent: #0e9f8e; --accent-ink: #0b7a6d; --accent-wash: #e7f6f4; --accent-line: #b8e6df;
|
|
94
|
+
--border: #e3e8ef;
|
|
95
|
+
--warn: #c23b3b; --warn-wash: #fbecec; --warn-line: #f0cccc;
|
|
96
|
+
--sh: 0 10px 34px -14px rgba(10, 37, 64, .22);
|
|
97
|
+
--display: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
98
|
+
--body: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
99
|
+
--mono: "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Code", Menlo, monospace;
|
|
100
|
+
color-scheme: light;
|
|
101
|
+
}
|
|
102
|
+
@media (prefers-color-scheme: dark) {
|
|
103
|
+
:root {
|
|
104
|
+
--bg: #071320; --surface: #0e2134;
|
|
105
|
+
--ink: #eaf1f8; --ink-2: #bdccdc; --muted: #90a3b7; --faint: #6c8098;
|
|
106
|
+
--accent: #2fd4bf; --accent-ink: #7fe7da; --accent-wash: #0f2e30; --accent-line: #1e514b;
|
|
107
|
+
--border: #1b3348;
|
|
108
|
+
--warn: #f2857f; --warn-wash: #2a1414; --warn-line: #5a2626;
|
|
109
|
+
--sh: 0 18px 44px -22px rgba(0, 0, 0, .7);
|
|
110
|
+
color-scheme: dark;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
:root[data-theme="dark"] {
|
|
114
|
+
--bg: #071320; --surface: #0e2134;
|
|
115
|
+
--ink: #eaf1f8; --ink-2: #bdccdc; --muted: #90a3b7; --faint: #6c8098;
|
|
116
|
+
--accent: #2fd4bf; --accent-ink: #7fe7da; --accent-wash: #0f2e30; --accent-line: #1e514b;
|
|
117
|
+
--border: #1b3348;
|
|
118
|
+
--warn: #f2857f; --warn-wash: #2a1414; --warn-line: #5a2626;
|
|
119
|
+
--sh: 0 18px 44px -22px rgba(0, 0, 0, .7);
|
|
120
|
+
color-scheme: dark;
|
|
121
|
+
}
|
|
122
|
+
:root[data-theme="light"] {
|
|
123
|
+
--bg: #f7f9fc; --surface: #ffffff;
|
|
124
|
+
--ink: #0a2540; --ink-2: #33415b; --muted: #55637a; --faint: #8593a8;
|
|
125
|
+
--accent: #0e9f8e; --accent-ink: #0b7a6d; --accent-wash: #e7f6f4; --accent-line: #b8e6df;
|
|
126
|
+
--border: #e3e8ef;
|
|
127
|
+
--warn: #c23b3b; --warn-wash: #fbecec; --warn-line: #f0cccc;
|
|
128
|
+
--sh: 0 10px 34px -14px rgba(10, 37, 64, .22);
|
|
129
|
+
color-scheme: light;
|
|
130
|
+
}
|
|
131
|
+
|
|
49
132
|
* { box-sizing: border-box; }
|
|
50
133
|
body {
|
|
51
|
-
font: 15px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
52
134
|
margin: 0; min-height: 100vh; display: grid; place-items: center;
|
|
53
|
-
padding: 2rem 1.25rem;
|
|
135
|
+
padding: 2rem 1.25rem;
|
|
136
|
+
background:
|
|
137
|
+
radial-gradient(34rem 18rem at 92% -8%, var(--accent-wash) 0%, transparent 60%),
|
|
138
|
+
var(--bg);
|
|
139
|
+
background-repeat: no-repeat;
|
|
140
|
+
color: var(--ink-2); font-family: var(--body); font-size: 15px; line-height: 1.6;
|
|
141
|
+
letter-spacing: -.011em; -webkit-font-smoothing: antialiased;
|
|
54
142
|
}
|
|
55
|
-
@media (prefers-color-scheme: dark) { body { background: #14141a; color: #ececf1; } }
|
|
56
143
|
.card {
|
|
57
|
-
width: 100%; max-width: 26rem; background:
|
|
58
|
-
border
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
144
|
+
width: 100%; max-width: 26rem; background: var(--surface);
|
|
145
|
+
border: 1px solid var(--accent-line); border-radius: 20px;
|
|
146
|
+
padding: 2.1rem 1.9rem 1.9rem; position: relative; overflow: hidden;
|
|
147
|
+
box-shadow: var(--sh); animation: rise .5s cubic-bezier(.2, .9, .3, 1) both;
|
|
148
|
+
}
|
|
149
|
+
.card::before {
|
|
150
|
+
content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
|
|
151
|
+
background: linear-gradient(90deg, var(--accent), var(--accent-line));
|
|
152
|
+
}
|
|
153
|
+
@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
|
|
154
|
+
|
|
155
|
+
.mark {
|
|
156
|
+
width: 46px; height: 46px; border-radius: 13px; display: grid; place-items: center;
|
|
157
|
+
color: #fff; background: linear-gradient(150deg, var(--accent), var(--accent-ink));
|
|
158
|
+
box-shadow: 0 6px 18px -6px rgba(14, 159, 142, .75); position: relative;
|
|
159
|
+
}
|
|
160
|
+
.mark::after {
|
|
161
|
+
content: ""; position: absolute; inset: -5px; border-radius: 17px;
|
|
162
|
+
border: 1.5px solid var(--accent); opacity: 0; animation: halo 2.8s ease-in-out infinite;
|
|
163
|
+
}
|
|
164
|
+
@keyframes halo { 0%, 100% { opacity: 0; transform: scale(.96); } 50% { opacity: .5; transform: scale(1.04); } }
|
|
165
|
+
.mark svg { width: 24px; height: 24px; }
|
|
166
|
+
|
|
167
|
+
.eyebrow {
|
|
168
|
+
font-family: var(--mono); font-size: .66rem; font-weight: 600; text-transform: uppercase;
|
|
169
|
+
letter-spacing: .13em; color: var(--accent-ink); margin: 1.15rem 0 .55rem;
|
|
170
|
+
}
|
|
171
|
+
h1 {
|
|
172
|
+
font-family: var(--display); font-weight: 800; letter-spacing: -.025em; color: var(--ink);
|
|
173
|
+
font-size: 1.55rem; margin: 0 0 .5rem; text-wrap: balance;
|
|
174
|
+
}
|
|
175
|
+
.sub { color: var(--muted); font-size: .95rem; line-height: 1.55; margin: 0 0 1.1rem; }
|
|
176
|
+
|
|
177
|
+
/* identity chip — reassures which inbox this link was sent to. The hint is
|
|
178
|
+
display-only / never trusted; the token governs the sign-in. */
|
|
179
|
+
.ident {
|
|
180
|
+
display: flex; align-items: center; gap: .65rem; margin: 0 0 1.5rem;
|
|
181
|
+
padding: .55rem .7rem; border: 1px solid var(--border); border-radius: 12px; background: var(--bg);
|
|
182
|
+
}
|
|
183
|
+
.ident .ava {
|
|
184
|
+
width: 30px; height: 30px; border-radius: 50%; flex: none; display: grid; place-items: center;
|
|
185
|
+
font-family: var(--display); font-weight: 700; font-size: .85rem; color: #fff; text-transform: uppercase;
|
|
186
|
+
background: linear-gradient(150deg, var(--accent), var(--accent-ink));
|
|
187
|
+
}
|
|
188
|
+
.ident .ava svg { width: 15px; height: 15px; }
|
|
189
|
+
.ident-txt { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
|
|
190
|
+
.ident-lbl { font-family: var(--mono); font-size: .6rem; text-transform: uppercase; letter-spacing: .12em; color: var(--faint); }
|
|
191
|
+
.ident-val { font-size: .9rem; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
192
|
+
.ident-dom { font-family: var(--mono); font-size: .72rem; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
193
|
+
|
|
194
|
+
form { margin: 0; }
|
|
195
|
+
.cta {
|
|
196
|
+
width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
|
|
197
|
+
font-family: var(--display); font-weight: 700; font-size: 1rem; color: #fff; background: var(--accent);
|
|
198
|
+
border: 0; border-radius: 999px; padding: .9rem 1.3rem; cursor: pointer;
|
|
199
|
+
box-shadow: 0 10px 26px -10px rgba(14, 159, 142, .85);
|
|
200
|
+
transition: transform .12s, background .12s, box-shadow .12s;
|
|
201
|
+
}
|
|
202
|
+
.cta:hover { transform: translateY(-1px); background: var(--accent-ink); box-shadow: 0 14px 30px -10px rgba(14, 159, 142, .95); }
|
|
203
|
+
.cta:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
204
|
+
.cta svg { width: 17px; height: 17px; }
|
|
205
|
+
|
|
206
|
+
.note {
|
|
207
|
+
display: flex; align-items: center; justify-content: center; gap: .4rem;
|
|
208
|
+
font-family: var(--mono); font-size: .74rem; color: var(--faint);
|
|
209
|
+
margin: 1.15rem 0 0; text-align: center;
|
|
210
|
+
}
|
|
211
|
+
.note svg { width: 12px; height: 12px; flex: none; opacity: .8; }
|
|
212
|
+
|
|
213
|
+
.err {
|
|
214
|
+
display: flex; gap: .55rem; align-items: flex-start; background: var(--warn-wash);
|
|
215
|
+
border: 1px solid var(--warn-line); color: var(--warn); border-radius: 11px;
|
|
216
|
+
padding: .75rem .9rem; font-size: .9rem; line-height: 1.5; margin: 0 0 .2rem;
|
|
217
|
+
}
|
|
218
|
+
.err svg { width: 16px; height: 16px; flex: none; margin-top: .1rem; }
|
|
219
|
+
.resend {
|
|
220
|
+
display: grid; gap: .75rem; margin: 1rem 0 0;
|
|
221
|
+
}
|
|
222
|
+
.field {
|
|
223
|
+
display: grid; gap: .35rem;
|
|
224
|
+
}
|
|
225
|
+
.hint { margin: -.2rem 0 0; color: var(--muted); font-size: .84rem; }
|
|
226
|
+
label {
|
|
227
|
+
font-family: var(--mono); font-size: .66rem; font-weight: 600; text-transform: uppercase;
|
|
228
|
+
letter-spacing: .12em; color: var(--faint);
|
|
229
|
+
}
|
|
230
|
+
input[type="email"] {
|
|
231
|
+
width: 100%; border: 1px solid var(--border); border-radius: 11px;
|
|
232
|
+
padding: .8rem .85rem; color: var(--ink); background: var(--bg);
|
|
233
|
+
font: 600 .95rem/1.35 var(--body);
|
|
234
|
+
}
|
|
235
|
+
input[type="email"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
236
|
+
.altrow { margin: 1rem 0 0; }
|
|
237
|
+
.altlink {
|
|
238
|
+
font-family: var(--display); font-weight: 600; font-size: .9rem;
|
|
239
|
+
color: var(--accent-ink); text-decoration: none;
|
|
240
|
+
}
|
|
241
|
+
.altlink:hover { text-decoration: underline; }
|
|
242
|
+
.altlink:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
|
|
243
|
+
|
|
244
|
+
@media (prefers-reduced-motion: reduce) {
|
|
245
|
+
.card, .mark::after { animation: none !important; }
|
|
246
|
+
.cta { transition: none; }
|
|
247
|
+
}
|
|
78
248
|
</style>
|
|
79
249
|
</head>
|
|
80
250
|
<body>
|
|
81
251
|
<main class="card">
|
|
252
|
+
<span class="mark" aria-hidden="true">
|
|
253
|
+
<svg viewBox="0 0 24 24" fill="none"><path d="M12 2.5 20 6v6c0 4.6-3.2 7.9-8 9.5-4.8-1.6-8-4.9-8-9.5V6l8-3.5Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="m8.5 12 2.4 2.4L15.8 9.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
254
|
+
</span>
|
|
82
255
|
<p class="eyebrow">Token of Trust© · Regulated Storefront</p>
|
|
83
|
-
{
|
|
256
|
+
{resendStatus === "sent" ? (
|
|
257
|
+
<>
|
|
258
|
+
<h1>Check your email</h1>
|
|
259
|
+
<p class="sub">If that address is still on this store's invite list, a fresh sign-in link is on the way.</p>
|
|
260
|
+
<p class="altrow"><a class="altlink" href="/auth/login?next=%2Fcockpit">Sign in with a code instead →</a></p>
|
|
261
|
+
</>
|
|
262
|
+
) : errorMessage ? (
|
|
84
263
|
<>
|
|
85
264
|
<h1>Sign-in link problem</h1>
|
|
86
|
-
<p class="err">
|
|
87
|
-
|
|
265
|
+
<p class="err">
|
|
266
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/><path d="M12 7.5v5M12 16h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
|
|
267
|
+
<span>{errorMessage}</span>
|
|
268
|
+
</p>
|
|
269
|
+
{showResendForm ? (
|
|
270
|
+
<form class="resend" method="post" action="/api/grants/magic-resend">
|
|
271
|
+
<input type="hidden" name="tenant" value={tenant} />
|
|
272
|
+
<input type="hidden" name="next" value={next} />
|
|
273
|
+
<div class="field">
|
|
274
|
+
<label for="resend-email">Invite email</label>
|
|
275
|
+
<input id="resend-email" type="email" name="email" autocomplete="email" required />
|
|
276
|
+
</div>
|
|
277
|
+
{emailHint ? <p class="hint">Hint: {emailHint}</p> : null}
|
|
278
|
+
<button class="cta" type="submit">
|
|
279
|
+
Send a fresh link
|
|
280
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 6.5h16v11H4z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="m4.5 7 7.5 5.5L19.5 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
281
|
+
</button>
|
|
282
|
+
</form>
|
|
283
|
+
) : (
|
|
284
|
+
<p class="altrow"><a class="altlink" href="/auth/login?next=%2Fcockpit">Sign in with a code instead →</a></p>
|
|
285
|
+
)}
|
|
88
286
|
</>
|
|
89
287
|
) : (
|
|
90
288
|
<>
|
|
91
289
|
<h1>Confirm sign in</h1>
|
|
92
|
-
<p class="sub">
|
|
93
|
-
|
|
94
|
-
|
|
290
|
+
<p class="sub">Confirm it's you to finish signing in.</p>
|
|
291
|
+
{appLabel ? (
|
|
292
|
+
<div class="ident">
|
|
293
|
+
<span class="ava" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none"><path d="M4 9.5 5.2 5h13.6L20 9.5M4 9.5V19h16V9.5M4 9.5h16M9 19v-5h6v5" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg></span>
|
|
294
|
+
<span class="ident-txt">
|
|
295
|
+
<span class="ident-lbl">Signing in to</span>
|
|
296
|
+
<span class="ident-val" title={appLabel}>{appLabel}</span>
|
|
297
|
+
{appTitle && appDomain && appTitle !== appDomain ? <span class="ident-dom" title={appDomain}>{appDomain}</span> : null}
|
|
298
|
+
</span>
|
|
299
|
+
</div>
|
|
300
|
+
) : null}
|
|
301
|
+
{emailHint ? (
|
|
302
|
+
<div class="ident">
|
|
303
|
+
<span class="ava" aria-hidden="true">{emailHint.slice(0, 1)}</span>
|
|
304
|
+
<span class="ident-txt" aria-label={`Invite sent to ${emailHint}`}><span class="ident-lbl">Invite sent to </span><span class="ident-val" title={emailHint}>{emailHint}</span></span>
|
|
305
|
+
</div>
|
|
306
|
+
) : null}
|
|
95
307
|
<form method="post" action="/api/auth/magic-exchange">
|
|
96
308
|
<input type="hidden" name="token" value={token} />
|
|
309
|
+
<input type="hidden" name="tenant" value={tenant} />
|
|
310
|
+
<input type="hidden" name="email_hint" value={emailHint} />
|
|
97
311
|
<input type="hidden" name="next" value={next} />
|
|
98
|
-
<button type="submit">
|
|
312
|
+
<button class="cta" type="submit">
|
|
313
|
+
Confirm sign in
|
|
314
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
315
|
+
</button>
|
|
99
316
|
</form>
|
|
100
|
-
<p class="note">
|
|
317
|
+
<p class="note">
|
|
318
|
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="5" y="11" width="14" height="9" rx="2" stroke="currentColor" stroke-width="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3" stroke="currentColor" stroke-width="2"/></svg>
|
|
319
|
+
This is a single-use link — confirming completes your sign-in.
|
|
320
|
+
</p>
|
|
101
321
|
</>
|
|
102
322
|
)}
|
|
103
323
|
</main>
|
|
@@ -63,7 +63,7 @@ interface CapMeta {
|
|
|
63
63
|
track: string;
|
|
64
64
|
}
|
|
65
65
|
const CAPS: CapMeta[] = [
|
|
66
|
-
{ key: "cartCheckout", name: "Cart & Checkout", sub: "
|
|
66
|
+
{ key: "cartCheckout", name: "Cart & Checkout", sub: "Managed checkout · Track A", track: "commerce" },
|
|
67
67
|
{ key: "ageVerification", name: "Age & ID Verification", sub: "Token of Trust · 21+", track: "compliance" },
|
|
68
68
|
{ key: "exciseTax", name: "Excise Tax Collection", sub: "at checkout · Track B", track: "compliance" },
|
|
69
69
|
];
|