@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.
- package/LICENSE +58 -0
- package/README.md +42 -0
- package/apps/storefront/astro.config.mjs +66 -0
- package/apps/storefront/dev-plugins/dev-loop-state.mjs +147 -0
- package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +238 -0
- package/apps/storefront/env.d.ts +162 -0
- package/apps/storefront/migrations/0000_baseline.sql +54 -0
- package/apps/storefront/migrations/0001_products_fts.sql +9 -0
- package/apps/storefront/migrations/README.md +54 -0
- package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
- package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
- package/apps/storefront/migrations/meta/_journal.json +20 -0
- package/apps/storefront/package.json +42 -0
- package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
- package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
- package/apps/storefront/public/brand/northwind-og.svg +13 -0
- package/apps/storefront/public/favicon.svg +5 -0
- package/apps/storefront/public/js/dashboard-team.js +147 -0
- package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
- package/apps/storefront/public/shared/commerce-chrome.css +369 -0
- package/apps/storefront/src/components/Badge.astro +34 -0
- package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
- package/apps/storefront/src/components/CollectionCard.astro +56 -0
- package/apps/storefront/src/components/EmptyState.astro +55 -0
- package/apps/storefront/src/components/Img.astro +68 -0
- package/apps/storefront/src/components/ProductCard.astro +228 -0
- package/apps/storefront/src/components/Section.astro +39 -0
- package/apps/storefront/src/components/Seo.astro +70 -0
- package/apps/storefront/src/components/WidgetFrame.astro +34 -0
- package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +531 -0
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +597 -0
- package/apps/storefront/src/components/admin/AdminFoxyCartSettingsPanel.astro +227 -0
- package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +465 -0
- package/apps/storefront/src/components/admin/AdminProductsTab.astro +901 -0
- package/apps/storefront/src/components/admin/AdminReportingTab.astro +435 -0
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +782 -0
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +1223 -0
- package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
- package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
- package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
- package/apps/storefront/src/components/chrome/NavDropdown.astro +201 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +201 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +234 -0
- package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
- package/apps/storefront/src/components/commerce/ArticleCard.astro +242 -0
- package/apps/storefront/src/components/commerce/BrandGrid.astro +175 -0
- package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
- package/apps/storefront/src/components/commerce/FoxySidecartStyles.astro +61 -0
- package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
- package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
- package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
- package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
- package/apps/storefront/src/components/commerce/ReviewList.astro +54 -0
- package/apps/storefront/src/components/commerce/ReviewSummary.astro +44 -0
- package/apps/storefront/src/components/commerce/TagFilterCloud.astro +149 -0
- package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +37 -0
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +99 -0
- package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +45 -0
- package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
- package/apps/storefront/src/components/compliance/PactActNotice.astro +35 -0
- package/apps/storefront/src/components/compliance/Prop65Warning.astro +80 -0
- package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +38 -0
- package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +35 -0
- package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +53 -0
- package/apps/storefront/src/components/home/Hero.astro +164 -0
- package/apps/storefront/src/components/islands/AgeGate.tsx +109 -0
- package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
- package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
- package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
- package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
- package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
- package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
- package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
- package/apps/storefront/src/components/islands/VariantSelector.tsx +375 -0
- package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
- package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
- package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
- package/apps/storefront/src/components/marketing/Faq.astro +37 -0
- package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
- package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
- package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
- package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
- package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
- package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
- package/apps/storefront/src/components/marketing/Steps.astro +36 -0
- package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
- package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
- package/apps/storefront/src/components/membership/IllustrativeLabel.astro +56 -0
- package/apps/storefront/src/components/membership/TierCards.astro +164 -0
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +148 -0
- package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
- package/apps/storefront/src/components/plp/FacetSidebar.astro +199 -0
- package/apps/storefront/src/components/plp/Pagination.astro +99 -0
- package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
- package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +95 -0
- package/apps/storefront/src/components/subscription/HowItWorksSteps.astro +67 -0
- package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +67 -0
- package/apps/storefront/src/components/subscription/SavingsExplainer.astro +101 -0
- package/apps/storefront/src/config/adminTargetTenant.ts +28 -0
- package/apps/storefront/src/config/devTenantSeed.ts +78 -0
- package/apps/storefront/src/config/devTenants.ts +16 -0
- package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
- package/apps/storefront/src/config/resolver.ts +102 -0
- package/apps/storefront/src/config/tenants.ts +87 -0
- package/apps/storefront/src/layouts/Layout.astro +368 -0
- package/apps/storefront/src/lib/admin/README.md +122 -0
- package/apps/storefront/src/lib/admin/ai/approvalMode.ts +21 -0
- package/apps/storefront/src/lib/admin/ai/askEnvelope.ts +48 -0
- package/apps/storefront/src/lib/admin/ai/audit.ts +14 -0
- package/apps/storefront/src/lib/admin/ai/index.ts +6 -0
- package/apps/storefront/src/lib/admin/ai/operationalRuns.ts +155 -0
- package/apps/storefront/src/lib/admin/ai/recommendation.ts +20 -0
- package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +172 -0
- package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +65 -0
- package/apps/storefront/src/lib/admin/envelope.ts +10 -0
- package/apps/storefront/src/lib/admin/evidence/evidenceReference.ts +32 -0
- package/apps/storefront/src/lib/admin/memoizeByEnvelope.ts +27 -0
- package/apps/storefront/src/lib/admin/orderClient.ts +146 -0
- package/apps/storefront/src/lib/admin/seed.ts +54 -0
- package/apps/storefront/src/lib/admin/services/mock/aiWorkflows.ts +59 -0
- package/apps/storefront/src/lib/admin/services/mock/customers.ts +86 -0
- package/apps/storefront/src/lib/admin/services/mock/evidence.ts +99 -0
- package/apps/storefront/src/lib/admin/services/mock/foxyCartSettings.ts +78 -0
- package/apps/storefront/src/lib/admin/services/mock/fulfillment.ts +62 -0
- package/apps/storefront/src/lib/admin/services/mock/index.ts +12 -0
- package/apps/storefront/src/lib/admin/services/mock/orders.ts +184 -0
- package/apps/storefront/src/lib/admin/services/mock/products.ts +105 -0
- package/apps/storefront/src/lib/admin/services/mock/reporting.ts +51 -0
- package/apps/storefront/src/lib/admin/services/mock/settings.ts +70 -0
- package/apps/storefront/src/lib/admin/services/mock/tenants.ts +29 -0
- package/apps/storefront/src/lib/admin/services/orders.ts +300 -0
- package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +67 -0
- package/apps/storefront/src/lib/admin/viewmodels/customers.ts +56 -0
- package/apps/storefront/src/lib/admin/viewmodels/fulfillment.ts +49 -0
- package/apps/storefront/src/lib/admin/viewmodels/orders.ts +92 -0
- package/apps/storefront/src/lib/admin/viewmodels/products.ts +74 -0
- package/apps/storefront/src/lib/admin/viewmodels/reporting.ts +34 -0
- package/apps/storefront/src/lib/admin/viewmodels/settings.ts +64 -0
- package/apps/storefront/src/lib/adminTenantEnvelope.ts +90 -0
- package/apps/storefront/src/lib/analytics/index.ts +47 -0
- package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
- package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
- package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
- package/apps/storefront/src/lib/auth/loginGate.ts +119 -0
- package/apps/storefront/src/lib/auth/route.ts +146 -0
- package/apps/storefront/src/lib/auth/session.ts +203 -0
- package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
- package/apps/storefront/src/lib/basePath.ts +41 -0
- package/apps/storefront/src/lib/cfImage.ts +55 -0
- package/apps/storefront/src/lib/chrome/model.ts +244 -0
- package/apps/storefront/src/lib/chrome/parseConfig.ts +34 -0
- package/apps/storefront/src/lib/colors.ts +97 -0
- package/apps/storefront/src/lib/commerce/foxyCartSettings.ts +253 -0
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +91 -0
- package/apps/storefront/src/lib/content-edit/client.ts +230 -0
- package/apps/storefront/src/lib/content-edit/index.ts +14 -0
- package/apps/storefront/src/lib/content-edit/route.ts +25 -0
- package/apps/storefront/src/lib/content-edit/types.ts +19 -0
- package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
- package/apps/storefront/src/lib/d1/catalog.ts +366 -0
- package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
- package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
- package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
- package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
- package/apps/storefront/src/lib/demoGate.ts +113 -0
- package/apps/storefront/src/lib/dev/activityIngestToken.ts +78 -0
- package/apps/storefront/src/lib/dev/activityStore.ts +72 -0
- package/apps/storefront/src/lib/dev/ai-widget-client.js +308 -0
- package/apps/storefront/src/lib/dev/apiBase.ts +14 -0
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +155 -0
- package/apps/storefront/src/lib/dev/devDraft.ts +133 -0
- package/apps/storefront/src/lib/dev/hostedCockpit.ts +169 -0
- package/apps/storefront/src/lib/dev/livePreview.ts +77 -0
- package/apps/storefront/src/lib/dev/runtimeStore.ts +120 -0
- package/apps/storefront/src/lib/dev/storeStatus.ts +142 -0
- package/apps/storefront/src/lib/diag/redact.ts +20 -0
- package/apps/storefront/src/lib/edgeCache.ts +128 -0
- package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
- package/apps/storefront/src/lib/env.ts +60 -0
- package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
- package/apps/storefront/src/lib/format.ts +38 -0
- package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
- package/apps/storefront/src/lib/imageAttrs.ts +55 -0
- package/apps/storefront/src/lib/jsonld.ts +166 -0
- package/apps/storefront/src/lib/maintenance.ts +71 -0
- package/apps/storefront/src/lib/membership/model.ts +97 -0
- package/apps/storefront/src/lib/memoryKv.ts +32 -0
- package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
- package/apps/storefront/src/lib/placeholder.ts +99 -0
- package/apps/storefront/src/lib/previewReload.ts +125 -0
- package/apps/storefront/src/lib/priceBuckets.ts +33 -0
- package/apps/storefront/src/lib/rawChrome.ts +505 -0
- package/apps/storefront/src/lib/rawMarketingHtml.ts +136 -0
- package/apps/storefront/src/lib/search/index.ts +74 -0
- package/apps/storefront/src/lib/sections.ts +129 -0
- package/apps/storefront/src/lib/securityHeaders.ts +31 -0
- package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
- package/apps/storefront/src/lib/storyblok/provider.ts +607 -0
- package/apps/storefront/src/lib/subscription/model.ts +68 -0
- package/apps/storefront/src/lib/tenantRouting.ts +91 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
- package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
- package/apps/storefront/src/lib/tot/query.ts +249 -0
- package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
- package/apps/storefront/src/lib/url.ts +142 -0
- package/apps/storefront/src/lib/wishlist.ts +63 -0
- package/apps/storefront/src/middleware/index.ts +455 -0
- package/apps/storefront/src/pages/404.astro +61 -0
- package/apps/storefront/src/pages/[...slug].astro +90 -0
- package/apps/storefront/src/pages/admin.astro +1675 -0
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +53 -0
- package/apps/storefront/src/pages/api/admin/orders.ts +71 -0
- package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
- package/apps/storefront/src/pages/api/auth/send.ts +85 -0
- package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
- package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
- package/apps/storefront/src/pages/api/checkout-style.css.ts +43 -0
- package/apps/storefront/src/pages/api/cli/policy.ts +70 -0
- package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +164 -0
- package/apps/storefront/src/pages/api/csp-report.ts +25 -0
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
- package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
- package/apps/storefront/src/pages/api/dev/activity.ts +124 -0
- package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +73 -0
- package/apps/storefront/src/pages/api/dev/diff.ts +68 -0
- package/apps/storefront/src/pages/api/dev/file.ts +57 -0
- package/apps/storefront/src/pages/api/dev/locate.ts +125 -0
- package/apps/storefront/src/pages/api/dev/publish.ts +128 -0
- package/apps/storefront/src/pages/api/dev/tree.ts +70 -0
- package/apps/storefront/src/pages/api/dev/write.ts +116 -0
- package/apps/storefront/src/pages/api/newsletter.ts +104 -0
- package/apps/storefront/src/pages/api/request-access.ts +172 -0
- package/apps/storefront/src/pages/auth/login.astro +149 -0
- package/apps/storefront/src/pages/auth/magic.astro +105 -0
- package/apps/storefront/src/pages/capabilities.astro +292 -0
- package/apps/storefront/src/pages/collections/[handle].astro +177 -0
- package/apps/storefront/src/pages/collections/index.astro +59 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
- package/apps/storefront/src/pages/dashboard/index.astro +160 -0
- package/apps/storefront/src/pages/index.astro +290 -0
- package/apps/storefront/src/pages/llms.txt.ts +92 -0
- package/apps/storefront/src/pages/products/[handle].astro +402 -0
- package/apps/storefront/src/pages/robots.txt.ts +24 -0
- package/apps/storefront/src/pages/saved.astro +35 -0
- package/apps/storefront/src/pages/search.astro +150 -0
- package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +1280 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +229 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +144 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +116 -0
- package/apps/storefront/src/pages/style-guide/index.astro +98 -0
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
- package/apps/storefront/src/styles/global.css +292 -0
- package/apps/storefront/src/themes/reference.ts +68 -0
- package/apps/storefront/src/themes/schema.ts +111 -0
- package/apps/storefront/src/themes/tenants/index.ts +51 -0
- package/apps/storefront/tsconfig.json +16 -0
- package/apps/storefront/vitest.config.ts +22 -0
- package/package.json +26 -0
- package/packages/public-runtime/LICENSE +58 -0
- package/packages/public-runtime/package.json +22 -0
- package/packages/public-runtime/src/binary-path.ts +68 -0
- package/packages/public-runtime/src/capabilities.ts +89 -0
- package/packages/public-runtime/src/catalog-api.ts +81 -0
- package/packages/public-runtime/src/catalog-d1.ts +192 -0
- package/packages/public-runtime/src/checkout-style-publish.ts +200 -0
- package/packages/public-runtime/src/checkout-style.ts +375 -0
- package/packages/public-runtime/src/csp.ts +109 -0
- package/packages/public-runtime/src/customization-preview.ts +197 -0
- package/packages/public-runtime/src/customization-reconcile.ts +63 -0
- package/packages/public-runtime/src/customization-runtime.ts +157 -0
- package/packages/public-runtime/src/customization-scripts.ts +64 -0
- package/packages/public-runtime/src/customization-versioning.ts +95 -0
- package/packages/public-runtime/src/foxy-orders.ts +136 -0
- package/packages/public-runtime/src/foxy.ts +196 -0
- package/packages/public-runtime/src/hot-standby-readiness.ts +140 -0
- package/packages/public-runtime/src/index.ts +34 -0
- package/packages/public-runtime/src/product.ts +159 -0
- package/packages/public-runtime/src/search.ts +45 -0
- package/packages/public-runtime/src/tenant-assets.ts +444 -0
- package/packages/public-runtime/src/tenant.ts +384 -0
- package/packages/public-runtime/tsconfig.json +8 -0
- package/packages/public-runtime/vitest.config.ts +8 -0
- package/pnpm-workspace.yaml +7 -0
- package/scripts/build/copy-tenant-assets.mjs +69 -0
- package/scripts/dev/ai-edit.mjs +183 -0
- package/scripts/dev/checkout-watch.mjs +116 -0
- package/scripts/dev/file-browser.mjs +159 -0
- package/scripts/dev/file-writer.mjs +141 -0
- package/scripts/dev/git-status.mjs +171 -0
- package/scripts/dev/locate-handler.mjs +163 -0
- package/scripts/dev/locate.mjs +112 -0
- package/scripts/dev/port-check.mjs +48 -0
- package/scripts/dev/safe-path.mjs +109 -0
- package/scripts/dev/shot.mjs +46 -0
- package/scripts/dev/tenant-source-root.mjs +76 -0
- package/scripts/dev/unified-diff.mjs +81 -0
- package/scripts/tot-dev.mjs +376 -0
- package/tenants/README.md +14 -0
- package/tenants/home/content/chrome.json +7 -0
- package/tenants/home/content/home.html +58 -0
- package/tenants/home/content/home.json +11 -0
- package/tenants/home/public/js/storefront.js +210 -0
- package/tenants/home/public/logo-wordmark.png +0 -0
- package/tenants/home/public/pages/storefront.css +212 -0
- package/tenants/home/theme.json +6 -0
- package/tsconfig.base.json +20 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chrome model — typed props for the reusable, tenant-agnostic site-chrome
|
|
3
|
+
* components (AnnouncementBar, UtilityNav, SiteHeader, NavDropdown/MegaMenu,
|
|
4
|
+
* SiteFooter). Chrome is a STANDARD component set skinned by theme tokens, never
|
|
5
|
+
* hand-built per tenant page. A tenant's chrome is data (`chrome.json`) that
|
|
6
|
+
* populates these shapes; the wrapping mechanism (see lib/rawChrome.ts) surrounds
|
|
7
|
+
* body-only page fragments with the rendered chrome.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** A navigation/footer link. `external` opens off-site; `badge` shows e.g. "NEW". */
|
|
11
|
+
export interface ChromeLink {
|
|
12
|
+
label: string;
|
|
13
|
+
href: string;
|
|
14
|
+
external?: boolean;
|
|
15
|
+
badge?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** One column of a mega-menu panel. */
|
|
19
|
+
export interface MegaColumn {
|
|
20
|
+
title?: string;
|
|
21
|
+
links: ChromeLink[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A primary-nav item. A plain link (no children/columns), a single-column
|
|
26
|
+
* dropdown (`children`), or a multi-column mega-menu (`columns`). `badge` marks
|
|
27
|
+
* it (e.g. "NEW"); `flag` gives it the emphasized/highlighted treatment.
|
|
28
|
+
*/
|
|
29
|
+
export interface ChromeNavItem {
|
|
30
|
+
label: string;
|
|
31
|
+
href: string;
|
|
32
|
+
badge?: string;
|
|
33
|
+
flag?: boolean;
|
|
34
|
+
children?: ChromeLink[];
|
|
35
|
+
columns?: MegaColumn[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SocialLink {
|
|
39
|
+
label: string;
|
|
40
|
+
href: string;
|
|
41
|
+
/** Optional inline-SVG path data (single <path d="…">) — no external images. */
|
|
42
|
+
iconPath?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ChromeLogo {
|
|
46
|
+
src: string;
|
|
47
|
+
alt: string;
|
|
48
|
+
width?: number;
|
|
49
|
+
height?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A marketing conversion CTA (header-actions / footer, non-commerce tenants). */
|
|
53
|
+
export interface ChromeCta {
|
|
54
|
+
label: string;
|
|
55
|
+
href: string;
|
|
56
|
+
variant?: "primary" | "secondary" | "ghost";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FooterColumn {
|
|
60
|
+
title: string;
|
|
61
|
+
links: ChromeLink[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface FooterNewsletter {
|
|
65
|
+
title?: string;
|
|
66
|
+
body?: string;
|
|
67
|
+
/** Form action (defaults to /api/newsletter). */
|
|
68
|
+
action?: string;
|
|
69
|
+
placeholder?: string;
|
|
70
|
+
/** Hidden `source` field value (e.g. "footer"). */
|
|
71
|
+
source?: string;
|
|
72
|
+
/** When true, render the "I confirm I am 21+" age-attestation checkbox (regulated tenants). */
|
|
73
|
+
ageAttestation?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// TenantChrome — the FULL per-tenant chrome config (content only) that drives the
|
|
78
|
+
// canonical RAW chrome wrapper (unit F3, see lib/rawChrome.ts). It is the superset
|
|
79
|
+
// the three vape tenants need: bigd/giant (marquee announce + flat nav + utility
|
|
80
|
+
// social) and PVC (dismissible rotating bar + mega panels + member cue + age gate +
|
|
81
|
+
// trust marks). A tenant authors this as `chrome.json`; the renderer turns it into
|
|
82
|
+
// the wrapper document that the rawMarketingHtml splice pipeline surrounds body-only
|
|
83
|
+
// page fragments with. CONTENT ONLY — every color/space/type comes from theme
|
|
84
|
+
// tokens via the platform-shipped stylesheet (decision raw-chrome-single-stylesheet);
|
|
85
|
+
// this config carries no styling. The same file also feeds SiteChrome (getChrome);
|
|
86
|
+
// the two consumers read overlapping-but-distinct field subsets.
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
/** A brand identity: an image wordmark/logo, OR a text wordmark of styled spans. */
|
|
90
|
+
export interface ChromeBrand {
|
|
91
|
+
/** Link target (defaults to "/"). */
|
|
92
|
+
href?: string;
|
|
93
|
+
/** Accessible name (aria-label / img alt), e.g. "Prime Vapor Club". */
|
|
94
|
+
label: string;
|
|
95
|
+
/** Image wordmark. Preferred when present and a valid src. */
|
|
96
|
+
logo?: ChromeLogo;
|
|
97
|
+
/**
|
|
98
|
+
* Text wordmark rendered as inline spans (e.g. PVC's `Prime`/`Vapor`/`Club`),
|
|
99
|
+
* each span optionally given a class the theme styles. Used when there's no logo
|
|
100
|
+
* image (placeholder brand — brief §9 Q15).
|
|
101
|
+
*/
|
|
102
|
+
wordmark?: { text: string; className?: string }[];
|
|
103
|
+
/** Marks the brand as a stand-in for a pending real logo (adds data-placeholder). */
|
|
104
|
+
placeholder?: boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Header search affordance (progressive GET form). Omit to hide search. */
|
|
108
|
+
export interface ChromeSearch {
|
|
109
|
+
/** Form action (defaults to "/search"). */
|
|
110
|
+
action?: string;
|
|
111
|
+
placeholder?: string;
|
|
112
|
+
/** sr-only label (defaults to "Search"). */
|
|
113
|
+
label?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The persistent member-price cue for guests (regulated/membership tenants). */
|
|
117
|
+
export interface ChromeMemberCue {
|
|
118
|
+
label: string;
|
|
119
|
+
href: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The thin utility bar above the header: minor links, locale chips, social, cue. */
|
|
123
|
+
export interface ChromeUtilityNav {
|
|
124
|
+
links?: ChromeLink[];
|
|
125
|
+
/** Locale/currency chips shown on the right (e.g. "English", "United States (USD $)"). */
|
|
126
|
+
locale?: string[];
|
|
127
|
+
social?: SocialLink[];
|
|
128
|
+
/** Optional right-aligned member-price cue link. */
|
|
129
|
+
memberCue?: ChromeMemberCue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Announcement banner. Three variants map to the F2 stylesheet's classes:
|
|
134
|
+
* - "marquee": infinite horizontal scroll (bigd/giant `.announce-marquee`);
|
|
135
|
+
* - "bar": dismissible + optionally message-rotating promo bar (PVC `.announce-bar`);
|
|
136
|
+
* - "solid": a static single-line solid bar (`.announce`).
|
|
137
|
+
*/
|
|
138
|
+
export interface ChromeAnnouncement {
|
|
139
|
+
variant: "marquee" | "bar" | "solid";
|
|
140
|
+
/** One or more messages. "bar" rotates through them when >1 and rotateMs set. */
|
|
141
|
+
messages: string[];
|
|
142
|
+
/** aria-label for the region (defaults to the first message). */
|
|
143
|
+
label?: string;
|
|
144
|
+
/** "bar" only — show a dismiss button (remembered via storageKey). */
|
|
145
|
+
dismissible?: boolean;
|
|
146
|
+
/** "bar" only — rotate messages every N ms (needs messages.length > 1). */
|
|
147
|
+
rotateMs?: number;
|
|
148
|
+
/** localStorage key remembering a dismissal (defaults to a stable derived key). */
|
|
149
|
+
storageKey?: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 21+ age-gate interstitial config (regulated tenants). The F3 renderer (rawChrome.ts)
|
|
154
|
+
* embeds this as JSON; the shared behavior JS mounts the gate inside a closed shadow
|
|
155
|
+
* root — the same tamper-resistance pattern components/islands/IsolatedAgeGate.tsx
|
|
156
|
+
* uses for component pages (unit C1) — so tenant CSS/JS can't reach the dialog.
|
|
157
|
+
* Per decision compliance-widgets-platform-owned.
|
|
158
|
+
*/
|
|
159
|
+
export interface ChromeAgeGate {
|
|
160
|
+
minAge: number;
|
|
161
|
+
/** localStorage key remembering a pass (defaults to a stable derived key). */
|
|
162
|
+
storageKey?: string;
|
|
163
|
+
title?: string;
|
|
164
|
+
lede?: string;
|
|
165
|
+
yesLabel?: string;
|
|
166
|
+
noLabel?: string;
|
|
167
|
+
denyText?: string;
|
|
168
|
+
/** Fine-print warning under the actions (e.g. the FDA nicotine line). */
|
|
169
|
+
fineText?: string;
|
|
170
|
+
/** Optional styled wordmark above the title (spans, like ChromeBrand.wordmark). */
|
|
171
|
+
mark?: { text: string; className?: string }[];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Footer brand block: identity + tagline/contact + optional inline newsletter. */
|
|
175
|
+
export interface ChromeFooterBrand {
|
|
176
|
+
logo?: ChromeLogo;
|
|
177
|
+
wordmark?: { text: string; className?: string }[];
|
|
178
|
+
/** Tagline / contact HTML-free text (rendered with <br> on newlines). */
|
|
179
|
+
text?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** A footer trust mark: a small inline-SVG cue + label (e.g. "Secure checkout"). */
|
|
183
|
+
export interface ChromeTrustMark {
|
|
184
|
+
/** Inner SVG markup (paths/rects/circles), viewBox 0 0 24 24. */
|
|
185
|
+
icon: string;
|
|
186
|
+
label: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** The full footer content. */
|
|
190
|
+
export interface ChromeFooter {
|
|
191
|
+
brand?: ChromeFooterBrand;
|
|
192
|
+
columns: FooterColumn[];
|
|
193
|
+
newsletter?: FooterNewsletter;
|
|
194
|
+
trustMarks?: ChromeTrustMark[];
|
|
195
|
+
/** Accepted-payment marks (text tokens, e.g. "VISA"). */
|
|
196
|
+
payments?: string[];
|
|
197
|
+
social?: SocialLink[];
|
|
198
|
+
/** The bold compliance/nicotine warning block. */
|
|
199
|
+
warning?: string;
|
|
200
|
+
/** Bottom bar: copyright + a legal note line. */
|
|
201
|
+
bottom?: { copyright?: string; note?: string };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Per-tenant <head> extras the wrapper links (the shared css is ALWAYS linked). */
|
|
205
|
+
export interface ChromeHead {
|
|
206
|
+
/** Favicon href (image/png). */
|
|
207
|
+
favicon?: string;
|
|
208
|
+
/** Tenant theme stylesheet hrefs (token definitions), e.g. the *-navy.css theme. */
|
|
209
|
+
themeStylesheets?: string[];
|
|
210
|
+
/** <meta name="theme-color"> — a deliberately brand-fixed surface color. */
|
|
211
|
+
themeColor?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export interface TenantChrome {
|
|
215
|
+
/**
|
|
216
|
+
* OPT-IN: render the canonical shared chrome for this tenant's RAW page
|
|
217
|
+
* fragments. Absent/false ⇒ the tenant keeps serving its hand-authored
|
|
218
|
+
* chrome.html (or a full document) unchanged — so a shipped tenant never flips
|
|
219
|
+
* to shared chrome until it explicitly adopts (units I1/I2/I3). Required so the
|
|
220
|
+
* cutover of a SHIPPED tenant stays a deliberate, gated step.
|
|
221
|
+
*/
|
|
222
|
+
sharedChrome?: boolean;
|
|
223
|
+
/** <html lang> (defaults to "en"). */
|
|
224
|
+
lang?: string;
|
|
225
|
+
head?: ChromeHead;
|
|
226
|
+
/** Skip-link target/label overrides (defaults to "#main" / "Skip to main content"). */
|
|
227
|
+
skipLink?: { href?: string; label?: string };
|
|
228
|
+
/** FDA/compliance warning strip at the very top (e.g. the nicotine line). */
|
|
229
|
+
warningBar?: string;
|
|
230
|
+
announcement?: ChromeAnnouncement;
|
|
231
|
+
utilityNav?: ChromeUtilityNav;
|
|
232
|
+
brand: ChromeBrand;
|
|
233
|
+
search?: ChromeSearch;
|
|
234
|
+
/** Header-actions member-price cue (in addition to any utility-nav cue). */
|
|
235
|
+
memberCue?: ChromeMemberCue;
|
|
236
|
+
accountHref?: string;
|
|
237
|
+
cartHref?: string;
|
|
238
|
+
/** Primary nav — flat links and/or mega panels (ChromeNavItem.columns). */
|
|
239
|
+
nav: ChromeNavItem[];
|
|
240
|
+
ageGate?: ChromeAgeGate;
|
|
241
|
+
/** The compliance strip below the page body (e.g. the nicotine warning). */
|
|
242
|
+
complianceStrip?: string;
|
|
243
|
+
footer: ChromeFooter;
|
|
244
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse/validate a tenant's `chrome.json` as a raw-chrome `TenantChrome` config
|
|
3
|
+
* (unit F3). This is the OPT-IN gate for the canonical shared chrome: it returns a
|
|
4
|
+
* config ONLY when the tenant has explicitly set `sharedChrome: true` AND supplies
|
|
5
|
+
* the minimum a wrapper needs (brand + nav + footer). Any other shape — a tenant
|
|
6
|
+
* that hasn't adopted, or a half-authored file — returns null, so the caller keeps
|
|
7
|
+
* serving the tenant's hand-authored chrome.html (or falls back cleanly) and a
|
|
8
|
+
* shipped tenant never flips to shared chrome by accident. Adoption (authoring the
|
|
9
|
+
* canonical config + removing chrome.html) is the tenant-integration units (I1/I2/I3).
|
|
10
|
+
*/
|
|
11
|
+
import type { TenantChrome } from "./model.js";
|
|
12
|
+
|
|
13
|
+
function isObject(v: unknown): v is Record<string, unknown> {
|
|
14
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Coerce parsed chrome.json into a `TenantChrome`, or null if it isn't an opted-in,
|
|
19
|
+
* minimally-complete raw-chrome config. Trusts the authored (first-party) field
|
|
20
|
+
* shapes beyond the required-key check — the renderer is defensive (escapes values,
|
|
21
|
+
* guards every optional section), so a missing optional degrades rather than breaks.
|
|
22
|
+
*/
|
|
23
|
+
export function parseChromeConfig(raw: unknown): TenantChrome | null {
|
|
24
|
+
if (!isObject(raw)) return null;
|
|
25
|
+
// Opt-in switch — absent/false means "not adopted; keep legacy chrome".
|
|
26
|
+
if (raw.sharedChrome !== true) return null;
|
|
27
|
+
// Minimum a wrapper needs to render something coherent.
|
|
28
|
+
const brand = raw.brand;
|
|
29
|
+
if (!isObject(brand) || typeof brand.label !== "string") return null;
|
|
30
|
+
if (!Array.isArray(raw.nav)) return null;
|
|
31
|
+
const footer = raw.footer;
|
|
32
|
+
if (!isObject(footer) || !Array.isArray(footer.columns)) return null;
|
|
33
|
+
return raw as unknown as TenantChrome;
|
|
34
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map human color-option values to swatch fills for product cards.
|
|
3
|
+
*
|
|
4
|
+
* Merchants name colors in their own vernacular ("Oatmeal", "Spruce"), not hex.
|
|
5
|
+
* We resolve a known palette first, fall back to a CSS named color when the
|
|
6
|
+
* value happens to be one, and otherwise render an "unknown" swatch (neutral
|
|
7
|
+
* with a slash) rather than guessing wrong. Tenant-agnostic: this is product
|
|
8
|
+
* data, not theme.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Curated names common to apparel/home catalogs (lowercased keys). */
|
|
12
|
+
const PALETTE: Record<string, string> = {
|
|
13
|
+
black: "#1a1a1a",
|
|
14
|
+
charcoal: "#36393b",
|
|
15
|
+
graphite: "#3a3a3a",
|
|
16
|
+
slate: "#556770",
|
|
17
|
+
grey: "#8a8d91",
|
|
18
|
+
gray: "#8a8d91",
|
|
19
|
+
silver: "#c7c9cc",
|
|
20
|
+
white: "#f6f5f1",
|
|
21
|
+
ivory: "#f3efe4",
|
|
22
|
+
cream: "#efe9d8",
|
|
23
|
+
ecru: "#e8e1cd",
|
|
24
|
+
oatmeal: "#ddd3bf",
|
|
25
|
+
natural: "#e3d9c2",
|
|
26
|
+
sand: "#d8c6a3",
|
|
27
|
+
stone: "#cbbfa9",
|
|
28
|
+
tan: "#c5a980",
|
|
29
|
+
camel: "#b48a5e",
|
|
30
|
+
rust: "#a4512f",
|
|
31
|
+
terracotta: "#bf6a4a",
|
|
32
|
+
brick: "#8f4334",
|
|
33
|
+
olive: "#6f6f3a",
|
|
34
|
+
forest: "#2f4a36",
|
|
35
|
+
spruce: "#23413a",
|
|
36
|
+
sage: "#9aa583",
|
|
37
|
+
teal: "#2a6f72",
|
|
38
|
+
navy: "#1f2a44",
|
|
39
|
+
indigo: "#2c3a63",
|
|
40
|
+
denim: "#41597d",
|
|
41
|
+
blue: "#2f5e8f",
|
|
42
|
+
burgundy: "#5e2230",
|
|
43
|
+
wine: "#5e2230",
|
|
44
|
+
plum: "#5a3a55",
|
|
45
|
+
blush: "#e7c7c0",
|
|
46
|
+
pink: "#e3a6ad",
|
|
47
|
+
mustard: "#c79a3a",
|
|
48
|
+
amber: "#b5612f",
|
|
49
|
+
brown: "#5f4733",
|
|
50
|
+
chocolate: "#46342a",
|
|
51
|
+
khaki: "#9a8b66",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** CSS named colors we accept as a fallback when not in PALETTE. */
|
|
55
|
+
const CSS_NAMED = new Set([
|
|
56
|
+
"red", "orange", "yellow", "green", "blue", "purple", "pink", "brown",
|
|
57
|
+
"black", "white", "gray", "grey", "beige", "ivory", "navy", "teal",
|
|
58
|
+
"maroon", "olive", "coral", "salmon", "khaki", "tan", "gold",
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
export interface Swatch {
|
|
62
|
+
/** Original option value, e.g. "Oatmeal". */
|
|
63
|
+
label: string;
|
|
64
|
+
/** CSS color for the dot, or null when unknown. */
|
|
65
|
+
fill: string | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resolveSwatch(value: string): Swatch {
|
|
69
|
+
const key = value.trim().toLowerCase();
|
|
70
|
+
if (key in PALETTE) return { label: value, fill: PALETTE[key]! };
|
|
71
|
+
// Two-word names: try the last token ("Heather Grey" -> "grey").
|
|
72
|
+
const last = key.split(/\s+/).pop()!;
|
|
73
|
+
if (last in PALETTE) return { label: value, fill: PALETTE[last]! };
|
|
74
|
+
if (CSS_NAMED.has(key)) return { label: value, fill: key };
|
|
75
|
+
if (CSS_NAMED.has(last)) return { label: value, fill: last };
|
|
76
|
+
return { label: value, fill: null };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The product option that holds colors, if any (Color / Colour / Shade). */
|
|
80
|
+
export function colorOptionValues(
|
|
81
|
+
options: { name: string; values: string[] }[],
|
|
82
|
+
): string[] {
|
|
83
|
+
const opt = options.find((o) => /colou?r|shade|finish/i.test(o.name));
|
|
84
|
+
return opt ? opt.values : [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** True when a fill is light enough to need a ring so it reads on a light bg. */
|
|
88
|
+
export function isLightFill(fill: string): boolean {
|
|
89
|
+
const m = /^#([0-9a-f]{6})$/i.exec(fill);
|
|
90
|
+
if (!m) return false;
|
|
91
|
+
const n = parseInt(m[1]!, 16);
|
|
92
|
+
const r = (n >> 16) & 255,
|
|
93
|
+
g = (n >> 8) & 255,
|
|
94
|
+
b = n & 255;
|
|
95
|
+
// Perceived luminance.
|
|
96
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b > 200;
|
|
97
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FoxyCart settings model — the admin "Cart & Checkout appearance" section.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the settings surface that the **tot-foxycart** service actually manages
|
|
5
|
+
* (verified against that repo), and defines how each field is PREPOPULATED from
|
|
6
|
+
* the tenant's theme (the style-guide), OVERRIDDEN, and RESET.
|
|
7
|
+
*
|
|
8
|
+
* tot-foxycart's real settings are three groups:
|
|
9
|
+
* 1. `colors` — Foxy's native `template_config.colors` (primary/secondary/
|
|
10
|
+
* tertiary + usage). The only named brand-color fields in the
|
|
11
|
+
* Foxy model; documented in tot-foxycart, applied via the theme.
|
|
12
|
+
* 2. `templates`— per-template-type `contentUrl` pointers (checkout / cart /
|
|
13
|
+
* cart_include[sidecart] / receipt / email), set via
|
|
14
|
+
* `PUT /theme/foxy` then published via `POST /theme/foxy/refresh`.
|
|
15
|
+
* The colors/fonts/logo live INSIDE the hosted template file.
|
|
16
|
+
* 3. `store` — storeSubDomain + loader feature toggles (excise tax, age/ID
|
|
17
|
+
* verification). Non-visual; no theme default.
|
|
18
|
+
*
|
|
19
|
+
* Prepopulation lands on the parts with a theme default — the brand colors and
|
|
20
|
+
* the template pointers (defaulted to a ToT-hosted, theme-derived template URL).
|
|
21
|
+
* The feature toggles are mirrored as plain config (no default → not resettable).
|
|
22
|
+
*
|
|
23
|
+
* The ACTUAL persistence — PATCHing Foxy's colors + PUT/refresh of the template
|
|
24
|
+
* URLs — is the tot-foxycart HTTP seam (`/theme/foxy`), intentionally NOT wired
|
|
25
|
+
* here; this module is the pure model the admin renders and edits.
|
|
26
|
+
*/
|
|
27
|
+
import type { ResolvedFoxyCommerce, ThemeTokens } from "@tot/public-runtime";
|
|
28
|
+
|
|
29
|
+
/** Foxy template types (tot-foxycart `TEMPLATE_TYPES`). */
|
|
30
|
+
export type FoxyTemplateType =
|
|
31
|
+
| "checkout"
|
|
32
|
+
| "cart"
|
|
33
|
+
| "cart_include"
|
|
34
|
+
| "receipt"
|
|
35
|
+
| "email";
|
|
36
|
+
|
|
37
|
+
export const FOXY_TEMPLATE_TYPES: readonly FoxyTemplateType[] = [
|
|
38
|
+
"checkout",
|
|
39
|
+
"cart",
|
|
40
|
+
"cart_include",
|
|
41
|
+
"receipt",
|
|
42
|
+
"email",
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
export const FOXY_TEMPLATE_LABELS: Record<FoxyTemplateType, string> = {
|
|
46
|
+
checkout: "Checkout page",
|
|
47
|
+
cart: "Full cart page",
|
|
48
|
+
cart_include: "Sidecart (slide-out)",
|
|
49
|
+
receipt: "Receipt page",
|
|
50
|
+
email: "Order email",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Foxy native `template_config.colors` — the only named brand-color fields. */
|
|
54
|
+
export interface FoxyBrandColors {
|
|
55
|
+
primary: string;
|
|
56
|
+
secondary: string;
|
|
57
|
+
tertiary: string;
|
|
58
|
+
/** Foxy's `usage`: how broadly the colors apply. "none" = Foxy default look. */
|
|
59
|
+
usage: "none" | "all";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A per-template-type pointer (mirrors `PUT /theme/foxy`). */
|
|
63
|
+
export interface FoxyTemplatePointer {
|
|
64
|
+
contentUrl: string;
|
|
65
|
+
templateSet: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Store + loader feature config (foxyCartOptions + totOpts). Non-visual. */
|
|
69
|
+
export interface FoxyStoreConfig {
|
|
70
|
+
storeSubDomain: string;
|
|
71
|
+
exciseTaxCollectionEnabled: boolean;
|
|
72
|
+
verificationEnabled: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The full tot-foxycart settings mirror. */
|
|
76
|
+
export interface FoxyCartSettings {
|
|
77
|
+
colors: FoxyBrandColors;
|
|
78
|
+
templates: Record<FoxyTemplateType, FoxyTemplatePointer>;
|
|
79
|
+
store: FoxyStoreConfig;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Overrides are a deep-partial of the mirror; anything unset falls back to the
|
|
83
|
+
* theme-derived default. Reset === delete the key here. */
|
|
84
|
+
export type FoxyCartSettingsOverrides = {
|
|
85
|
+
colors?: Partial<FoxyBrandColors>;
|
|
86
|
+
templates?: Partial<Record<FoxyTemplateType, Partial<FoxyTemplatePointer>>>;
|
|
87
|
+
store?: Partial<FoxyStoreConfig>;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Prepopulation: theme (style-guide) → FoxyCart settings defaults.
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
export interface DeriveOptions {
|
|
95
|
+
/**
|
|
96
|
+
* Absolute origin the ToT-hosted, theme-derived templates are served from —
|
|
97
|
+
* the tenant's own storefront origin (e.g. `https://sample-store.example`). Templates
|
|
98
|
+
* live at `${originBase}/foxy-templates/<type>.html`.
|
|
99
|
+
*/
|
|
100
|
+
originBase: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build the theme-derived DEFAULTS. This is the "prepopulate from the style-guide"
|
|
105
|
+
* step: brand colors come straight from the theme tokens; each template pointer
|
|
106
|
+
* defaults to a stable ToT-hosted URL for that tenant's theme-derived template.
|
|
107
|
+
*/
|
|
108
|
+
export function deriveFoxyCartSettings(
|
|
109
|
+
theme: ThemeTokens,
|
|
110
|
+
commerce: ResolvedFoxyCommerce | null,
|
|
111
|
+
opts: DeriveOptions,
|
|
112
|
+
): FoxyCartSettings {
|
|
113
|
+
const base = opts.originBase.replace(/\/$/, "");
|
|
114
|
+
const templateUrl = (type: FoxyTemplateType) =>
|
|
115
|
+
`${base}/foxy-templates/${type}.html`;
|
|
116
|
+
|
|
117
|
+
const templates = Object.fromEntries(
|
|
118
|
+
FOXY_TEMPLATE_TYPES.map((type) => [
|
|
119
|
+
type,
|
|
120
|
+
{ contentUrl: templateUrl(type), templateSet: "DEFAULT" },
|
|
121
|
+
]),
|
|
122
|
+
) as Record<FoxyTemplateType, FoxyTemplatePointer>;
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
colors: {
|
|
126
|
+
// Foxy primary/secondary/tertiary ← the tenant's brand tokens.
|
|
127
|
+
primary: theme.color.primary,
|
|
128
|
+
secondary: theme.color.accent,
|
|
129
|
+
tertiary: theme.color.sale,
|
|
130
|
+
usage: "all",
|
|
131
|
+
},
|
|
132
|
+
templates,
|
|
133
|
+
store: {
|
|
134
|
+
storeSubDomain: commerce?.storeSubDomain ?? "",
|
|
135
|
+
// Feature toggles have no theme default — start off; not prepopulatable.
|
|
136
|
+
exciseTaxCollectionEnabled: false,
|
|
137
|
+
verificationEnabled: false,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Resolve with provenance: defaults + overrides → renderable field descriptors.
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
export type FoxyFieldKind = "color" | "url" | "text" | "boolean" | "select";
|
|
147
|
+
export type FoxyFieldGroup = "colors" | "templates" | "store";
|
|
148
|
+
|
|
149
|
+
/** One renderable settings row: resolved value + provenance + reset affordance. */
|
|
150
|
+
export interface FoxySettingsField {
|
|
151
|
+
group: FoxyFieldGroup;
|
|
152
|
+
/** Dot path into FoxyCartSettings, e.g. "colors.primary" / "templates.checkout.contentUrl". */
|
|
153
|
+
key: string;
|
|
154
|
+
label: string;
|
|
155
|
+
kind: FoxyFieldKind;
|
|
156
|
+
value: string | boolean;
|
|
157
|
+
/** The theme-derived default; undefined when the field has no theme default. */
|
|
158
|
+
default?: string | boolean;
|
|
159
|
+
source: "theme" | "override";
|
|
160
|
+
/** True when there is a theme default to reset back to. */
|
|
161
|
+
resettable: boolean;
|
|
162
|
+
options?: string[];
|
|
163
|
+
help?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function readPath(obj: unknown, path: string): unknown {
|
|
167
|
+
return path.split(".").reduce<unknown>((o, k) => {
|
|
168
|
+
if (o && typeof o === "object" && k in (o as Record<string, unknown>)) {
|
|
169
|
+
return (o as Record<string, unknown>)[k];
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}, obj);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Merge defaults + overrides into the flat field list the admin renders. Each
|
|
177
|
+
* field's `source` is "override" when the overrides object supplies that exact
|
|
178
|
+
* path, else "theme". `resettable` marks fields with a real theme default (the
|
|
179
|
+
* visual ones) so the UI can show/hide the "Reset to theme" control.
|
|
180
|
+
*/
|
|
181
|
+
export function buildFoxySettingsFields(
|
|
182
|
+
defaults: FoxyCartSettings,
|
|
183
|
+
overrides: FoxyCartSettingsOverrides,
|
|
184
|
+
): FoxySettingsField[] {
|
|
185
|
+
const field = (
|
|
186
|
+
group: FoxyFieldGroup,
|
|
187
|
+
key: string,
|
|
188
|
+
label: string,
|
|
189
|
+
kind: FoxyFieldKind,
|
|
190
|
+
opts: { resettable?: boolean; options?: string[]; help?: string } = {},
|
|
191
|
+
): FoxySettingsField => {
|
|
192
|
+
const def = readPath(defaults, key);
|
|
193
|
+
const ov = readPath(overrides, key);
|
|
194
|
+
const overridden = ov !== undefined;
|
|
195
|
+
const resettable = opts.resettable ?? true;
|
|
196
|
+
return {
|
|
197
|
+
group,
|
|
198
|
+
key,
|
|
199
|
+
label,
|
|
200
|
+
kind,
|
|
201
|
+
value: (overridden ? ov : def) as string | boolean,
|
|
202
|
+
default: resettable ? (def as string | boolean) : undefined,
|
|
203
|
+
source: overridden ? "override" : "theme",
|
|
204
|
+
resettable,
|
|
205
|
+
options: opts.options,
|
|
206
|
+
help: opts.help,
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const fields: FoxySettingsField[] = [
|
|
211
|
+
// Group 1 — brand colors (theme-derived; the "make it look bigd" surface).
|
|
212
|
+
field("colors", "colors.primary", "Primary color", "color", {
|
|
213
|
+
help: "Checkout/cart primary buttons and accents. From --color-primary.",
|
|
214
|
+
}),
|
|
215
|
+
field("colors", "colors.secondary", "Secondary color", "color", {
|
|
216
|
+
help: "Secondary accents. From --color-accent.",
|
|
217
|
+
}),
|
|
218
|
+
field("colors", "colors.tertiary", "Tertiary color", "color", {
|
|
219
|
+
help: "Sale/clearance accent. From --color-sale.",
|
|
220
|
+
}),
|
|
221
|
+
field("colors", "colors.usage", "Apply brand colors", "select", {
|
|
222
|
+
options: ["all", "none"],
|
|
223
|
+
help: '"all" applies the brand palette to Foxy; "none" keeps Foxy defaults.',
|
|
224
|
+
}),
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
// Group 2 — template pointers (default = ToT-hosted theme-derived template).
|
|
228
|
+
for (const type of FOXY_TEMPLATE_TYPES) {
|
|
229
|
+
fields.push(
|
|
230
|
+
field("templates", `templates.${type}.contentUrl`, FOXY_TEMPLATE_LABELS[type], "url", {
|
|
231
|
+
help: `Hosted template URL for the ${FOXY_TEMPLATE_LABELS[type].toLowerCase()}. Defaults to the ToT theme-derived template; override with a custom URL.`,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Group 3 — store/feature config (no theme default → not resettable).
|
|
237
|
+
fields.push(
|
|
238
|
+
field("store", "store.storeSubDomain", "Foxy store subdomain", "text", {
|
|
239
|
+
resettable: false,
|
|
240
|
+
help: "The <sub>.foxycart.com store this tenant checks out through.",
|
|
241
|
+
}),
|
|
242
|
+
field("store", "store.exciseTaxCollectionEnabled", "Collect excise tax", "boolean", {
|
|
243
|
+
resettable: false,
|
|
244
|
+
help: "tot-foxycart pre-cart excise tax collection.",
|
|
245
|
+
}),
|
|
246
|
+
field("store", "store.verificationEnabled", "Age / identity verification", "boolean", {
|
|
247
|
+
resettable: false,
|
|
248
|
+
help: "Token of Trust verification at checkout.",
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return fields;
|
|
253
|
+
}
|