@tokenoftrust/storefront-runner 1.3.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.
Files changed (310) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +42 -0
  3. package/apps/storefront/astro.config.mjs +66 -0
  4. package/apps/storefront/dev-plugins/dev-loop-state.mjs +147 -0
  5. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +238 -0
  6. package/apps/storefront/env.d.ts +162 -0
  7. package/apps/storefront/migrations/0000_baseline.sql +54 -0
  8. package/apps/storefront/migrations/0001_products_fts.sql +9 -0
  9. package/apps/storefront/migrations/README.md +54 -0
  10. package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
  11. package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
  12. package/apps/storefront/migrations/meta/_journal.json +20 -0
  13. package/apps/storefront/package.json +42 -0
  14. package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
  15. package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
  16. package/apps/storefront/public/brand/northwind-og.svg +13 -0
  17. package/apps/storefront/public/favicon.svg +5 -0
  18. package/apps/storefront/public/js/dashboard-team.js +147 -0
  19. package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
  20. package/apps/storefront/public/shared/commerce-chrome.css +369 -0
  21. package/apps/storefront/src/components/Badge.astro +34 -0
  22. package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
  23. package/apps/storefront/src/components/CollectionCard.astro +56 -0
  24. package/apps/storefront/src/components/EmptyState.astro +55 -0
  25. package/apps/storefront/src/components/Img.astro +68 -0
  26. package/apps/storefront/src/components/ProductCard.astro +228 -0
  27. package/apps/storefront/src/components/Section.astro +39 -0
  28. package/apps/storefront/src/components/Seo.astro +70 -0
  29. package/apps/storefront/src/components/WidgetFrame.astro +34 -0
  30. package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +531 -0
  31. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +597 -0
  32. package/apps/storefront/src/components/admin/AdminFoxyCartSettingsPanel.astro +227 -0
  33. package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +465 -0
  34. package/apps/storefront/src/components/admin/AdminProductsTab.astro +901 -0
  35. package/apps/storefront/src/components/admin/AdminReportingTab.astro +435 -0
  36. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +782 -0
  37. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +1223 -0
  38. package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
  39. package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
  40. package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
  41. package/apps/storefront/src/components/chrome/NavDropdown.astro +201 -0
  42. package/apps/storefront/src/components/chrome/SiteFooter.astro +201 -0
  43. package/apps/storefront/src/components/chrome/SiteHeader.astro +234 -0
  44. package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
  45. package/apps/storefront/src/components/commerce/ArticleCard.astro +242 -0
  46. package/apps/storefront/src/components/commerce/BrandGrid.astro +175 -0
  47. package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
  48. package/apps/storefront/src/components/commerce/FoxySidecartStyles.astro +61 -0
  49. package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
  50. package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
  51. package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
  52. package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
  53. package/apps/storefront/src/components/commerce/ReviewList.astro +54 -0
  54. package/apps/storefront/src/components/commerce/ReviewSummary.astro +44 -0
  55. package/apps/storefront/src/components/commerce/TagFilterCloud.astro +149 -0
  56. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +37 -0
  57. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +99 -0
  58. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +45 -0
  59. package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
  60. package/apps/storefront/src/components/compliance/PactActNotice.astro +35 -0
  61. package/apps/storefront/src/components/compliance/Prop65Warning.astro +80 -0
  62. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +38 -0
  63. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +35 -0
  64. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +53 -0
  65. package/apps/storefront/src/components/home/Hero.astro +164 -0
  66. package/apps/storefront/src/components/islands/AgeGate.tsx +109 -0
  67. package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
  68. package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
  69. package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
  70. package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
  71. package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
  72. package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
  73. package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
  74. package/apps/storefront/src/components/islands/VariantSelector.tsx +375 -0
  75. package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
  76. package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
  77. package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
  78. package/apps/storefront/src/components/marketing/Faq.astro +37 -0
  79. package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
  80. package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
  81. package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
  82. package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
  83. package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
  84. package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
  85. package/apps/storefront/src/components/marketing/Steps.astro +36 -0
  86. package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
  87. package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
  88. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +56 -0
  89. package/apps/storefront/src/components/membership/TierCards.astro +164 -0
  90. package/apps/storefront/src/components/membership/TierComparisonTable.astro +148 -0
  91. package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
  92. package/apps/storefront/src/components/plp/FacetSidebar.astro +199 -0
  93. package/apps/storefront/src/components/plp/Pagination.astro +99 -0
  94. package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
  95. package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +95 -0
  96. package/apps/storefront/src/components/subscription/HowItWorksSteps.astro +67 -0
  97. package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +67 -0
  98. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +101 -0
  99. package/apps/storefront/src/config/adminTargetTenant.ts +28 -0
  100. package/apps/storefront/src/config/devTenantSeed.ts +78 -0
  101. package/apps/storefront/src/config/devTenants.ts +16 -0
  102. package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
  103. package/apps/storefront/src/config/resolver.ts +102 -0
  104. package/apps/storefront/src/config/tenants.ts +87 -0
  105. package/apps/storefront/src/layouts/Layout.astro +368 -0
  106. package/apps/storefront/src/lib/admin/README.md +122 -0
  107. package/apps/storefront/src/lib/admin/ai/approvalMode.ts +21 -0
  108. package/apps/storefront/src/lib/admin/ai/askEnvelope.ts +48 -0
  109. package/apps/storefront/src/lib/admin/ai/audit.ts +14 -0
  110. package/apps/storefront/src/lib/admin/ai/index.ts +6 -0
  111. package/apps/storefront/src/lib/admin/ai/operationalRuns.ts +155 -0
  112. package/apps/storefront/src/lib/admin/ai/recommendation.ts +20 -0
  113. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +172 -0
  114. package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +65 -0
  115. package/apps/storefront/src/lib/admin/envelope.ts +10 -0
  116. package/apps/storefront/src/lib/admin/evidence/evidenceReference.ts +32 -0
  117. package/apps/storefront/src/lib/admin/memoizeByEnvelope.ts +27 -0
  118. package/apps/storefront/src/lib/admin/orderClient.ts +146 -0
  119. package/apps/storefront/src/lib/admin/seed.ts +54 -0
  120. package/apps/storefront/src/lib/admin/services/mock/aiWorkflows.ts +59 -0
  121. package/apps/storefront/src/lib/admin/services/mock/customers.ts +86 -0
  122. package/apps/storefront/src/lib/admin/services/mock/evidence.ts +99 -0
  123. package/apps/storefront/src/lib/admin/services/mock/foxyCartSettings.ts +78 -0
  124. package/apps/storefront/src/lib/admin/services/mock/fulfillment.ts +62 -0
  125. package/apps/storefront/src/lib/admin/services/mock/index.ts +12 -0
  126. package/apps/storefront/src/lib/admin/services/mock/orders.ts +184 -0
  127. package/apps/storefront/src/lib/admin/services/mock/products.ts +105 -0
  128. package/apps/storefront/src/lib/admin/services/mock/reporting.ts +51 -0
  129. package/apps/storefront/src/lib/admin/services/mock/settings.ts +70 -0
  130. package/apps/storefront/src/lib/admin/services/mock/tenants.ts +29 -0
  131. package/apps/storefront/src/lib/admin/services/orders.ts +300 -0
  132. package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +67 -0
  133. package/apps/storefront/src/lib/admin/viewmodels/customers.ts +56 -0
  134. package/apps/storefront/src/lib/admin/viewmodels/fulfillment.ts +49 -0
  135. package/apps/storefront/src/lib/admin/viewmodels/orders.ts +92 -0
  136. package/apps/storefront/src/lib/admin/viewmodels/products.ts +74 -0
  137. package/apps/storefront/src/lib/admin/viewmodels/reporting.ts +34 -0
  138. package/apps/storefront/src/lib/admin/viewmodels/settings.ts +64 -0
  139. package/apps/storefront/src/lib/adminTenantEnvelope.ts +90 -0
  140. package/apps/storefront/src/lib/analytics/index.ts +47 -0
  141. package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
  142. package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
  143. package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
  144. package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
  145. package/apps/storefront/src/lib/auth/loginGate.ts +119 -0
  146. package/apps/storefront/src/lib/auth/route.ts +146 -0
  147. package/apps/storefront/src/lib/auth/session.ts +203 -0
  148. package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
  149. package/apps/storefront/src/lib/basePath.ts +41 -0
  150. package/apps/storefront/src/lib/cfImage.ts +55 -0
  151. package/apps/storefront/src/lib/chrome/model.ts +244 -0
  152. package/apps/storefront/src/lib/chrome/parseConfig.ts +34 -0
  153. package/apps/storefront/src/lib/colors.ts +97 -0
  154. package/apps/storefront/src/lib/commerce/foxyCartSettings.ts +253 -0
  155. package/apps/storefront/src/lib/content-edit/applyDiff.ts +91 -0
  156. package/apps/storefront/src/lib/content-edit/client.ts +230 -0
  157. package/apps/storefront/src/lib/content-edit/index.ts +14 -0
  158. package/apps/storefront/src/lib/content-edit/route.ts +25 -0
  159. package/apps/storefront/src/lib/content-edit/types.ts +19 -0
  160. package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
  161. package/apps/storefront/src/lib/d1/catalog.ts +366 -0
  162. package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
  163. package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
  164. package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
  165. package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
  166. package/apps/storefront/src/lib/demoGate.ts +113 -0
  167. package/apps/storefront/src/lib/dev/activityIngestToken.ts +78 -0
  168. package/apps/storefront/src/lib/dev/activityStore.ts +72 -0
  169. package/apps/storefront/src/lib/dev/ai-widget-client.js +308 -0
  170. package/apps/storefront/src/lib/dev/apiBase.ts +14 -0
  171. package/apps/storefront/src/lib/dev/cliSignInCode.ts +155 -0
  172. package/apps/storefront/src/lib/dev/devDraft.ts +133 -0
  173. package/apps/storefront/src/lib/dev/hostedCockpit.ts +169 -0
  174. package/apps/storefront/src/lib/dev/livePreview.ts +77 -0
  175. package/apps/storefront/src/lib/dev/runtimeStore.ts +120 -0
  176. package/apps/storefront/src/lib/dev/storeStatus.ts +142 -0
  177. package/apps/storefront/src/lib/diag/redact.ts +20 -0
  178. package/apps/storefront/src/lib/edgeCache.ts +128 -0
  179. package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
  180. package/apps/storefront/src/lib/env.ts +60 -0
  181. package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
  182. package/apps/storefront/src/lib/format.ts +38 -0
  183. package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
  184. package/apps/storefront/src/lib/imageAttrs.ts +55 -0
  185. package/apps/storefront/src/lib/jsonld.ts +166 -0
  186. package/apps/storefront/src/lib/maintenance.ts +71 -0
  187. package/apps/storefront/src/lib/membership/model.ts +97 -0
  188. package/apps/storefront/src/lib/memoryKv.ts +32 -0
  189. package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
  190. package/apps/storefront/src/lib/placeholder.ts +99 -0
  191. package/apps/storefront/src/lib/previewReload.ts +125 -0
  192. package/apps/storefront/src/lib/priceBuckets.ts +33 -0
  193. package/apps/storefront/src/lib/rawChrome.ts +505 -0
  194. package/apps/storefront/src/lib/rawMarketingHtml.ts +136 -0
  195. package/apps/storefront/src/lib/search/index.ts +74 -0
  196. package/apps/storefront/src/lib/sections.ts +129 -0
  197. package/apps/storefront/src/lib/securityHeaders.ts +31 -0
  198. package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
  199. package/apps/storefront/src/lib/storyblok/provider.ts +607 -0
  200. package/apps/storefront/src/lib/subscription/model.ts +68 -0
  201. package/apps/storefront/src/lib/tenantRouting.ts +91 -0
  202. package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
  203. package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
  204. package/apps/storefront/src/lib/tot/query.ts +249 -0
  205. package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
  206. package/apps/storefront/src/lib/url.ts +142 -0
  207. package/apps/storefront/src/lib/wishlist.ts +63 -0
  208. package/apps/storefront/src/middleware/index.ts +455 -0
  209. package/apps/storefront/src/pages/404.astro +61 -0
  210. package/apps/storefront/src/pages/[...slug].astro +90 -0
  211. package/apps/storefront/src/pages/admin.astro +1675 -0
  212. package/apps/storefront/src/pages/api/admin/orders/[id].ts +53 -0
  213. package/apps/storefront/src/pages/api/admin/orders.ts +71 -0
  214. package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
  215. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
  216. package/apps/storefront/src/pages/api/auth/send.ts +85 -0
  217. package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
  218. package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
  219. package/apps/storefront/src/pages/api/checkout-style.css.ts +43 -0
  220. package/apps/storefront/src/pages/api/cli/policy.ts +70 -0
  221. package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +164 -0
  222. package/apps/storefront/src/pages/api/csp-report.ts +25 -0
  223. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
  224. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
  225. package/apps/storefront/src/pages/api/dev/activity.ts +124 -0
  226. package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +73 -0
  227. package/apps/storefront/src/pages/api/dev/diff.ts +68 -0
  228. package/apps/storefront/src/pages/api/dev/file.ts +57 -0
  229. package/apps/storefront/src/pages/api/dev/locate.ts +125 -0
  230. package/apps/storefront/src/pages/api/dev/publish.ts +128 -0
  231. package/apps/storefront/src/pages/api/dev/tree.ts +70 -0
  232. package/apps/storefront/src/pages/api/dev/write.ts +116 -0
  233. package/apps/storefront/src/pages/api/newsletter.ts +104 -0
  234. package/apps/storefront/src/pages/api/request-access.ts +172 -0
  235. package/apps/storefront/src/pages/auth/login.astro +149 -0
  236. package/apps/storefront/src/pages/auth/magic.astro +105 -0
  237. package/apps/storefront/src/pages/capabilities.astro +292 -0
  238. package/apps/storefront/src/pages/collections/[handle].astro +177 -0
  239. package/apps/storefront/src/pages/collections/index.astro +59 -0
  240. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
  241. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
  242. package/apps/storefront/src/pages/dashboard/index.astro +160 -0
  243. package/apps/storefront/src/pages/index.astro +290 -0
  244. package/apps/storefront/src/pages/llms.txt.ts +92 -0
  245. package/apps/storefront/src/pages/products/[handle].astro +402 -0
  246. package/apps/storefront/src/pages/robots.txt.ts +24 -0
  247. package/apps/storefront/src/pages/saved.astro +35 -0
  248. package/apps/storefront/src/pages/search.astro +150 -0
  249. package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
  250. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +1280 -0
  251. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +229 -0
  252. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +144 -0
  253. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +116 -0
  254. package/apps/storefront/src/pages/style-guide/index.astro +98 -0
  255. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
  256. package/apps/storefront/src/styles/global.css +292 -0
  257. package/apps/storefront/src/themes/reference.ts +68 -0
  258. package/apps/storefront/src/themes/schema.ts +111 -0
  259. package/apps/storefront/src/themes/tenants/index.ts +51 -0
  260. package/apps/storefront/tsconfig.json +16 -0
  261. package/apps/storefront/vitest.config.ts +22 -0
  262. package/package.json +26 -0
  263. package/packages/public-runtime/LICENSE +58 -0
  264. package/packages/public-runtime/package.json +22 -0
  265. package/packages/public-runtime/src/binary-path.ts +68 -0
  266. package/packages/public-runtime/src/capabilities.ts +89 -0
  267. package/packages/public-runtime/src/catalog-api.ts +81 -0
  268. package/packages/public-runtime/src/catalog-d1.ts +192 -0
  269. package/packages/public-runtime/src/checkout-style-publish.ts +200 -0
  270. package/packages/public-runtime/src/checkout-style.ts +375 -0
  271. package/packages/public-runtime/src/csp.ts +109 -0
  272. package/packages/public-runtime/src/customization-preview.ts +197 -0
  273. package/packages/public-runtime/src/customization-reconcile.ts +63 -0
  274. package/packages/public-runtime/src/customization-runtime.ts +157 -0
  275. package/packages/public-runtime/src/customization-scripts.ts +64 -0
  276. package/packages/public-runtime/src/customization-versioning.ts +95 -0
  277. package/packages/public-runtime/src/foxy-orders.ts +136 -0
  278. package/packages/public-runtime/src/foxy.ts +196 -0
  279. package/packages/public-runtime/src/hot-standby-readiness.ts +140 -0
  280. package/packages/public-runtime/src/index.ts +34 -0
  281. package/packages/public-runtime/src/product.ts +159 -0
  282. package/packages/public-runtime/src/search.ts +45 -0
  283. package/packages/public-runtime/src/tenant-assets.ts +444 -0
  284. package/packages/public-runtime/src/tenant.ts +384 -0
  285. package/packages/public-runtime/tsconfig.json +8 -0
  286. package/packages/public-runtime/vitest.config.ts +8 -0
  287. package/pnpm-workspace.yaml +7 -0
  288. package/scripts/build/copy-tenant-assets.mjs +69 -0
  289. package/scripts/dev/ai-edit.mjs +183 -0
  290. package/scripts/dev/checkout-watch.mjs +116 -0
  291. package/scripts/dev/file-browser.mjs +159 -0
  292. package/scripts/dev/file-writer.mjs +141 -0
  293. package/scripts/dev/git-status.mjs +171 -0
  294. package/scripts/dev/locate-handler.mjs +163 -0
  295. package/scripts/dev/locate.mjs +112 -0
  296. package/scripts/dev/port-check.mjs +48 -0
  297. package/scripts/dev/safe-path.mjs +109 -0
  298. package/scripts/dev/shot.mjs +46 -0
  299. package/scripts/dev/tenant-source-root.mjs +76 -0
  300. package/scripts/dev/unified-diff.mjs +81 -0
  301. package/scripts/tot-dev.mjs +376 -0
  302. package/tenants/README.md +14 -0
  303. package/tenants/home/content/chrome.json +7 -0
  304. package/tenants/home/content/home.html +58 -0
  305. package/tenants/home/content/home.json +11 -0
  306. package/tenants/home/public/js/storefront.js +210 -0
  307. package/tenants/home/public/logo-wordmark.png +0 -0
  308. package/tenants/home/public/pages/storefront.css +212 -0
  309. package/tenants/home/theme.json +6 -0
  310. package/tsconfig.base.json +20 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * POST /api/request-access — early-access lead capture for the `home` landing page.
