create-ailk 0.21.0 → 0.22.0

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.
Files changed (175) hide show
  1. package/component-catalog.json +37 -3
  2. package/component-catalog.md +6 -5
  3. package/dist/add-module.js +9 -5
  4. package/dist/derive-architecture.d.ts +8 -1
  5. package/dist/derive-architecture.js +38 -3
  6. package/dist/design-package.js +7 -2
  7. package/dist/lib/env-catalog.js +5 -1
  8. package/dist/module-architecture.js +4 -2
  9. package/dist/programmatic.js +19 -5
  10. package/dist/site-chrome.d.ts +8 -0
  11. package/dist/site-chrome.js +17 -1
  12. package/dist/spoke-checkout.d.ts +39 -18
  13. package/dist/spoke-checkout.js +177 -236
  14. package/dist/surfaces.js +19 -14
  15. package/dist/sync-routes.js +3 -2
  16. package/dist/template-record.d.ts +4 -3
  17. package/dist/template-record.js +6 -3
  18. package/dist/topology.js +25 -1
  19. package/dist/update.js +5 -0
  20. package/package.json +3 -3
  21. package/site-templates/designs/minimalism/DESIGN.md +1 -1
  22. package/templates/apps/api/CLAUDE.md +15 -6
  23. package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
  24. package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
  25. package/templates/apps/api/src/lib/consent-store.ts +28 -0
  26. package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
  27. package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
  28. package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
  29. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
  30. package/templates/apps/api/src/middleware/auth.ts +13 -6
  31. package/templates/apps/api/src/openapi/spec.ts +30 -3
  32. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
  33. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
  34. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
  35. package/templates/apps/api/src/routes/consent/account.ts +266 -0
  36. package/templates/apps/api/src/routes/consent/index.ts +15 -7
  37. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
  38. package/templates/apps/api/src/routes/content/get-page.ts +4 -0
  39. package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
  40. package/templates/apps/api/src/routes/leads/index.ts +161 -7
  41. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
  42. package/templates/apps/api/src/routes/webhooks/stripe.ts +154 -19
  43. package/templates/apps/api/src/server.ts +28 -0
  44. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1038 -118
  45. package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
  46. package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
  47. package/templates/apps/api/src/services/consent.ts +95 -0
  48. package/templates/apps/api/src/services/lead-routing.ts +337 -8
  49. package/templates/apps/api/src/services/magnet-completion.ts +242 -0
  50. package/templates/apps/api/src/services/person.ts +40 -8
  51. package/templates/apps/api/src/services/plan-offerings.ts +59 -0
  52. package/templates/apps/api/src/services/public-site-resolver.ts +8 -1
  53. package/templates/apps/web/.env.example +14 -0
  54. package/templates/apps/web/CLAUDE.md +120 -9
  55. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
  56. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
  57. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
  58. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
  59. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
  60. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
  61. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
  62. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
  63. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
  64. package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
  65. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
  66. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
  67. package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
  68. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
  69. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
  70. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
  71. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
  72. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
  73. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
  74. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
  75. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
  76. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
  77. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
  78. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
  79. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
  80. package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
  81. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
  82. package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
  83. package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
  84. package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
  85. package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
  86. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
  87. package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
  88. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +293 -0
  89. package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +115 -0
  90. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +146 -1
  91. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +310 -0
  92. package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +143 -0
  93. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +350 -0
  94. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +29 -3
  95. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  96. package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
  97. package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
  98. package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
  99. package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
  100. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
  101. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +17 -1
  102. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
  103. package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +41 -2
  104. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +438 -31
  105. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +7 -0
  106. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +168 -57
  107. package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
  108. package/templates/apps/web/app/[locale]/contact/page.tsx +49 -17
  109. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
  110. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +1 -1
  111. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
  112. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +106 -1
  113. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
  114. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
  115. package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
  116. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
  117. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  118. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
  119. package/templates/apps/web/app/[locale]/page.tsx +13 -1
  120. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  121. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
  122. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
  123. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
  124. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  125. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  126. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
  127. package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
  128. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
  129. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
  130. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
  131. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  132. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
  133. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
  134. package/templates/apps/web/app/api/checkout/[offering]/route.ts +48 -0
  135. package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +120 -0
  136. package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +86 -0
  137. package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
  138. package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
  139. package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +92 -0
  140. package/templates/apps/web/app/api/webhooks/stripe/route.ts +31 -0
  141. package/templates/apps/web/auth.ts +28 -2
  142. package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
  143. package/templates/apps/web/lib/__tests__/checkout.test.ts +243 -0
  144. package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
  145. package/templates/apps/web/lib/__tests__/deck-page.test.ts +5 -0
  146. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +34 -17
  147. package/templates/apps/web/lib/account-invite.ts +37 -0
  148. package/templates/apps/web/lib/checkout-config.ts +27 -0
  149. package/templates/apps/web/lib/checkout.ts +93 -0
  150. package/templates/apps/web/lib/consent-policies.ts +22 -0
  151. package/templates/apps/web/lib/deck-page.ts +6 -0
  152. package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
  153. package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
  154. package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
  155. package/templates/apps/web/lib/sign-up-agreements.ts +447 -0
  156. package/templates/apps/web/lib/site-chrome.ts +2 -0
  157. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  158. package/templates/apps/web/messages/en.json +24 -7
  159. package/templates/apps/web/package.json +1 -0
  160. package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
  161. package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
  162. package/templates/apps/web/src/lib/lead-write.ts +90 -0
  163. package/templates/apps/web/src/lib/rate-limit.ts +79 -0
  164. package/templates/database/CHANGELOG.md +38 -0
  165. package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
  166. package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
  167. package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
  168. package/templates/database/__tests__/seed.test.ts +34 -1
  169. package/templates/database/inbox/schema.prisma +17 -2
  170. package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
  171. package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
  172. package/templates/database/package.json +1 -1
  173. package/templates/database/seed.ts +34 -3
  174. package/versions.json +34 -34
  175. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
