@tokenoftrust/storefront-runner 0.1.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 (337) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +40 -0
  3. package/apps/storefront/.dev.vars.example +41 -0
  4. package/apps/storefront/.env.example +24 -0
  5. package/apps/storefront/astro.config.mjs +245 -0
  6. package/apps/storefront/env.d.ts +155 -0
  7. package/apps/storefront/lib/ensure-preview-reconcile-secret.sh +31 -0
  8. package/apps/storefront/lib/resolve-cf-token.sh +48 -0
  9. package/apps/storefront/lib/resolve-tot-credentials.sh +69 -0
  10. package/apps/storefront/migrations/0000_baseline.sql +54 -0
  11. package/apps/storefront/migrations/0001_products_fts.sql +9 -0
  12. package/apps/storefront/migrations/README.md +54 -0
  13. package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
  14. package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
  15. package/apps/storefront/migrations/meta/_journal.json +20 -0
  16. package/apps/storefront/package.json +41 -0
  17. package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
  18. package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
  19. package/apps/storefront/public/brand/northwind-og.svg +13 -0
  20. package/apps/storefront/public/favicon.svg +5 -0
  21. package/apps/storefront/public/js/dashboard-team.js +147 -0
  22. package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
  23. package/apps/storefront/src/components/Badge.astro +34 -0
  24. package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
  25. package/apps/storefront/src/components/CollectionCard.astro +56 -0
  26. package/apps/storefront/src/components/EmptyState.astro +35 -0
  27. package/apps/storefront/src/components/Img.astro +68 -0
  28. package/apps/storefront/src/components/ProductCard.astro +228 -0
  29. package/apps/storefront/src/components/Section.astro +39 -0
  30. package/apps/storefront/src/components/Seo.astro +70 -0
  31. package/apps/storefront/src/components/WidgetFrame.astro +34 -0
  32. package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
  33. package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
  34. package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
  35. package/apps/storefront/src/components/chrome/NavDropdown.astro +116 -0
  36. package/apps/storefront/src/components/chrome/SiteFooter.astro +181 -0
  37. package/apps/storefront/src/components/chrome/SiteHeader.astro +181 -0
  38. package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
  39. package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
  40. package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
  41. package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
  42. package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
  43. package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
  44. package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
  45. package/apps/storefront/src/components/home/Hero.astro +164 -0
  46. package/apps/storefront/src/components/islands/AgeGate.tsx +105 -0
  47. package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
  48. package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
  49. package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
  50. package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
  51. package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
  52. package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
  53. package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
  54. package/apps/storefront/src/components/islands/VariantSelector.tsx +378 -0
  55. package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
  56. package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
  57. package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
  58. package/apps/storefront/src/components/marketing/Faq.astro +37 -0
  59. package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
  60. package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
  61. package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
  62. package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
  63. package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
  64. package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
  65. package/apps/storefront/src/components/marketing/Steps.astro +36 -0
  66. package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
  67. package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
  68. package/apps/storefront/src/components/nav/Footer.astro +148 -0
  69. package/apps/storefront/src/components/nav/Header.astro +190 -0
  70. package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
  71. package/apps/storefront/src/components/plp/FacetSidebar.astro +146 -0
  72. package/apps/storefront/src/components/plp/Pagination.astro +99 -0
  73. package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
  74. package/apps/storefront/src/config/devTenants.ts +16 -0
  75. package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
  76. package/apps/storefront/src/config/resolver.ts +46 -0
  77. package/apps/storefront/src/config/tenants.ts +193 -0
  78. package/apps/storefront/src/layouts/Layout.astro +303 -0
  79. package/apps/storefront/src/lib/analytics/index.ts +47 -0
  80. package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
  81. package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
  82. package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
  83. package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
  84. package/apps/storefront/src/lib/auth/loginGate.ts +107 -0
  85. package/apps/storefront/src/lib/auth/route.ts +146 -0
  86. package/apps/storefront/src/lib/auth/session.ts +203 -0
  87. package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
  88. package/apps/storefront/src/lib/basePath.ts +41 -0
  89. package/apps/storefront/src/lib/cfImage.ts +55 -0
  90. package/apps/storefront/src/lib/chrome/model.ts +65 -0
  91. package/apps/storefront/src/lib/colors.ts +97 -0
  92. package/apps/storefront/src/lib/content-edit/client.ts +230 -0
  93. package/apps/storefront/src/lib/content-edit/index.ts +14 -0
  94. package/apps/storefront/src/lib/content-edit/route.ts +25 -0
  95. package/apps/storefront/src/lib/content-edit/types.ts +19 -0
  96. package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
  97. package/apps/storefront/src/lib/d1/catalog.ts +289 -0
  98. package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
  99. package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
  100. package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
  101. package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
  102. package/apps/storefront/src/lib/demoGate.ts +113 -0
  103. package/apps/storefront/src/lib/dev/ai-widget-client.js +127 -0
  104. package/apps/storefront/src/lib/dev/cliSignInCode.ts +132 -0
  105. package/apps/storefront/src/lib/diag/redact.ts +20 -0
  106. package/apps/storefront/src/lib/edgeCache.ts +128 -0
  107. package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
  108. package/apps/storefront/src/lib/env.ts +60 -0
  109. package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
  110. package/apps/storefront/src/lib/format.ts +29 -0
  111. package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
  112. package/apps/storefront/src/lib/imageAttrs.ts +55 -0
  113. package/apps/storefront/src/lib/jsonld.ts +136 -0
  114. package/apps/storefront/src/lib/maintenance.ts +71 -0
  115. package/apps/storefront/src/lib/memoryKv.ts +32 -0
  116. package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
  117. package/apps/storefront/src/lib/placeholder.ts +99 -0
  118. package/apps/storefront/src/lib/previewReload.ts +125 -0
  119. package/apps/storefront/src/lib/rawMarketingHtml.ts +133 -0
  120. package/apps/storefront/src/lib/search/index.ts +74 -0
  121. package/apps/storefront/src/lib/sections.ts +129 -0
  122. package/apps/storefront/src/lib/securityHeaders.ts +31 -0
  123. package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
  124. package/apps/storefront/src/lib/storyblok/provider.ts +524 -0
  125. package/apps/storefront/src/lib/tenantRouting.ts +91 -0
  126. package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
  127. package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
  128. package/apps/storefront/src/lib/tot/query.ts +187 -0
  129. package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
  130. package/apps/storefront/src/lib/url.ts +119 -0
  131. package/apps/storefront/src/lib/wishlist.ts +63 -0
  132. package/apps/storefront/src/middleware/index.ts +413 -0
  133. package/apps/storefront/src/pages/404.astro +61 -0
  134. package/apps/storefront/src/pages/[...slug].astro +90 -0
  135. package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
  136. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
  137. package/apps/storefront/src/pages/api/auth/send.ts +85 -0
  138. package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
  139. package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
  140. package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +64 -0
  141. package/apps/storefront/src/pages/api/csp-report.ts +25 -0
  142. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
  143. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
  144. package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +44 -0
  145. package/apps/storefront/src/pages/api/newsletter.ts +104 -0
  146. package/apps/storefront/src/pages/api/request-access.ts +172 -0
  147. package/apps/storefront/src/pages/auth/login.astro +149 -0
  148. package/apps/storefront/src/pages/auth/magic.astro +105 -0
  149. package/apps/storefront/src/pages/capabilities.astro +292 -0
  150. package/apps/storefront/src/pages/collections/[handle].astro +154 -0
  151. package/apps/storefront/src/pages/collections/index.astro +59 -0
  152. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
  153. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
  154. package/apps/storefront/src/pages/dashboard/index.astro +160 -0
  155. package/apps/storefront/src/pages/dev.astro +653 -0
  156. package/apps/storefront/src/pages/index.astro +290 -0
  157. package/apps/storefront/src/pages/llms.txt.ts +92 -0
  158. package/apps/storefront/src/pages/products/[handle].astro +443 -0
  159. package/apps/storefront/src/pages/robots.txt.ts +24 -0
  160. package/apps/storefront/src/pages/saved.astro +35 -0
  161. package/apps/storefront/src/pages/search.astro +150 -0
  162. package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
  163. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +646 -0
  164. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +85 -0
  165. package/apps/storefront/src/pages/style-guide/index.astro +94 -0
  166. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
  167. package/apps/storefront/src/styles/global.css +277 -0
  168. package/apps/storefront/src/themes/reference.ts +61 -0
  169. package/apps/storefront/src/themes/schema.ts +111 -0
  170. package/apps/storefront/src/themes/tenants/index.ts +51 -0
  171. package/apps/storefront/tsconfig.json +16 -0
  172. package/apps/storefront/vitest.config.ts +22 -0
  173. package/apps/storefront/wrangler.toml +175 -0
  174. package/package.json +23 -0
  175. package/packages/public-runtime/LICENSE +58 -0
  176. package/packages/public-runtime/package.json +22 -0
  177. package/packages/public-runtime/src/binary-path.ts +68 -0
  178. package/packages/public-runtime/src/capabilities.ts +89 -0
  179. package/packages/public-runtime/src/catalog-api.ts +81 -0
  180. package/packages/public-runtime/src/catalog-d1.ts +192 -0
  181. package/packages/public-runtime/src/csp.ts +109 -0
  182. package/packages/public-runtime/src/customization-preview.ts +197 -0
  183. package/packages/public-runtime/src/customization-reconcile.ts +63 -0
  184. package/packages/public-runtime/src/customization-runtime.ts +157 -0
  185. package/packages/public-runtime/src/customization-scripts.ts +64 -0
  186. package/packages/public-runtime/src/customization-versioning.ts +95 -0
  187. package/packages/public-runtime/src/foxy.ts +184 -0
  188. package/packages/public-runtime/src/index.ts +30 -0
  189. package/packages/public-runtime/src/product.ts +159 -0
  190. package/packages/public-runtime/src/search.ts +45 -0
  191. package/packages/public-runtime/src/tenant-assets.ts +444 -0
  192. package/packages/public-runtime/src/tenant.ts +243 -0
  193. package/packages/public-runtime/tsconfig.json +8 -0
  194. package/packages/public-runtime/vitest.config.ts +8 -0
  195. package/pnpm-workspace.yaml +7 -0
  196. package/scripts/build/copy-tenant-assets.mjs +69 -0
  197. package/scripts/tot-dev.mjs +439 -0
  198. package/tenants/bigdvapor/content/blog.json +52 -0
  199. package/tenants/bigdvapor/content/chrome.html +149 -0
  200. package/tenants/bigdvapor/content/chrome.json +47 -0
  201. package/tenants/bigdvapor/content/home.html +463 -0
  202. package/tenants/bigdvapor/content/home.json +73 -0
  203. package/tenants/bigdvapor/content/pages/about.json +5 -0
  204. package/tenants/bigdvapor/content/pages/privacy.json +6 -0
  205. package/tenants/bigdvapor/content/pages/shipping-returns.json +6 -0
  206. package/tenants/bigdvapor/content/pages-html/blogs/news.html +86 -0
  207. package/tenants/bigdvapor/content/pages-html/pages/about-us.html +106 -0
  208. package/tenants/bigdvapor/content/pages-html/pages/contact-us.html +61 -0
  209. package/tenants/bigdvapor/content/pages-html/pages/privacy-policy.html +72 -0
  210. package/tenants/bigdvapor/content/pages-html/pages/shipping-returns.html +172 -0
  211. package/tenants/bigdvapor/content/themes/bigd-navy.json +109 -0
  212. package/tenants/bigdvapor/public/img/brands/adjust-vape-disposable.png +0 -0
  213. package/tenants/bigdvapor/public/img/brands/again-vape-disposable.png +0 -0
  214. package/tenants/bigdvapor/public/img/brands/al-fakher-vape-disposable.png +0 -0
  215. package/tenants/bigdvapor/public/img/brands/arro-vape-disposable.png +0 -0
  216. package/tenants/bigdvapor/public/img/brands/cookies-vape-disposable.png +0 -0
  217. package/tenants/bigdvapor/public/img/brands/core-vape-disposable.png +0 -0
  218. package/tenants/bigdvapor/public/img/brands/cyclone-vape-disposable.png +0 -0
  219. package/tenants/bigdvapor/public/img/brands/dinner-lady-vape-disposable.png +0 -0
  220. package/tenants/bigdvapor/public/img/brands/extre-bar-vape-disposable.png +0 -0
  221. package/tenants/bigdvapor/public/img/brands/fifty-bar-vape-disposable.jpg +0 -0
  222. package/tenants/bigdvapor/public/img/brands/firerose-vape-disposable.jpg +0 -0
  223. package/tenants/bigdvapor/public/img/brands/flavor-beast-vape-disposable.jpg +0 -0
  224. package/tenants/bigdvapor/public/img/brands/foger-vape-disposable.png +0 -0
  225. package/tenants/bigdvapor/public/img/brands/kk-energy-vape-disposable.png +0 -0
  226. package/tenants/bigdvapor/public/img/brands/kumi-vape-disposable.png +0 -0
  227. package/tenants/bigdvapor/public/img/brands/lost-vape-dispoable.png +0 -0
  228. package/tenants/bigdvapor/public/img/brands/melo-labs-vape-disposable.png +0 -0
  229. package/tenants/bigdvapor/public/img/brands/mixo-vape-disposable.webp +0 -0
  230. package/tenants/bigdvapor/public/img/brands/one-tank-vape-disposable.jpg +0 -0
  231. package/tenants/bigdvapor/public/img/brands/oxbar-vape-disposable.webp +0 -0
  232. package/tenants/bigdvapor/public/img/brands/pillow-talk-vape-disposable.jpg +0 -0
  233. package/tenants/bigdvapor/public/img/brands/pod-king-vape-disposable.jpg +0 -0
  234. package/tenants/bigdvapor/public/img/brands/raz-vape-disposable.png +0 -0
  235. package/tenants/bigdvapor/public/img/hero-texas.jpg +0 -0
  236. package/tenants/bigdvapor/public/img/og.jpg +0 -0
  237. package/tenants/bigdvapor/public/img/pages/AdobeStock_202106312_web.jpg +0 -0
  238. package/tenants/bigdvapor/public/img/pages/CREW_web.jpg +0 -0
  239. package/tenants/bigdvapor/public/img/pages/Hero-Image.jpg +0 -0
  240. package/tenants/bigdvapor/public/img/tiles/adjust.png +0 -0
  241. package/tenants/bigdvapor/public/img/tiles/again.png +0 -0
  242. package/tenants/bigdvapor/public/img/tiles/al-fakher.png +0 -0
  243. package/tenants/bigdvapor/public/img/tiles/arro.png +0 -0
  244. package/tenants/bigdvapor/public/img/tiles/cookies.png +0 -0
  245. package/tenants/bigdvapor/public/img/tiles/core.png +0 -0
  246. package/tenants/bigdvapor/public/img/tiles/cyclone.png +0 -0
  247. package/tenants/bigdvapor/public/img/tiles/disposables.svg +23 -0
  248. package/tenants/bigdvapor/public/logo-wordmark.png +0 -0
  249. package/tenants/bigdvapor/public/pages/home.css +118 -0
  250. package/tenants/bigdvapor/public/pages/mkt.css +185 -0
  251. package/tenants/bigdvapor/public/pages/page.css +148 -0
  252. package/tenants/bigdvapor/public/themes/bigd-navy.css +73 -0
  253. package/tenants/bigdvapor/theme.json +12 -0
  254. package/tenants/giantvapes/content/chrome.html +152 -0
  255. package/tenants/giantvapes/content/chrome.json +94 -0
  256. package/tenants/giantvapes/content/home.html +194 -0
  257. package/tenants/giantvapes/content/home.json +50 -0
  258. package/tenants/giantvapes/content/pages/about.json +10 -0
  259. package/tenants/giantvapes/content/pages/privacy.json +6 -0
  260. package/tenants/giantvapes/content/pages/shipping-returns.json +6 -0
  261. package/tenants/giantvapes/content/pages-html/blogs/news.html +68 -0
  262. package/tenants/giantvapes/content/pages-html/pages/about-us.html +95 -0
  263. package/tenants/giantvapes/content/pages-html/pages/contact-us.html +68 -0
  264. package/tenants/giantvapes/content/pages-html/pages/privacy-policy.html +65 -0
  265. package/tenants/giantvapes/content/pages-html/pages/shipping-returns.html +77 -0
  266. package/tenants/giantvapes/content/themes/giant-navy.json +73 -0
  267. package/tenants/giantvapes/public/img/hero-suicide-bunny.jpg +0 -0
  268. package/tenants/giantvapes/public/img/hero.webp +0 -0
  269. package/tenants/giantvapes/public/img/og.jpg +0 -0
  270. package/tenants/giantvapes/public/logo-wordmark.png +0 -0
  271. package/tenants/giantvapes/public/pages/home.css +120 -0
  272. package/tenants/giantvapes/public/pages/mkt.css +185 -0
  273. package/tenants/giantvapes/public/pages/page.css +155 -0
  274. package/tenants/giantvapes/public/themes/giant-navy.css +76 -0
  275. package/tenants/giantvapes/theme.json +39 -0
  276. package/tenants/home/content/chrome.json +11 -0
  277. package/tenants/home/content/home.html +304 -0
  278. package/tenants/home/content/home.json +13 -0
  279. package/tenants/home/public/js/storefront.js +210 -0
  280. package/tenants/home/public/logo-wordmark.png +0 -0
  281. package/tenants/home/public/pages/storefront.css +212 -0
  282. package/tenants/home/theme.json +6 -0
  283. package/tenants/tokenoftrust/.tot/config.json +9 -0
  284. package/tenants/tokenoftrust/content/chrome.json +55 -0
  285. package/tenants/tokenoftrust/content/home.html +723 -0
  286. package/tenants/tokenoftrust/content/home.json +233 -0
  287. package/tenants/tokenoftrust/content/pages-html/careers.html +231 -0
  288. package/tenants/tokenoftrust/content/pages-html/company.html +275 -0
  289. package/tenants/tokenoftrust/content/pages-html/contact/contact-sales.html +188 -0
  290. package/tenants/tokenoftrust/content/pages-html/contact/startup-apply.html +206 -0
  291. package/tenants/tokenoftrust/content/pages-html/contact.html +178 -0
  292. package/tenants/tokenoftrust/content/pages-html/industries/adult-content-age-verification.html +286 -0
  293. package/tenants/tokenoftrust/content/pages-html/industries/cannabis.html +277 -0
  294. package/tenants/tokenoftrust/content/pages-html/pact-act-pricing.html +289 -0
  295. package/tenants/tokenoftrust/content/pages-html/pricing.html +483 -0
  296. package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-estimation.html +278 -0
  297. package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-gate.html +279 -0
  298. package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-verification.html +265 -0
  299. package/tenants/tokenoftrust/content/pages-html/product/age-assurance.html +173 -0
  300. package/tenants/tokenoftrust/content/pages-html/product/compliance/aml.html +214 -0
  301. package/tenants/tokenoftrust/content/pages-html/product/compliance/pact-act.html +226 -0
  302. package/tenants/tokenoftrust/content/pages-html/product/fraud-prevention.html +254 -0
  303. package/tenants/tokenoftrust/content/pages-html/product/social-profile-verification.html +212 -0
  304. package/tenants/tokenoftrust/content/pages-html/product/tax/excise-tax.html +213 -0
  305. package/tenants/tokenoftrust/content/pages-html/product/verification/aml.html +259 -0
  306. package/tenants/tokenoftrust/content/pages-html/product/verification/biometric-selfie.html +305 -0
  307. package/tenants/tokenoftrust/content/pages-html/product/verification/coverage.html +254 -0
  308. package/tenants/tokenoftrust/content/pages-html/product/verification/document-verification.html +306 -0
  309. package/tenants/tokenoftrust/content/pages-html/product/verification/electronic-identity-verification.html +304 -0
  310. package/tenants/tokenoftrust/content/pages-html/product/verification/government-id-verification.html +308 -0
  311. package/tenants/tokenoftrust/content/pages-html/product/verification.html +183 -0
  312. package/tenants/tokenoftrust/content/pages-html/product.html +228 -0
  313. package/tenants/tokenoftrust/content/pages-html/resources/integrations/bigcommerce-integration.html +220 -0
  314. package/tenants/tokenoftrust/content/pages-html/resources/integrations/shopify-integration.html +221 -0
  315. package/tenants/tokenoftrust/content/pages-html/resources/integrations/wordpress.html +220 -0
  316. package/tenants/tokenoftrust/content/pages-html/resources/integrations.html +172 -0
  317. package/tenants/tokenoftrust/content/pages-html/resources/refer-a-friend-program.html +317 -0
  318. package/tenants/tokenoftrust/content/pages-html/resources.html +177 -0
  319. package/tenants/tokenoftrust/content/pages-html/reviews.html +273 -0
  320. package/tenants/tokenoftrust/content/pages-html/solutions.html +292 -0
  321. package/tenants/tokenoftrust/content/pages-html/vlp-alcohol.html +289 -0
  322. package/tenants/tokenoftrust/content/pages-html/vlp-cigars-and-tobacco.html +286 -0
  323. package/tenants/tokenoftrust/content/pages-html/vlp-firearm-accessories.html +267 -0
  324. package/tenants/tokenoftrust/content/pages-html/vlp-peptides.html +273 -0
  325. package/tenants/tokenoftrust/content/themes/tot-navy.json +88 -0
  326. package/tenants/tokenoftrust/public/favicon.png +0 -0
  327. package/tenants/tokenoftrust/public/logo-icon.png +0 -0
  328. package/tenants/tokenoftrust/public/logo-wordmark.png +0 -0
  329. package/tenants/tokenoftrust/public/pages/company.css +108 -0
  330. package/tenants/tokenoftrust/public/pages/home.css +457 -0
  331. package/tenants/tokenoftrust/public/pages/mkt.css +482 -0
  332. package/tenants/tokenoftrust/public/pages/pricing.css +214 -0
  333. package/tenants/tokenoftrust/public/pages/solutions.css +66 -0
  334. package/tenants/tokenoftrust/public/themes/tot-navy-1.0.0.css +136 -0
  335. package/tenants/tokenoftrust/public/themes/tot-navy.css +358 -0
  336. package/tenants/tokenoftrust/theme.json +31 -0
  337. package/tsconfig.base.json +20 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Slim per-product search document — the storefront's prebuilt search projection.