3
+ *
4
+ * The browser form (tenants/home) POSTs here same-origin (CSP `connect-src 'self'`
5
+ * + `form-action 'self'`). This route forwards the lead to tot20's generic
6
+ * messaging endpoint SERVER-SIDE — the storefront's ToT credentials never reach
7
+ * the browser. The message emails the Trello Success board so a card is created.
8
+ *
9
+ * tot20 auth wire: like `@/lib/auth/totAccessClient`, tot20's swagger reads
10
+ * machine creds from the request BODY fields `totApiKey`/`totSecretKey` (NOT
11
+ * headers). So we merge those into the JSON POST body.
12
+ *
13
+ * GRACEFUL FALLBACK: the tot20 `/api/messages` endpoint is being built in a
14
+ * parallel workstream (Claim A) and isn't live yet. Until it is — or whenever the
15
+ * send fails / creds are unset — we still return `{ ok: true }` with a
16
+ * `fallback: "mailto"` + a prefilled `mailto:` so the client opens the user's mail
17
+ * client to the board. The form is never a dead end. Once A lands + creds are set,
18
+ * the server-side send just works and no mailto is returned.
19
+ *
20
+ * Deliberately NOT gated on `context.locals.features.*` — unlike newsletter.ts,
21
+ * this must work on the `home` marketing tenant.
22
+ */
23
+ import type { APIContext } from "astro";
24
+ import { readEnv } from "@/lib/env";
25
+
26
+ export const prerender = false;
27
+
28
+ // Trello Success board — a card is created from the forwarded email.
29
+ // Kept in sync with the mailto: fallback in tenants/home/public/js/storefront.js.
30
+ const BOARD_EMAIL = "darrin84+l6gajmvvoo1x4edew5qs@boards.trello.com";
31
+
32
+ // Permissive email shape — tot20 / the mail system is the real deliverability authority.
33
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
34
+
35
+ interface RequestAccessFields {
36
+ email: string;
37
+ firstName: string;
38
+ lastName: string;
39
+ phone: string;
40
+ details: string;
41
+ }
42
+
43
+ function json(body: unknown, status = 200): Response {
44
+ return new Response(JSON.stringify(body), {
45
+ status,
46
+ headers: { "content-type": "application/json; charset=utf-8" },
47
+ });
48
+ }
49
+
50
+ /** Read fields from either a JSON body (the fetch form) or urlencoded/multipart (no-JS). */
51
+ async function readFields(request: Request): Promise<RequestAccessFields> {
52
+ const ct = request.headers.get("content-type") || "";
53
+ const pick = (v: unknown) => String(v ?? "").trim();
54
+ if (ct.includes("application/json")) {
55
+ const b = (await request.json().catch(() => ({}))) as Record<string, unknown>;
56
+ return {
57
+ email: pick(b.email),
58
+ firstName: pick(b.firstName),
59
+ lastName: pick(b.lastName),
60
+ phone: pick(b.phone),
61
+ details: pick(b.details),
62
+ };
63
+ }
64
+ const form = await request.formData();
65
+ return {
66
+ email: pick(form.get("email")),
67
+ firstName: pick(form.get("firstName")),
68
+ lastName: pick(form.get("lastName")),
69
+ phone: pick(form.get("phone")),
70
+ details: pick(form.get("details")),
71
+ };
72
+ }
73
+
74
+ /** Escape user text before it lands in the HTML email body. */
75
+ function esc(s: string): string {
76
+ return s
77
+ .replace(/&/g, "&amp;")
78
+ .replace(/</g, "&lt;")
79
+ .replace(/>/g, "&gt;")
80
+ .replace(/"/g, "&quot;");
81
+ }
82
+
83
+ function buildHtml(f: RequestAccessFields): string {
84
+ const rows: Array<[string, string]> = [
85
+ ["Name", `${f.firstName} ${f.lastName}`],
86
+ ["Email", f.email],
87
+ ];
88
+ if (f.phone) rows.push(["Phone", f.phone]);
89
+ if (f.details) rows.push(["What they sell", f.details]);
90
+ const body = rows
91
+ .map(([k, v]) => `<p style="margin:0 0 8px"><strong>${esc(k)}:</strong> ${esc(v)}</p>`)
92
+ .join("\n");
93
+ return `<div style="font-family:system-ui,sans-serif;line-height:1.5">
94
+ <h2 style="margin:0 0 12px">Storefront early-access request</h2>
95
+ ${body}
96
+ <hr style="margin:16px 0;border:none;border-top:1px solid #ddd">
97
+ <p style="color:#888;font-size:12px;margin:0">Sent from the Storefront landing page (tokenoftrust.com).</p>
98
+ </div>`;
99
+ }
100
+
101
+ /** Prefilled mailto: to the board — the fallback so the lead is never dropped. */
102
+ function mailtoFallback(f: RequestAccessFields): string {
103
+ const lines = [
104
+ `First name: ${f.firstName}`,
105
+ `Last name: ${f.lastName}`,
106
+ `Email: ${f.email}`,
107
+ f.phone ? `Phone: ${f.phone}` : null,
108
+ f.details ? `\nDetails:\n${f.details}` : null,
109
+ ]
110
+ .filter(Boolean)
111
+ .join("\n");
112
+ const subject = `Early-access request — ${f.firstName} ${f.lastName}`;
113
+ return `mailto:${BOARD_EMAIL}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(lines)}`;
114
+ }
115
+
116
+ export async function POST(context: APIContext): Promise<Response> {
117
+ let f: RequestAccessFields;
118
+ try {
119
+ f = await readFields(context.request);
120
+ } catch {
121
+ return json({ ok: false, error: "invalid_body" }, 400);
122
+ }
123
+
124
+ // Validate: email + first + last required.
125
+ if (!EMAIL_RE.test(f.email) || !f.firstName || !f.lastName) {
126
+ return json({ ok: false, error: "missing_required_fields" }, 400);
127
+ }
128
+
129
+ const subject = `Early-access request — ${f.firstName} ${f.lastName}`;
130
+
131
+ // Try the server-side send via tot20. Any shortfall (unconfigured creds,
132
+ // endpoint not live yet, non-2xx, network error) degrades to the mailto fallback.
133
+ const baseUrl = (await readEnv("TOT_CORE_URL"))?.replace(/\/+$/, "");
134
+ const apiKey = await readEnv("TOT_API_KEY");
135
+ const secretKey = await readEnv("TOT_SECRET_KEY");
136
+
137
+ if (baseUrl && apiKey) {
138
+ try {
139
+ const res = await fetch(`${baseUrl}/api/messages`, {
140
+ method: "POST",
141
+ headers: { "content-type": "application/json", accept: "application/json" },
142
+ body: JSON.stringify({
143
+ totApiKey: apiKey,
144
+ ...(secretKey ? { totSecretKey: secretKey } : {}),
145
+ channel: "email",
146
+ to: BOARD_EMAIL,
147
+ subject,
148
+ replyTo: f.email,
149
+ html: buildHtml(f),
150
+ }),
151
+ });
152
+ if (res.ok) {
153
+ return json({ ok: true });
154
+ }
155
+ console.warn(`[request-access] tot20 /api/messages returned ${res.status}; using mailto fallback`);
156
+ } catch (err) {
157
+ console.warn("[request-access] tot20 /api/messages send failed; using mailto fallback", {
158
+ error: err instanceof Error ? err.message : String(err),
159
+ });
160
+ }
161
+ } else {
162
+ console.log("[request-access] TOT_CORE_URL/TOT_API_KEY unset; using mailto fallback");
163
+ }
164
+
165
+ // Fallback: acknowledge success and hand the client a prefilled mailto so the
166
+ // request still reaches the board via the user's own mail client.
167
+ return json({ ok: true, fallback: "mailto", mailto: mailtoFallback(f) });
168
+ }
169
+
170
+ export async function GET(context: APIContext): Promise<Response> {
171
+ return context.redirect("/", 303);
172
+ }
@@ -0,0 +1,149 @@
1
+ ---
2
+ /**
3
+ * /auth/login — "Sign in with Token of Trust" for a gated preview host.
4
+ *
5
+ * Two-step, no-JS-friendly, all on the tenant's own branded host:
6
+ * step 1: enter your email → POST /api/auth/send
7
+ * step 2: enter the 6-digit code you were emailed → POST /api/auth/verify
8
+ *
9
+ * The page renders instructions + forms only; every auth decision is server-side
10
+ * (tot20 sends/verifies the code, the storefront mints the session). No secrets
11
+ * reach the browser. Inline style carries the per-request CSP nonce.
12
+ */
13
+ export const prerender = false;
14
+
15
+ const cspNonce = Astro.locals.cspNonce;
16
+ const url = new URL(Astro.request.url);
17
+ const sent = url.searchParams.get("sent") === "1";
18
+ const email = (url.searchParams.get("email") ?? "").trim();
19
+ const errorCode = url.searchParams.get("error");
20
+ const next = (() => {
21
+ const n = url.searchParams.get("next") ?? "/";
22
+ return n.startsWith("/") && !n.startsWith("//") ? n : "/";
23
+ })();
24
+
25
+ const ERROR_MESSAGES: Record<string, string> = {
26
+ bad_email: "That email doesn't look right — please try again.",
27
+ unconfigured: "Sign-in isn't available right now. Please try again shortly.",
28
+ invalid: "That code didn't match. Check it and try again.",
29
+ expired: "That code has expired — request a fresh one.",
30
+ too_many_attempts: "Too many attempts. Request a new code to continue.",
31
+ verify_failed: "We couldn't verify your sign-in. Please request a fresh code and try again.",
32
+ };
33
+ const errorMessage = errorCode ? (ERROR_MESSAGES[errorCode] ?? "Something went wrong — try again.") : null;
34
+ // A stale/failed code should drop back to the email step so a new code can be requested.
35
+ const showCodeStep = sent && errorCode !== "expired" && errorCode !== "too_many_attempts";
36
+ // Local-dev banner (statically dropped from prod). The message depends on the mode:
37
+ // DEV_AUTH=1 → the console bypass is active (no email; code printed to the server console);
38
+ // DEV_AUTH=0 → the REAL path is active (a code IS emailed by the configured tot20/qa, but
39
+ // only to an address that already holds a role). Read the toggle so the banner never lies.
40
+ const devMode = import.meta.env.DEV;
41
+ let devAuthOn = false;
42
+ if (devMode) {
43
+ const { readEnv } = await import("@/lib/env");
44
+ devAuthOn = (await readEnv("DEV_AUTH")) === "1";
45
+ }
46
+ ---
47
+
48
+ <html lang="en">
49
+ <head>
50
+ <meta charset="utf-8" />
51
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
52
+ <meta name="robots" content="noindex, nofollow" />
53
+ <title>Sign in — Regulated Storefront by Token of Trust©</title>
54
+ <style nonce={cspNonce}>
55
+ :root { color-scheme: light dark; }
56
+ * { box-sizing: border-box; }
57
+ body {
58
+ font: 15px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
59
+ margin: 0; min-height: 100vh; display: grid; place-items: center;
60
+ padding: 2rem 1.25rem; background: #f5f4f0; color: #1c1c1c;
61
+ }
62
+ @media (prefers-color-scheme: dark) {
63
+ body { background: #14141a; color: #ececf1; }
64
+ }
65
+ .card {
66
+ width: 100%; max-width: 26rem; background: #fff; border: 1px solid #e6e4de;
67
+ border-radius: 14px; padding: 2rem 1.75rem; box-shadow: 0 6px 24px rgba(0,0,0,.06);
68
+ }
69
+ @media (prefers-color-scheme: dark) {
70
+ .card { background: #1e1e28; border-color: #33333f; box-shadow: none; }
71
+ }
72
+ .eyebrow { font: 600 .68rem/1 ui-monospace, monospace; letter-spacing: .12em;
73
+ text-transform: uppercase; color: #8a7f66; margin: 0 0 .75rem; }
74
+ h1 { font-size: 1.4rem; letter-spacing: -.01em; margin: 0 0 .4rem; }
75
+ .sub { color: #6a6a6a; margin: 0 0 1.5rem; font-size: .95rem; }
76
+ @media (prefers-color-scheme: dark) { .sub { color: #a2a2ad; } }
77
+ label { display: block; font-weight: 600; font-size: .82rem; margin: 0 0 .4rem; }
78
+ input[type=email], input[type=text] {
79
+ width: 100%; padding: .7rem .8rem; font-size: 1rem; border-radius: 9px;
80
+ border: 1px solid #cfccc4; background: #fff; color: inherit;
81
+ }
82
+ @media (prefers-color-scheme: dark) {
83
+ input[type=email], input[type=text] { background: #14141a; border-color: #40404d; }
84
+ }
85
+ input[type=text].code { letter-spacing: .4em; font: 600 1.25rem ui-monospace, monospace; text-align: center; }
86
+ button {
87
+ width: 100%; margin-top: 1rem; padding: .75rem 1rem; border: 0; border-radius: 9px;
88
+ background: #1a1a1a; color: #fff; font-weight: 600; font-size: .95rem; cursor: pointer;
89
+ }
90
+ @media (prefers-color-scheme: dark) { button { background: #ececf1; color: #14141a; } }
91
+ button:hover { opacity: .92; }
92
+ .err { background: #fdecec; border: 1px solid #f5c2c2; color: #9a1c1c;
93
+ border-radius: 9px; padding: .6rem .8rem; font-size: .88rem; margin: 0 0 1.15rem; }
94
+ @media (prefers-color-scheme: dark) { .err { background: #3a1f22; border-color: #6b2f34; color: #f6b9b9; } }
95
+ .dev { background: #eef4ff; border: 1px solid #c3d4f5; color: #234; border-radius: 9px;
96
+ padding: .6rem .8rem; font-size: .82rem; margin: 0 0 1.15rem; }
97
+ .dev code { font: 600 .8em ui-monospace, monospace; }
98
+ @media (prefers-color-scheme: dark) { .dev { background: #1b2740; border-color: #2f4468; color: #cfe0ff; } }
99
+ .note { font-size: .82rem; color: #8a8a8a; margin: 1.1rem 0 0; }
100
+ .who { font-weight: 600; }
101
+ a { color: inherit; }
102
+ .foot { margin-top: 1.5rem; font-size: .72rem; color: #9a9a9a; text-align: center; }
103
+ </style>
104
+ </head>
105
+ <body>
106
+ <main class="card">
107
+ <p class="eyebrow">Token of Trust© · Regulated Storefront</p>
108
+ {devMode && (devAuthOn ? (
109
+ <p class="dev"><strong>Local dev — console bypass</strong> (<code>DEV_AUTH=1</code>). No email is sent;
110
+ your sign-in code is printed to the <strong>server console</strong>. Set <code>DEV_AUTH=0</code> to use real emails.</p>
111
+ ) : (
112
+ <p class="dev"><strong>Local dev — live email</strong> (<code>DEV_AUTH=0</code>). A code is emailed by the
113
+ configured tot20 (<code>TOT_CORE_URL</code>), but only to an address that already holds a role. Set
114
+ <code>DEV_AUTH=1</code> for offline console codes.</p>
115
+ ))}
116
+ {errorMessage && <p class="err">{errorMessage}</p>}
117
+
118
+ {showCodeStep ? (
119
+ <>
120
+ <h1>Check your email</h1>
121
+ <p class="sub">We sent a 6-digit code to <span class="who">{email}</span> if it has access to this preview. Enter it below.</p>
122
+ <form method="post" action="/api/auth/verify" autocomplete="one-time-code">
123
+ <input type="hidden" name="email" value={email} />
124
+ <input type="hidden" name="next" value={next} />
125
+ <label for="code">Access code</label>
126
+ <input class="code" id="code" name="code" type="text" inputmode="numeric"
127
+ pattern="[0-9]*" maxlength="6" autocomplete="one-time-code" required autofocus />
128
+ <button type="submit">Verify &amp; view preview</button>
129
+ </form>
130
+ <p class="note"><a href={`/auth/login${next !== "/" ? `?next=${encodeURIComponent(next)}` : ""}`}>Use a different email</a> · <a href="https://mcp.tokenoftrust.com/early-access/storefront">Don't have access? Request early access →</a></p>
131
+ </>
132
+ ) : (
133
+ <>
134
+ <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.</p>
136
+ <form method="post" action="/api/auth/send">
137
+ <input type="hidden" name="next" value={next} />
138
+ <label for="email">Work email</label>
139
+ <input id="email" name="email" type="email" value={email}
140
+ autocomplete="email" placeholder="you@company.com" required autofocus />
141
+ <button type="submit">Email me a code</button>
142
+ </form>
143
+ <p class="note">Access is granted by role — no signup needed. Don't have access yet? <a href="https://mcp.tokenoftrust.com/early-access/storefront">Request early access →</a></p>
144
+ </>
145
+ )}
146
+ <p class="foot">Token of Trust© Regulated Storefront</p>
147
+ </main>
148
+ </body>
149
+ </html>
@@ -0,0 +1,105 @@
1
+ ---
2
+ /**
3
+ * /auth/magic — GET-safe landing for the magic-link sign-in.
4
+ *
5
+ * SECURITY INVARIANT: a GET of this page renders a "Confirm sign in" card and
6
+ * **consumes nothing** — the one-time token is only exchanged when the human clicks
7
+ * Confirm, which POSTs to /api/auth/magic-exchange. Inbox "safe-link" scanners fire
8
+ * GET on every URL; if this page auto-exchanged on GET, a scanner would silently burn
9
+ * the token before the user ever clicked. So: NEVER exchange on GET. The token rides
10
+ * in a hidden form field and leaves only via the POST.
11
+ */
12
+ export const prerender = false;
13
+
14
+ const cspNonce = Astro.locals.cspNonce;
15
+ const url = new URL(Astro.request.url);
16
+ const token = url.searchParams.get("token") ?? "";
17
+ const email = (url.searchParams.get("email") ?? "").trim(); // display-only (never trusted)
18
+ const errorCode = url.searchParams.get("error");
19
+ const next = (() => {
20
+ const n = url.searchParams.get("next") ?? "/dashboard";
21
+ return n.startsWith("/") && !n.startsWith("//") ? n : "/dashboard";
22
+ })();
23
+
24
+ const ERROR_MESSAGES: Record<string, string> = {
25
+ invalid: "This sign-in link isn't valid or has already been used. Ask for a fresh invite.",
26
+ expired: "This sign-in link has expired. Ask for a fresh invite.",
27
+ used: "This sign-in link was already used. Ask for a fresh invite.",
28
+ missing: "This sign-in link is missing its token.",
29
+ unconfigured: "Sign-in isn't available right now. Please try again shortly.",
30
+ verify_failed: "We couldn't complete sign-in. Ask for a fresh invite and try again.",
31
+ };
32
+ // On GET with a token → confirm card. On GET with ?error → error card. No token, no
33
+ // error → treat as a broken link.
34
+ const errorMessage = errorCode
35
+ ? (ERROR_MESSAGES[errorCode] ?? "Something went wrong — ask for a fresh invite.")
36
+ : token
37
+ ? null
38
+ : ERROR_MESSAGES.missing;
39
+ ---
40
+
41
+ <html lang="en">
42
+ <head>
43
+ <meta charset="utf-8" />
44
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
45
+ <meta name="robots" content="noindex, nofollow" />
46
+ <title>Confirm sign in — Regulated Storefront by Token of Trust©</title>
47
+ <style nonce={cspNonce}>
48
+ :root { color-scheme: light dark; }
49
+ * { box-sizing: border-box; }
50
+ body {
51
+ font: 15px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
52
+ margin: 0; min-height: 100vh; display: grid; place-items: center;
53
+ padding: 2rem 1.25rem; background: #f5f4f0; color: #1c1c1c;
54
+ }
55
+ @media (prefers-color-scheme: dark) { body { background: #14141a; color: #ececf1; } }
56
+ .card {
57
+ width: 100%; max-width: 26rem; background: #fff; border: 1px solid #e6e4de;
58
+ border-radius: 14px; padding: 2rem 1.75rem; box-shadow: 0 6px 24px rgba(0,0,0,.06);
59
+ }
60
+ @media (prefers-color-scheme: dark) { .card { background: #1e1e28; border-color: #33333f; box-shadow: none; } }
61
+ .eyebrow { font: 600 .68rem/1 ui-monospace, monospace; letter-spacing: .12em;
62
+ text-transform: uppercase; color: #8a7f66; margin: 0 0 .75rem; }
63
+ h1 { font-size: 1.4rem; letter-spacing: -.01em; margin: 0 0 .4rem; }
64
+ .sub { color: #6a6a6a; margin: 0 0 1.5rem; font-size: .95rem; }
65
+ @media (prefers-color-scheme: dark) { .sub { color: #a2a2ad; } }
66
+ .who { font-weight: 600; }
67
+ button {
68
+ width: 100%; margin-top: .5rem; padding: .8rem 1rem; border: 0; border-radius: 9px;
69
+ background: #1a1a1a; color: #fff; font-weight: 600; font-size: 1rem; cursor: pointer;
70
+ }
71
+ @media (prefers-color-scheme: dark) { button { background: #ececf1; color: #14141a; } }
72
+ button:hover { opacity: .92; }
73
+ .err { background: #fdecec; border: 1px solid #f5c2c2; color: #9a1c1c;
74
+ border-radius: 9px; padding: .7rem .85rem; font-size: .9rem; margin: 0 0 1rem; }
75
+ @media (prefers-color-scheme: dark) { .err { background: #3a1f22; border-color: #6b2f34; color: #f6b9b9; } }
76
+ .note { font-size: .78rem; color: #9a9a9a; margin: 1.1rem 0 0; text-align: center; }
77
+ a { color: inherit; }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <main class="card">
82
+ <p class="eyebrow">Token of Trust© · Regulated Storefront</p>
83
+ {errorMessage ? (
84
+ <>
85
+ <h1>Sign-in link problem</h1>
86
+ <p class="err">{errorMessage}</p>
87
+ <p class="note"><a href="/auth/login">Sign in with a code instead →</a></p>
88
+ </>
89
+ ) : (
90
+ <>
91
+ <h1>Confirm sign in</h1>
92
+ <p class="sub">{email
93
+ ? <>Continue as <span class="who">{email}</span> to the Regulated Storefront dashboard.</>
94
+ : <>Continue to the Regulated Storefront dashboard.</>}</p>
95
+ <form method="post" action="/api/auth/magic-exchange">
96
+ <input type="hidden" name="token" value={token} />
97
+ <input type="hidden" name="next" value={next} />
98
+ <button type="submit">Confirm sign in</button>
99
+ </form>
100
+ <p class="note">This is a single-use link — confirming completes your sign-in.</p>
101
+ </>
102
+ )}
103
+ </main>
104
+ </body>
105
+ </html>