@@ -0,0 +1,111 @@
1
+ /**
2
+ * HubSignInGate (#7894 D10) as #8790 AC-1/AC-2 shape it: the site's inner-page
3
+ * header (back arrow + mark), a label-size heading, the one hub sign-in
4
+ * action, and a prospect line only when the site's messages carry it.
5
+ */
6
+ import { render, screen } from "@testing-library/react";
7
+ import en from "../../../messages/en.json";
8
+ import { HubSignInGate } from "../sign-in-gate";
9
+
10
+ type Gate = Record<string, string>;
11
+ let gateMessages: Gate;
12
+
13
+ jest.mock("next-intl/server", () => ({
14
+ __esModule: true,
15
+ getTranslations: jest.fn(async () => {
16
+ const t = (key: string) => gateMessages[key] ?? `copy:${key}`;
17
+ t.has = (key: string) => key in gateMessages;
18
+ return t;
19
+ }),
20
+ }));
21
+
22
+ const mockConfig = jest.fn();
23
+ // eslint-disable-next-line no-restricted-syntax -- #507: mock-internal rewrite pending; hubSessionConfig() is a pure env read
24
+ jest.mock("../config", () => ({
25
+ __esModule: true,
26
+ hubSessionConfig: () => mockConfig(),
27
+ }));
28
+
29
+ // eslint-disable-next-line no-restricted-syntax -- #507: mock-internal rewrite pending; siteLogo() reads the site's brand files
30
+ jest.mock("../../site-logo", () => ({
31
+ __esModule: true,
32
+ siteLogo: () => (
33
+ <img src="/logomark.svg" alt="Site" data-testid="site-mark" />
34
+ ),
35
+ }));
36
+
37
+ async function renderGate(pathname?: string): Promise<void> {
38
+ const requestHeaders = new Headers();
39
+ if (pathname) requestHeaders.set("x-pathname", pathname);
40
+ render(await HubSignInGate({ locale: "en", requestHeaders }));
41
+ }
42
+
43
+ beforeEach(() => {
44
+ gateMessages = { ...(en.account.gate as Gate) };
45
+ mockConfig.mockReset().mockReturnValue({ clientId: "spoke_1" });
46
+ });
47
+
48
+ describe("<HubSignInGate/> (#8790 AC-1)", () => {
49
+ it("wears the site's inner-page header: a back arrow to the home and the mark", async () => {
50
+ await renderGate("/account");
51
+ const header = screen.getByTestId("account-sign-in-gate-header");
52
+ const back = screen.getByTestId("nav-back-link");
53
+ expect(header).toContainElement(back);
54
+ expect(back).toHaveAttribute("href", "/");
55
+ expect(back).toHaveAccessibleName(en.account.gate.homeLink);
56
+ expect(screen.getByTestId("account-sign-in-gate-mark")).toContainElement(
57
+ screen.getByTestId("site-mark"),
58
+ );
59
+ });
60
+
61
+ it("sets the heading at the label size and offers the one sign-in action", async () => {
62
+ await renderGate("/account/settings");
63
+ const heading = screen.getByRole("heading", { level: 1 });
64
+ expect(heading).toHaveTextContent(en.account.gate.hubHeadline);
65
+ expect(heading).toHaveClass("text-heading-2");
66
+ expect(screen.getByTestId("account-hub-sign-in")).toHaveAttribute(
67
+ "href",
68
+ "/api/hub-session/sign-in?next=%2Faccount%2Fsettings",
69
+ );
70
+ });
71
+
72
+ it("renders no prospect line by default (the shipped messages carry no prospect keys)", async () => {
73
+ await renderGate();
74
+ expect(screen.queryByTestId("account-sign-in-gate-prospect")).toBeNull();
75
+ });
76
+ });
77
+
78
+ describe("<HubSignInGate/> prospect line (#8790 AC-2)", () => {
79
+ it("renders the line and its link when the site fills all three keys", async () => {
80
+ gateMessages.prospectPrompt = "New here?";
81
+ gateMessages.prospectLink = "See if we fit";
82
+ gateMessages.prospectHref = "/fit";
83
+ await renderGate();
84
+ const line = screen.getByTestId("account-sign-in-gate-prospect");
85
+ expect(line).toHaveTextContent("New here? See if we fit.");
86
+ expect(screen.getByRole("link", { name: "See if we fit" })).toHaveAttribute(
87
+ "href",
88
+ "/fit",
89
+ );
90
+ });
91
+
92
+ it("renders nothing when any of the three keys is absent", async () => {
93
+ gateMessages.prospectPrompt = "New here?";
94
+ gateMessages.prospectLink = "See if we fit";
95
+ await renderGate();
96
+ expect(screen.queryByTestId("account-sign-in-gate-prospect")).toBeNull();
97
+ });
98
+
99
+ it("keeps the not-configured state when the hub is not configured", async () => {
100
+ mockConfig.mockReturnValue(null);
101
+ await renderGate();
102
+ expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent(
103
+ en.account.gate.emptyHeadline,
104
+ );
105
+ expect(screen.getByTestId("account-sign-in-gate-home")).toHaveAttribute(
106
+ "href",
107
+ "/",
108
+ );
109
+ expect(screen.queryByTestId("account-hub-sign-in")).toBeNull();
110
+ });
111
+ });
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * HubSignInGate — the signed-out account gate of a spoke that signs in
3
- * through its hub (#7894 D10).
3
+ * through its hub (#7894 D10; #8790 AC-1/AC-2).
4
4
  *
5
5
  * A Server Component, rendered by the `(authed)` layout through the generated
6
6
  * gate seam (`signed-out-gate.generated.tsx`, its `signIn: "hub"` render). It
@@ -9,16 +9,26 @@
9
9
  * link is a plain anchor (the `Link` primitive), so nothing prefetches the
10
10
  * route and starts a flow the visitor did not ask for.
11
11
  *
12
+ * #8790 — the gate reads as one of the site's inner pages, not a bare
13
+ * full-height stop: the site's inner-page header (the back arrow at the start
14
+ * edge, the mark centred — the shape `Nav bar="bare"` draws, composed here
15
+ * from the same `NavBackLink` and `siteLogo()`), a heading at the label size
16
+ * (`heading-2`), and an optional quiet prospect line a site turns on by
17
+ * adding `account.gate.prospectPrompt` + `prospectLink` + `prospectHref`
18
+ * to its messages — absent, the shipped default, renders nothing.
19
+ * The document title is the account page's job (`generateMetadata` there
20
+ * reads the session and names this state "Sign in · <site>").
21
+ *
12
22
  * Not configured (`hubSessionConfig()` is `null`: a client id, secret or
13
23
  * session secret missing), it renders the account area's existing "sign-in