3
+ * The ETL emits an array of these per tenant;
4
+ * the storefront builds its Fuse index from them instead of pulling the full
5
+ * catalog per request. Keep this SMALL — it's loaded into the Worker and (later)
6
+ * stored in KV; full product detail is fetched per-handle only for what's shown.
7
+ *
8
+ * NOTE on store shape: a Fuse-able blob is the right shape for typo-tolerant
9
+ * SEARCH. Faceted filtering/listing is a different read model (D1/SQL). Don't
10
+ * grow this into a facet store.
11
+ */
12
+ import type { CatalogProduct, ProductStatus } from "./product.js";
13
+
14
+ export interface SearchDoc {
15
+ handle: string;
16
+ title: string;
17
+ vendor?: string;
18
+ product_type?: string;
19
+ tags: string[];
20
+ description_text: string;
21
+ /** Lowest variant price (the "from" price). */
22
+ price_min: number;
23
+ currency: string;
24
+ /** First usable image URL, if any. */
25
+ image?: string;
26
+ status: ProductStatus;
27
+ }
28
+
29
+ /** Project a full CatalogProduct down to the slim search document. */
30
+ export function toSearchDoc(p: CatalogProduct): SearchDoc {
31
+ const doc: SearchDoc = {
32
+ handle: p.handle,
33
+ title: p.title,
34
+ tags: p.tags,
35
+ description_text: p.description_text,
36
+ price_min: p.price_range.min,
37
+ currency: p.price_range.currency,
38
+ status: p.status,
39
+ };
40
+ if (p.vendor) doc.vendor = p.vendor;
41
+ if (p.product_type) doc.product_type = p.product_type;
42
+ const img = p.images.find((i) => i.url) ?? p.images[0];
43
+ if (img?.url) doc.image = img.url;
44
+ return doc;
45
+ }
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Versioned tenant static-asset store (decision `assets-r2-versioned`).
3
+ *
4
+ * Tenant static assets (`tenants/<id>/public/**` — logo, CSS, ~36 binaries) are
5
+ * served from **R2, version-scoped**, replacing the build-time
6
+ * `copy-tenant-assets.mjs` baking for extracted/versioned tenants. A **version**
7
+ * is an immutable snapshot: every asset for a version lives under one R2 key
8
+ * namespace `tenants/<id>/<versionId>/<rel>`, and a per-tenant, per-channel
9
+ * pointer selects which snapshot `live` vs `test` serves. A pointer flip switches
10
+ * every asset in lockstep — the same channel→version model the content publish
11
+ * spine (`customization-versioning.ts`) uses, applied to the static tree that
12
+ * `reconcile` reports as `publicAssets` ("mapped but NOT KV-served, served
13
+ * separately").
14
+ *
15
+ * Pure + runtime-portable (Web Crypto, `atob`, no `node:*`) so it runs in the
16
+ * Astro/Cloudflare Worker, the publish/ingest pipeline, and unit tests alike. The
17
+ * concrete R2 binding is injected via the structural `R2BucketLike` seam (a real
18
+ * `R2Bucket` satisfies it; `MemoryR2Bucket` backs dev + tests).
19
+ *
20
+ * ── versionId contract (assumption to reconcile with teammate `publish`) ──────
21
+ * The snapshot `versionId` is supplied by the publish pipeline (so a version's
22
+ * assets and content share ONE id and flip together). When publish has not
23
+ * defined one, `computeAssetsVersionId` derives a deterministic id from the asset
24
+ * set (sha256 over the sorted `<rel>:sha256(bytes)` list) — self-contained and
25
+ * idempotent. The channel→version pointer is a dedicated KV record
26
+ * (`cust:assets-pointer:<id>` → `{ live, test, rev }`) so it composes with, but
27
+ * does not collide with, the content `PointerRecord`. See
28
+ * `docs/versioned-assets-contract.md`.
29
+ */
30
+ import type { CustomizationEnv } from "./customization-preview.js";
31
+
32
+ /** sha256 as lowercase hex, via Web Crypto (Workers + Node 20+). Kept local so
33
+ * this renderer-safe module stays free of the control-plane versioning layer. */
34
+ async function sha256Hex(content: string | Uint8Array): Promise<string> {
35
+ const bytes =
36
+ typeof content === "string" ? new TextEncoder().encode(content) : content;
37
+ const digest = await crypto.subtle.digest(
38
+ "SHA-256",
39
+ bytes as unknown as BufferSource,
40
+ );
41
+ return Array.from(new Uint8Array(digest))
42
+ .map((b) => b.toString(16).padStart(2, "0"))
43
+ .join("");
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // R2 seam — structural subset a real R2Bucket satisfies (no workers-types dep)
48
+ // ---------------------------------------------------------------------------
49
+
50
+ export interface R2ObjectLike {
51
+ /** Streaming body (real R2). Null for a HEAD-style object. */
52
+ body: ReadableStream<Uint8Array> | null;
53
+ arrayBuffer(): Promise<ArrayBuffer>;
54
+ /** Byte length, when the store knows it. */
55
+ size?: number;
56
+ }
57
+
58
+ export interface R2BucketLike {
59
+ get(key: string): Promise<R2ObjectLike | null>;
60
+ put(
61
+ key: string,
62
+ value: ArrayBuffer | ArrayBufferView | ReadableStream | string,
63
+ ): Promise<unknown>;
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Channel→version pointer (one snapshot versionId per channel)
68
+ // ---------------------------------------------------------------------------
69
+
70
+ /** Per-tenant asset pointer: the live/test snapshot versionId + a CAS token. */
71
+ export interface AssetsPointer {
72
+ tenantId: string;
73
+ /** Optimistic-concurrency token; bumped on every successful write. */
74
+ rev: number;
75
+ /** Snapshot versionId selected for the live channel (null = none published). */
76
+ live: string | null;
77
+ /** Snapshot versionId selected for the test channel. */
78
+ test: string | null;
79
+ }
80
+
81
+ export function emptyAssetsPointer(tenantId: string): AssetsPointer {
82
+ return { tenantId, rev: 0, live: null, test: null };
83
+ }
84
+
85
+ /** KV key for a tenant's asset pointer (mirrors the `cust:*` layout). */
86
+ export function assetsPointerKey(tenantId: string): string {
87
+ return `cust:assets-pointer:${tenantId}`;
88
+ }
89
+
90
+ /** Resolve the snapshot versionId a channel serves (null = nothing published). */
91
+ export function resolveAssetVersion(
92
+ pointer: AssetsPointer,
93
+ channel: CustomizationEnv,
94
+ ): string | null {
95
+ return channel === "test" ? pointer.test : pointer.live;
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // R2 key scheme + path normalization
100
+ // ---------------------------------------------------------------------------
101
+
102
+ /** Reject empty, absolute, or traversal-bearing relative paths. */
103
+ function isSafeRel(rel: string): boolean {
104
+ if (!rel || rel.startsWith("/")) return false;
105
+ return !rel.split("/").some((seg) => seg === ".." || seg === ".");
106
+ }
107
+
108
+ /**
109
+ * R2 object key for one asset of a version: `tenants/<id>/<versionId>/<rel>`.
110
+ * Throws on an unsafe `rel` (empty / absolute / `..`) so a crafted request can
111
+ * never escape the version namespace.
112
+ */
113
+ export function tenantAssetKey(
114
+ tenantId: string,
115
+ versionId: string,
116
+ rel: string,
117
+ ): string {
118
+ if (!isSafeRel(rel)) {
119
+ throw new Error(`unsafe tenant-asset path: ${JSON.stringify(rel)}`);
120
+ }
121
+ return `tenants/${tenantId}/${versionId}/${rel}`;
122
+ }
123
+
124
+ function escapeRe(s: string): string {
125
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
126
+ }
127
+
128
+ /**
129
+ * Normalize a source path to the tenant's public-relative path (the `<rel>` in
130
+ * the R2 key), or null when the path is not a public asset for this tenant.
131
+ * Accepts both the colocated workspace shape (`tenants/<id>/public/<rel>`, from
132
+ * seed's `buildSeedFiles`) and the mapped artifact shape
133
+ * (`apps/storefront/public/tenants/<id>/<rel>`, reconcile's `publicAssets`).
134
+ */
135
+ export function toPublicRel(tenantId: string, path: string): string | null {
136
+ const id = escapeRe(tenantId);
137
+ // apps/storefront/public/tenants/<id>/<rel> (mapped artifact)
138
+ const m1 = path.match(new RegExp(`(?:^|/)public/tenants/${id}/(.+)$`))?.[1];
139
+ if (m1 && isSafeRel(m1)) return m1;
140
+ // tenants/<id>/public/<rel> (colocated workspace)
141
+ const m2 = path.match(new RegExp(`(?:^|/)tenants/${id}/public/(.+)$`))?.[1];
142
+ if (m2 && isSafeRel(m2)) return m2;
143
+ return null;
144
+ }
145
+
146
+ /**
147
+ * Parse a request pathname `/tenants/<id>/<rel...>` into its tenant + relative
148
+ * asset path, or null when the shape does not match (so the caller can fall
149
+ * through). `<rel>` is validated: a traversal attempt yields null.
150
+ */
151
+ export function parseTenantAssetPath(
152
+ pathname: string,
153
+ ): { tenantId: string; rel: string } | null {
154
+ let p = pathname;
155
+ try {
156
+ p = decodeURIComponent(pathname);
157
+ } catch {
158
+ return null;
159
+ }
160
+ const m = p.match(/^\/tenants\/([^/]+)\/(.+)$/);
161
+ const tenantId = m?.[1];
162
+ const rel = m?.[2];
163
+ if (!tenantId || !rel || !isSafeRel(rel)) return null;
164
+ return { tenantId, rel };
165
+ }
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Content-type + cache headers
169
+ // ---------------------------------------------------------------------------
170
+
171
+ const CONTENT_TYPES: Record<string, string> = {
172
+ css: "text/css; charset=utf-8",
173
+ js: "text/javascript; charset=utf-8",
174
+ mjs: "text/javascript; charset=utf-8",
175
+ json: "application/json; charset=utf-8",
176
+ html: "text/html; charset=utf-8",
177
+ txt: "text/plain; charset=utf-8",
178
+ xml: "application/xml; charset=utf-8",
179
+ svg: "image/svg+xml",
180
+ png: "image/png",
181
+ jpg: "image/jpeg",
182
+ jpeg: "image/jpeg",
183
+ webp: "image/webp",
184
+ avif: "image/avif",
185
+ gif: "image/gif",
186
+ ico: "image/x-icon",
187
+ woff: "font/woff",
188
+ woff2: "font/woff2",
189
+ ttf: "font/ttf",
190
+ otf: "font/otf",
191
+ eot: "application/vnd.ms-fontobject",
192
+ pdf: "application/pdf",
193
+ mp4: "video/mp4",
194
+ webm: "video/webm",
195
+ mov: "video/quicktime",
196
+ wasm: "application/wasm",
197
+ map: "application/json; charset=utf-8",
198
+ };
199
+
200
+ /** Best-effort content-type from a relative path's extension. */
201
+ export function assetContentType(rel: string): string {
202
+ const dot = rel.lastIndexOf(".");
203
+ const ext = dot >= 0 ? rel.slice(dot + 1).toLowerCase() : "";
204
+ return CONTENT_TYPES[ext] ?? "application/octet-stream";
205
+ }
206
+
207
+ /**
208
+ * Cache-control for a versioned asset. The URL path is stable but its BYTES flip
209
+ * with the pointer, so the resource at `/tenants/<id>/<rel>` is NOT immutable —
210
+ * we cache for a short window and revalidate. (The R2 KEY is immutable because
211
+ * versionId is content/snapshot-scoped, but the public URL is not versioned.)
212
+ */
213
+ export const ASSET_CACHE_CONTROL =
214
+ "public, max-age=300, s-maxage=300, stale-while-revalidate=86400";
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Ingest — write a version's public assets to R2
218
+ // ---------------------------------------------------------------------------
219
+
220
+ /** One input file (seed's `buildSeedFiles` shape, or a reconcile publicAsset). */
221
+ export interface AssetFile {
222
+ /** Source path (workspace or mapped artifact form — see `toPublicRel`). */
223
+ path: string;
224
+ /** How `content` is encoded. Bytes bypass this. Default "utf8". */
225
+ encoding?: "utf8" | "base64";
226
+ content: string | Uint8Array;
227
+ }
228
+
229
+ function decodeContent(f: AssetFile): Uint8Array {
230
+ if (f.content instanceof Uint8Array) return f.content;
231
+ if (f.encoding === "base64") {
232
+ const bin = atob(f.content);
233
+ const out = new Uint8Array(bin.length);
234
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
235
+ return out;
236
+ }
237
+ return new TextEncoder().encode(f.content);
238
+ }
239
+
240
+ /** The public-asset subset of a file list, normalized to `{ rel, bytes }`. */
241
+ export function selectPublicAssets(
242
+ tenantId: string,
243
+ files: AssetFile[],
244
+ ): Array<{ rel: string; bytes: Uint8Array }> {
245
+ const out: Array<{ rel: string; bytes: Uint8Array }> = [];
246
+ for (const f of files) {
247
+ const rel = toPublicRel(tenantId, f.path);
248
+ if (rel == null) continue;
249
+ out.push({ rel, bytes: decodeContent(f) });
250
+ }
251
+ return out;
252
+ }
253
+
254
+ /**
255
+ * Deterministic snapshot versionId derived purely from the asset set: sha256
256
+ * over the sorted `<rel>:sha256(bytes)` lines. Idempotent + self-contained — the
257
+ * default when the publish pipeline has not supplied its own snapshot id.
258
+ */
259
+ export async function computeAssetsVersionId(
260
+ tenantId: string,
261
+ files: AssetFile[],
262
+ ): Promise<string> {
263
+ const assets = selectPublicAssets(tenantId, files);
264
+ const lines = await Promise.all(
265
+ assets.map(async (a) => `${a.rel}:${await sha256Hex(a.bytes)}`),
266
+ );
267
+ lines.sort();
268
+ return sha256Hex(lines.join("\n"));
269
+ }
270
+
271
+ export interface IngestResult {
272
+ versionId: string;
273
+ /** R2 keys written, in input order. */
274
+ keys: string[];
275
+ count: number;
276
+ }
277
+
278
+ /**
279
+ * Write a version's public assets to R2 under `tenants/<id>/<versionId>/<rel>`.
280
+ * Content-addressed by the (caller-supplied or computed) snapshot id → immutable
281
+ * + idempotent: re-ingesting the same version overwrites identical bytes (no-op).
282
+ * Non-public files in `files` are ignored, so the whole seed/reconcile file set
283
+ * can be passed. Pass `versionId` to bind assets to the publish snapshot id;
284
+ * omit it to derive one from the bytes.
285
+ */
286
+ export async function ingestAssetVersion(
287
+ bucket: R2BucketLike,
288
+ tenantId: string,
289
+ files: AssetFile[],
290
+ versionId?: string,
291
+ ): Promise<IngestResult> {
292
+ const vid = versionId ?? (await computeAssetsVersionId(tenantId, files));
293
+ const assets = selectPublicAssets(tenantId, files);
294
+ const keys: string[] = [];
295
+ for (const a of assets) {
296
+ const key = tenantAssetKey(tenantId, vid, a.rel);
297
+ await bucket.put(key, a.bytes);
298
+ keys.push(key);
299
+ }
300
+ return { versionId: vid, keys, count: keys.length };
301
+ }
302
+
303
+ // ---------------------------------------------------------------------------
304
+ // Pointer persistence (KV) — read/write the channel→version selection
305
+ // ---------------------------------------------------------------------------
306
+
307
+ export interface AssetsKv {
308
+ get(key: string): Promise<string | null>;
309
+ put(key: string, value: string): Promise<void>;
310
+ }
311
+
312
+ export async function readAssetsPointer(
313
+ kv: Pick<AssetsKv, "get">,
314
+ tenantId: string,
315
+ ): Promise<AssetsPointer> {
316
+ const raw = await kv.get(assetsPointerKey(tenantId));
317
+ if (!raw) return emptyAssetsPointer(tenantId);
318
+ try {
319
+ const p = JSON.parse(raw) as Partial<AssetsPointer>;
320
+ return {
321
+ tenantId,
322
+ rev: typeof p.rev === "number" ? p.rev : 0,
323
+ live: typeof p.live === "string" ? p.live : null,
324
+ test: typeof p.test === "string" ? p.test : null,
325
+ };
326
+ } catch {
327
+ // Fail safe: a malformed record reads as empty (never wedges writes).
328
+ return emptyAssetsPointer(tenantId);
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Point a channel at a snapshot versionId (read-check-write on `rev`). KV has no
334
+ * hardware CAS; a single serialized writer per tenant (the publish/reconcile
335
+ * pipeline) makes the optimistic rev check sufficient — same trade-off as
336
+ * `KVVersionStore.setPointer`.
337
+ */
338
+ export async function setAssetsChannel(
339
+ kv: AssetsKv,
340
+ tenantId: string,
341
+ channel: CustomizationEnv,
342
+ versionId: string,
343
+ ): Promise<AssetsPointer> {
344
+ const current = await readAssetsPointer(kv, tenantId);
345
+ const next: AssetsPointer = {
346
+ ...current,
347
+ rev: current.rev + 1,
348
+ [channel]: versionId,
349
+ };
350
+ await kv.put(assetsPointerKey(tenantId), JSON.stringify(next));
351
+ return next;
352
+ }
353
+
354
+ /** Promote the test snapshot to live (atomic pointer flip). */
355
+ export async function promoteAssets(
356
+ kv: AssetsKv,
357
+ tenantId: string,
358
+ ): Promise<AssetsPointer> {
359
+ const current = await readAssetsPointer(kv, tenantId);
360
+ const next: AssetsPointer = {
361
+ ...current,
362
+ rev: current.rev + 1,
363
+ live: current.test,
364
+ };
365
+ await kv.put(assetsPointerKey(tenantId), JSON.stringify(next));
366
+ return next;
367
+ }
368
+
369
+ // ---------------------------------------------------------------------------
370
+ // Serve — resolve version, stream from R2 with headers
371
+ // ---------------------------------------------------------------------------
372
+
373
+ export interface ServeTenantAssetDeps {
374
+ bucket: R2BucketLike;
375
+ /** KV read for the assets pointer. */
376
+ kvGet: (key: string) => Promise<string | null>;
377
+ /** Channel to serve; default "live". Pass `locals.customizationEnv`. */
378
+ channel?: CustomizationEnv;
379
+ /** Request `If-None-Match` header, for conditional GET (304). */
380
+ ifNoneMatch?: string | null;
381
+ }
382
+
383
+ /**
384
+ * Serve `/tenants/<id>/<rel>` from the version-scoped R2 store.
385
+ *
386
+ * Returns `null` — meaning "not handled here, fall through" — when the path is
387
+ * not a tenant-asset path OR the tenant has no published asset version for the
388
+ * channel (the back-compat gate: non-versioned tenants are served by the baked
389
+ * static tree, which the adapter matches BEFORE this SSR route). Returns a 404
390
+ * `Response` when a version IS selected but the object is missing. On a hit,
391
+ * streams the object with the resolved content-type, a revalidating cache
392
+ * header, and a versionId ETag (honoring `If-None-Match` with a 304).
393
+ */
394
+ export async function serveTenantAsset(
395
+ deps: ServeTenantAssetDeps,
396
+ pathname: string,
397
+ ): Promise<Response | null> {
398
+ const parsed = parseTenantAssetPath(pathname);
399
+ if (!parsed) return null;
400
+ const { tenantId, rel } = parsed;
401
+
402
+ const pointer = await readAssetsPointer({ get: deps.kvGet }, tenantId);
403
+ const versionId = resolveAssetVersion(pointer, deps.channel ?? "live");
404
+ if (!versionId) return null; // non-versioned tenant → fall through
405
+
406
+ const etag = `"${versionId}"`;
407
+ const baseHeaders: Record<string, string> = {
408
+ "cache-control": ASSET_CACHE_CONTROL,
409
+ etag,
410
+ vary: "If-None-Match",
411
+ };
412
+
413
+ if (deps.ifNoneMatch && matchesEtag(deps.ifNoneMatch, etag)) {
414
+ return new Response(null, { status: 304, headers: baseHeaders });
415
+ }
416
+
417
+ const key = tenantAssetKey(tenantId, versionId, rel);
418
+ const object = await deps.bucket.get(key);
419
+ if (!object) {
420
+ return new Response("Not found", {
421
+ status: 404,
422
+ headers: { "cache-control": "no-store" },
423
+ });
424
+ }
425
+
426
+ const headers: Record<string, string> = {
427
+ ...baseHeaders,
428
+ "content-type": assetContentType(rel),
429
+ };
430
+ if (typeof object.size === "number") {
431
+ headers["content-length"] = String(object.size);
432
+ }
433
+ const body: BodyInit = object.body ?? (await object.arrayBuffer());
434
+ return new Response(body, { status: 200, headers });
435
+ }
436
+
437
+ /** Weak `If-None-Match` compare: `*`, exact, or member of a comma list. */
438
+ function matchesEtag(ifNoneMatch: string, etag: string): boolean {
439
+ const norm = (s: string) => s.trim().replace(/^W\//, "");
440
+ const target = norm(etag);
441
+ return ifNoneMatch
442
+ .split(",")
443
+ .some((t) => t.trim() === "*" || norm(t) === target);
444
+ }