14
24
  * isn't available yet" state, whose one action leads back to the site.
15
25
  */
16
-
17
26
  import { localeHref } from "@working-theory/ui";
27
+ import { NavBackLink } from "@working-theory/ui/chrome";
18
28
  import { Button, Heading, Link, Text } from "@working-theory/ui/primitives";
19
29
  import { getTranslations } from "next-intl/server";
20
30
  import type { ReactElement } from "react";
21
-
31
+ import { siteLogo } from "../site-logo";
22
32
  import { hubSessionConfig } from "./config";
23
33
  import { HUB_SESSION_FALLBACK_PATH } from "./session";
24
34
 
@@ -40,40 +50,86 @@ export async function HubSignInGate({
40
50
  const next =
41
51
  requestHeaders.get("x-pathname") ??
42
52
  localeHref(locale, HUB_SESSION_FALLBACK_PATH);
53
+ const home = localeHref(locale, "/");
54
+ // The three prospect keys are optional extensions of the typed catalog (the
55
+ // shipped en.json carries none — every leaf must be a non-empty string), so
56
+ // presence is the switch; hence the cast on `has`.
57
+ type Key = Parameters<typeof t.has>[0];
58
+ const has = (key: string): boolean => t.has(key as Key);
59
+ const prospect =
60
+ has("prospectPrompt") && has("prospectLink") && has("prospectHref")
61
+ ? {
62
+ prompt: t("prospectPrompt" as Key),
63
+ linkLabel: t("prospectLink" as Key),
64
+ href: t("prospectHref" as Key),
65
+ }
66
+ : null;
43
67
 
44
68
  return (
45
69
  <div
46
- className="flex min-h-screen flex-col items-center justify-center gap-4 py-section text-center"
70
+ className="flex min-h-screen flex-col"
47
71
  data-testid="account-sign-in-gate"
48
72
  >
49
- <Heading level={1} visual={4}>
50
- {configured ? t("hubHeadline") : t("emptyHeadline")}
51
- </Heading>
52
- <Text variant="body-sm" tone="muted" className="max-w-xs">
53
- {configured ? t("hubDescription") : t("emptyDescription")}
54
- </Text>
55
- {configured ? (
56
- // The primary action: 44px on a phone, 36px from md up.
57
- <Button asChild className="max-md:min-h-11">
58
- <Link
59
- href={`${HUB_SIGN_IN_ROUTE}?next=${encodeURIComponent(next)}`}
60
- variant="bare"
61
- data-testid="account-hub-sign-in"
62
- >
63
- {t("hubSignIn")}
64
- </Link>
65
- </Button>
66
- ) : (
67
- <Button asChild variant="secondary" className="max-md:min-h-11">
68
- <Link
69
- href={localeHref(locale, "/")}
70
- variant="bare"
71
- data-testid="account-sign-in-gate-home"
72
- >
73
- {t("homeLink")}
74
- </Link>
75
- </Button>
76
- )}
73
+ {/* The site's inner-page header: back arrow at the start, the mark centred (#8790 AC-1). */}
74
+ <header
75
+ className="grid h-16 grid-cols-[1fr_auto_1fr] items-center px-5 md:px-8"
76
+ data-testid="account-sign-in-gate-header"
77
+ >
78
+ <div className="justify-self-start">
79
+ <NavBackLink href={home} label={t("homeLink")} />
80
+ </div>
81
+ <Link
82
+ href={home}
83
+ variant="unstyled"
84
+ className="inline-flex h-7 items-center [&>img]:h-full [&>img]:w-auto [&>img]:object-contain"
85
+ data-testid="account-sign-in-gate-mark"
86
+ >
87
+ {siteLogo()}
88
+ </Link>
89
+ <div />
90
+ </header>
91
+ <div className="flex flex-1 flex-col items-center justify-center gap-4 py-section text-center">
92
+ <Heading level={1} visual={2}>
93
+ {configured ? t("hubHeadline") : t("emptyHeadline")}
94
+ </Heading>
95
+ <Text variant="body-sm" tone="muted" className="max-w-xs">
96
+ {configured ? t("hubDescription") : t("emptyDescription")}
97
+ </Text>
98
+ {configured ? (
99
+ // The primary action: 44px on a phone, 36px from md up.
100
+ <Button asChild className="max-md:min-h-11">
101
+ <Link
102
+ href={`${HUB_SIGN_IN_ROUTE}?next=${encodeURIComponent(next)}`}
103
+ variant="bare"
104
+ data-testid="account-hub-sign-in"
105
+ >
106
+ {t("hubSignIn")}
107
+ </Link>
108
+ </Button>
109
+ ) : (
110
+ <Button asChild variant="secondary" className="max-md:min-h-11">
111
+ <Link
112
+ href={home}
113
+ variant="bare"
114
+ data-testid="account-sign-in-gate-home"
115
+ >
116
+ {t("homeLink")}
117
+ </Link>
118
+ </Button>
119
+ )}
120
+ {configured && prospect ? (
121
+ // A wrapping div carries the hook: `Text` forwards no data-* attributes.
122
+ <div data-testid="account-sign-in-gate-prospect">
123
+ <Text as="p" variant="body-sm" tone="muted">
124
+ {prospect.prompt}{" "}
125
+ <Link href={prospect.href} variant="inline">
126
+ {prospect.linkLabel}
127
+ </Link>
128
+ .
129
+ </Text>
130
+ </div>
131
+ ) : null}
132
+ </div>
77
133
  </div>
78
134
  );
79
135
  }
@@ -9,6 +9,14 @@
9
9
  * `lib/license-key-store.ts`, which hands `prismaForAuth.siteSetting` to the
10
10
  * licence client's SiteSetting store for the ONE deployment-wide
11
11
  * `license.renewed_key` row (#6677 DD-6 — a narrow, named exception).
12
+ *
13
+ * A third use rides on `auth.ts`'s own (#8765 D4, SEC-8765-K):
14
+ * `lib/sign-up-agreements.ts`'s account hook writes the new user's `sign_up`
15
+ * consent grants through `consentGrant.createMany` on the transaction client
16
+ * Better Auth's sign-up transaction runs on — only rows for the user that
17
+ * transaction is creating, never a read and never any other row. It imports
18
+ * nothing from here; it reaches the client through the adapter.
19
+ *
12
20
  * Every other business-logic caller goes through apps/api, never the
13
21
  * database directly (architecture.yaml `apps/web` may_not_import_from
14
22
  * `database`; the single-file singleton here is the documented exception,