@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,184 @@
|
|
|
1
|
+
import type { AdminTenantEnvelope } from "../../envelope";
|
|
2
|
+
import { MOCK_ANCHOR_ISO, offsetIso, pick, randomBool, randomInt, seededRandom } from "../../seed";
|
|
3
|
+
import type {
|
|
4
|
+
OrderDetail,
|
|
5
|
+
OrderEvidenceEvent,
|
|
6
|
+
OrderLineItem,
|
|
7
|
+
OrderQueueSummary,
|
|
8
|
+
OrderRow,
|
|
9
|
+
OrdersViewModel,
|
|
10
|
+
} from "../../viewmodels/orders";
|
|
11
|
+
import { tenantProfile } from "./tenants";
|
|
12
|
+
import { memoizeByEnvelope } from "../../memoizeByEnvelope";
|
|
13
|
+
|
|
14
|
+
// Must stay index-aligned with customers.ts's NAMES: an order's customerId is
|
|
15
|
+
// derived from the picked name's position here, and customers.ts assigns each
|
|
16
|
+
// customer the id `cust-<tenant>-<position>`. The evidence graph joins order→
|
|
17
|
+
// customer by that shared id, so these two arrays must remain identical.
|
|
18
|
+
const CUSTOMER_NAMES = [
|
|
19
|
+
"Riley Harper",
|
|
20
|
+
"Jordan Lee",
|
|
21
|
+
"Casey Nguyen",
|
|
22
|
+
"Morgan Blake",
|
|
23
|
+
"Sam Ortiz",
|
|
24
|
+
"Avery Chen",
|
|
25
|
+
"Quinn Sullivan",
|
|
26
|
+
"Reese Patel",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// Must stay index-aligned with products.ts's PRODUCT_NAMES: an order's productId
|
|
30
|
+
// is derived from the picked item's position here, and products.ts assigns each
|
|
31
|
+
// product the id `prod-<tenant>-<position>`. The evidence graph joins order→
|
|
32
|
+
// product by that shared id, so these pools must remain identical per vertical.
|
|
33
|
+
const ITEM_POOLS: Record<string, string[]> = {
|
|
34
|
+
vape: ["Vape Pen Starter Kit", "Disposable Vape 5000", "Nicotine Pouches 6mg", "Replacement Coil 3-Pack"],
|
|
35
|
+
tobacco: ["Pipe Tobacco 1oz", "Cigar Sampler Pack", "Rolling Papers 32ct", "Loose Leaf Blend"],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function buildRow(rng: () => number, index: number, targetTenant: string, vertical: string): OrderRow {
|
|
39
|
+
const items = ITEM_POOLS[vertical] ?? ITEM_POOLS.vape!;
|
|
40
|
+
const total = randomInt(rng, 45, 420);
|
|
41
|
+
const isHold = randomBool(rng, index === 0 ? 1 : 0.28);
|
|
42
|
+
const isCompleted = !isHold && randomBool(rng, 0.35);
|
|
43
|
+
const payment = isHold ? "authorized" : "captured";
|
|
44
|
+
const shipState = isHold ? "hold_review_required" : isCompleted ? "completed" : "ready_to_ship";
|
|
45
|
+
const blocker = isHold ? pick(rng, ["Payment capture", "Manual review", "Age verification hold"]) : null;
|
|
46
|
+
|
|
47
|
+
const customerIndex = randomInt(rng, 0, CUSTOMER_NAMES.length - 1);
|
|
48
|
+
const itemIndex = randomInt(rng, 0, items.length - 1);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
id: `#${2_200_000_000 + Math.floor(rng() * 90_000_000)}`,
|
|
52
|
+
placedAt: offsetIso(MOCK_ANCHOR_ISO, -randomInt(rng, 5, 600)),
|
|
53
|
+
customerId: `cust-${targetTenant}-${customerIndex}`,
|
|
54
|
+
customerName: CUSTOMER_NAMES[customerIndex]!,
|
|
55
|
+
customerNote: index === 0 ? "First-time customer" : `Returning customer, ${randomInt(rng, 2, 6)} orders`,
|
|
56
|
+
itemsSummary: items[itemIndex]!,
|
|
57
|
+
productId: `prod-${targetTenant}-${itemIndex}`,
|
|
58
|
+
total,
|
|
59
|
+
payment,
|
|
60
|
+
shipState,
|
|
61
|
+
blocker,
|
|
62
|
+
nextStep: isHold ? (blocker === "Payment capture" ? "Capture payment" : "Review risk") : isCompleted ? "View record" : "Create label",
|
|
63
|
+
riskLevel: isHold ? "high" : randomBool(rng, 0.15) ? "medium" : "low",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildDetail(row: OrderRow, rng: () => number): OrderDetail {
|
|
68
|
+
const lineItems: OrderLineItem[] = [
|
|
69
|
+
{ sku: `sku-${row.id.slice(1, 6)}`, name: row.itemsSummary, quantity: 1, unitPrice: Math.round(row.total * 0.82) },
|
|
70
|
+
];
|
|
71
|
+
const shipping = 5;
|
|
72
|
+
const estimatedTax = 0;
|
|
73
|
+
const taxesAndFees = row.total - lineItems[0]!.unitPrice - shipping;
|
|
74
|
+
|
|
75
|
+
const evidenceTimeline: OrderEvidenceEvent[] = [
|
|
76
|
+
{ title: "Order imported", detail: "Storefront order became available in the admin queue.", at: row.placedAt },
|
|
77
|
+
{ title: "Payment authorized", detail: "The customer completed checkout and payment was authorized.", at: offsetIso(row.placedAt, 0) },
|
|
78
|
+
];
|
|
79
|
+
if (row.blocker) {
|
|
80
|
+
evidenceTimeline.push({ title: "Hold added", detail: `${row.blocker} must be resolved before shipment.`, at: offsetIso(row.placedAt, 1) });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
...row,
|
|
85
|
+
lineItems,
|
|
86
|
+
shipping,
|
|
87
|
+
estimatedTax,
|
|
88
|
+
taxesAndFees,
|
|
89
|
+
readinessChecks: [
|
|
90
|
+
{
|
|
91
|
+
key: "payment_capture",
|
|
92
|
+
label: "Payment capture",
|
|
93
|
+
value: row.payment === "captured" ? "Captured" : "Authorized",
|
|
94
|
+
detail: "Capture the authorization before creating the shipping label.",
|
|
95
|
+
tone: row.payment === "captured" ? "green" : "amber",
|
|
96
|
+
action: "Capture payment",
|
|
97
|
+
blocking: row.payment !== "captured",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: "tax_and_fees",
|
|
101
|
+
label: "Tax and fees",
|
|
102
|
+
value: "Collected",
|
|
103
|
+
detail: "Required amounts are included in the order total.",
|
|
104
|
+
tone: "blue",
|
|
105
|
+
action: "View total",
|
|
106
|
+
blocking: false,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: "identity_and_age",
|
|
110
|
+
label: "Identity and age",
|
|
111
|
+
value: row.blocker === "Age verification hold" ? "Needs review" : "Passed",
|
|
112
|
+
detail: "Checkout assurance completed before order placement.",
|
|
113
|
+
tone: row.blocker === "Age verification hold" ? "amber" : "green",
|
|
114
|
+
action: "View evidence",
|
|
115
|
+
blocking: row.blocker === "Age verification hold",
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
key: "address",
|
|
119
|
+
label: "Address",
|
|
120
|
+
value: "Serviceable",
|
|
121
|
+
detail: "Destination is deliverable after any hold is resolved.",
|
|
122
|
+
tone: "green",
|
|
123
|
+
action: "View address",
|
|
124
|
+
blocking: false,
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
evidenceTimeline,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function computeOrdersViewModel(envelope: AdminTenantEnvelope): OrdersViewModel {
|
|
132
|
+
const { targetTenant } = envelope;
|
|
133
|
+
const profile = tenantProfile(targetTenant);
|
|
134
|
+
const rng = seededRandom(`orders:${targetTenant}`);
|
|
135
|
+
|
|
136
|
+
const rowCount = randomInt(rng, 5, 8);
|
|
137
|
+
const rows = Array.from({ length: rowCount }, (_, index) => buildRow(rng, index, targetTenant, profile.vertical));
|
|
138
|
+
|
|
139
|
+
// Newest-first, so the "All orders — Newest first" queue copy and the
|
|
140
|
+
// "Oldest open order first" unfulfilled copy actually hold, and so
|
|
141
|
+
// today.oldestOpenAt below can be read off the chronological tail.
|
|
142
|
+
rows.sort((a, b) => new Date(b.placedAt).getTime() - new Date(a.placedAt).getTime());
|
|
143
|
+
|
|
144
|
+
const detailsById: Record<string, OrderDetail> = {};
|
|
145
|
+
for (const row of rows) {
|
|
146
|
+
detailsById[row.id] = buildDetail(row, rng);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const needsAttention = rows.filter((r) => r.shipState === "hold_review_required").length;
|
|
150
|
+
const readyToShip = rows.filter((r) => r.shipState === "ready_to_ship").length;
|
|
151
|
+
const completed = rows.filter((r) => r.shipState === "completed").length;
|
|
152
|
+
|
|
153
|
+
const queues: OrderQueueSummary[] = [
|
|
154
|
+
{ key: "needs_attention", label: "Needs attention", count: needsAttention, detail: "Hold before shipment" },
|
|
155
|
+
{ key: "ready_to_ship", label: "Ready to ship", count: readyToShip, detail: "Labels available" },
|
|
156
|
+
{ key: "unfulfilled", label: "Unfulfilled", count: readyToShip + needsAttention, detail: "Oldest open order first" },
|
|
157
|
+
{ key: "all", label: "All orders", count: rows.length, detail: "Newest first" },
|
|
158
|
+
{ key: "completed", label: "Completed", count: completed, detail: "Delivered" },
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const paidOrders = rows.filter((r) => r.payment === "captured");
|
|
162
|
+
const grossSales = paidOrders.reduce((sum, r) => sum + r.total, 0);
|
|
163
|
+
|
|
164
|
+
const openPlacedAt = rows.filter((r) => r.shipState !== "completed").map((r) => r.placedAt);
|
|
165
|
+
const oldestOpenAt = openPlacedAt.length
|
|
166
|
+
? openPlacedAt.reduce((oldest, at) => (at < oldest ? at : oldest))
|
|
167
|
+
: null;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
targetTenant,
|
|
171
|
+
queues,
|
|
172
|
+
rows,
|
|
173
|
+
detailsById,
|
|
174
|
+
today: {
|
|
175
|
+
grossSales,
|
|
176
|
+
paidOrders: paidOrders.length,
|
|
177
|
+
averageOrder: paidOrders.length ? Math.round((grossSales / paidOrders.length) * 100) / 100 : 0,
|
|
178
|
+
refunds: 0,
|
|
179
|
+
oldestOpenAt,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const getOrdersViewModel = memoizeByEnvelope(computeOrdersViewModel);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { AdminTenantEnvelope } from "../../envelope";
|
|
2
|
+
import { MOCK_ANCHOR_ISO, offsetIso, pick, randomBool, randomInt, seededRandom } from "../../seed";
|
|
3
|
+
import type {
|
|
4
|
+
ExciseCheckKey,
|
|
5
|
+
ExciseReadinessCheck,
|
|
6
|
+
ProductDetail,
|
|
7
|
+
ProductRow,
|
|
8
|
+
ProductsViewModel,
|
|
9
|
+
ProductVariant,
|
|
10
|
+
} from "../../viewmodels/products";
|
|
11
|
+
import { tenantProfile } from "./tenants";
|
|
12
|
+
import { memoizeByEnvelope } from "../../memoizeByEnvelope";
|
|
13
|
+
|
|
14
|
+
// Must stay index-aligned with orders.ts's ITEM_POOLS: a product's id is
|
|
15
|
+
// `prod-<tenant>-<position here>`, and orders.ts derives each order's productId
|
|
16
|
+
// from the same position, so the evidence graph can join order→product by id.
|
|
17
|
+
const PRODUCT_NAMES: Record<string, string[]> = {
|
|
18
|
+
vape: ["Vape Pen Starter Kit", "Disposable Vape 5000", "Nicotine Pouches 6mg", "Replacement Coil 3-Pack"],
|
|
19
|
+
tobacco: ["Pipe Tobacco 1oz", "Cigar Sampler Pack", "Rolling Papers 32ct", "Loose Leaf Blend"],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const EXCISE_CHECK_DEFS: { key: ExciseCheckKey; label: string }[] = [
|
|
23
|
+
{ key: "tax_class", label: "Tax class" },
|
|
24
|
+
{ key: "exempt_flag", label: "Exempt flag" },
|
|
25
|
+
{ key: "nicotine_flag", label: "Nicotine flag" },
|
|
26
|
+
{ key: "wholesale_basis", label: "Wholesale basis" },
|
|
27
|
+
{ key: "retail_price", label: "Retail price" },
|
|
28
|
+
{ key: "pack_quantity", label: "Pack quantity" },
|
|
29
|
+
{ key: "volume_or_weight", label: "Volume or weight" },
|
|
30
|
+
{ key: "category", label: "Product category" },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function buildExciseChecks(rng: () => number): ExciseReadinessCheck[] {
|
|
34
|
+
return EXCISE_CHECK_DEFS.map((def) => {
|
|
35
|
+
const present = randomBool(rng, 0.82);
|
|
36
|
+
return {
|
|
37
|
+
key: def.key,
|
|
38
|
+
label: def.label,
|
|
39
|
+
present,
|
|
40
|
+
value: present ? pick(rng, ["Set", "Confirmed", "20mg", "Case of 12", "Vape"]) : null,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function computeProductsViewModel(envelope: AdminTenantEnvelope): ProductsViewModel {
|
|
46
|
+
const { targetTenant } = envelope;
|
|
47
|
+
const profile = tenantProfile(targetTenant);
|
|
48
|
+
const rng = seededRandom(`products:${targetTenant}`);
|
|
49
|
+
const names = PRODUCT_NAMES[profile.vertical] ?? PRODUCT_NAMES.vape!;
|
|
50
|
+
|
|
51
|
+
const rows: ProductRow[] = [];
|
|
52
|
+
const detailsById: Record<string, ProductDetail> = {};
|
|
53
|
+
|
|
54
|
+
names.forEach((name, index) => {
|
|
55
|
+
const id = `prod-${targetTenant}-${index}`;
|
|
56
|
+
const exciseChecks = buildExciseChecks(rng);
|
|
57
|
+
const missingExciseCount = exciseChecks.filter((c) => !c.present).length;
|
|
58
|
+
const readinessScore = Math.max(0, 100 - missingExciseCount * 15);
|
|
59
|
+
const sellable = missingExciseCount === 0;
|
|
60
|
+
|
|
61
|
+
const row: ProductRow = {
|
|
62
|
+
id,
|
|
63
|
+
name,
|
|
64
|
+
productType: profile.vertical === "vape" ? "Vaping device" : "Tobacco product",
|
|
65
|
+
category: profile.vertical === "vape" ? "Vape" : "Tobacco",
|
|
66
|
+
status: "active",
|
|
67
|
+
readinessScore,
|
|
68
|
+
sellable,
|
|
69
|
+
aiChannelReady: sellable && randomBool(rng, 0.6),
|
|
70
|
+
missingExciseCount,
|
|
71
|
+
};
|
|
72
|
+
rows.push(row);
|
|
73
|
+
|
|
74
|
+
const variants: ProductVariant[] = Array.from({ length: randomInt(rng, 1, 3) }, (_, vIndex) => ({
|
|
75
|
+
sku: `${id}-v${vIndex}`,
|
|
76
|
+
name: vIndex === 0 ? "Default" : `Variant ${vIndex}`,
|
|
77
|
+
inventory: randomInt(rng, 0, 250),
|
|
78
|
+
price: randomInt(rng, 15, 65),
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
detailsById[id] = {
|
|
82
|
+
...row,
|
|
83
|
+
variants,
|
|
84
|
+
media: [{ url: `/mock/admin/products/${id}.png`, alt: name, kind: "image" }],
|
|
85
|
+
exciseChecks,
|
|
86
|
+
activity: [
|
|
87
|
+
{ title: "Listed", detail: `${name} added to catalog.`, at: offsetIso(MOCK_ANCHOR_ISO, -randomInt(rng, 1000, 20000)) },
|
|
88
|
+
],
|
|
89
|
+
complianceNotes: missingExciseCount > 0 ? [`${missingExciseCount} excise metadata field(s) missing before this can sell.`] : [],
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
targetTenant,
|
|
95
|
+
savedViews: [
|
|
96
|
+
{ key: "all", label: "All products" },
|
|
97
|
+
{ key: "not_sellable", label: "Not sellable" },
|
|
98
|
+
{ key: "ai_channel_ready", label: "AI channel ready" },
|
|
99
|
+
],
|
|
100
|
+
rows,
|
|
101
|
+
detailsById,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const getProductsViewModel = memoizeByEnvelope(computeProductsViewModel);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AdminTenantEnvelope } from "../../envelope";
|
|
2
|
+
import { memoizeByEnvelope } from "../../memoizeByEnvelope";
|
|
3
|
+
import { pick, randomInt, seededRandom } from "../../seed";
|
|
4
|
+
import type { AnalysisBriefItem, ReportingCategory, ReportingSummary, ReportingViewModel } from "../../viewmodels/reporting";
|
|
5
|
+
|
|
6
|
+
const CATEGORY_DEFS: { category: ReportingCategory; label: string; drilldownHref: string }[] = [
|
|
7
|
+
{ category: "sales", label: "Sales", drilldownHref: "#orders" },
|
|
8
|
+
{ category: "products", label: "Products", drilldownHref: "#products" },
|
|
9
|
+
{ category: "customers", label: "Customers", drilldownHref: "#customers" },
|
|
10
|
+
{ category: "fulfillment", label: "Fulfillment", drilldownHref: "#fulfillment" },
|
|
11
|
+
{ category: "compliance", label: "Compliance", drilldownHref: "#products" },
|
|
12
|
+
{ category: "tax", label: "Tax", drilldownHref: "#settings" },
|
|
13
|
+
{ category: "risk", label: "Risk", drilldownHref: "#orders" },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const TRENDS = ["up", "down", "flat"] as const;
|
|
17
|
+
|
|
18
|
+
function computeReportingViewModel(envelope: AdminTenantEnvelope): ReportingViewModel {
|
|
19
|
+
const { targetTenant } = envelope;
|
|
20
|
+
const rng = seededRandom(`reporting:${targetTenant}`);
|
|
21
|
+
|
|
22
|
+
const summaries: ReportingSummary[] = CATEGORY_DEFS.map((def) => ({
|
|
23
|
+
category: def.category,
|
|
24
|
+
label: def.label,
|
|
25
|
+
headline: `${def.label} this week`,
|
|
26
|
+
value: def.category === "sales" ? `$${randomInt(rng, 4000, 18000).toLocaleString()}` : `${randomInt(rng, 2, 40)}`,
|
|
27
|
+
trend: pick(rng, TRENDS),
|
|
28
|
+
drilldownHref: def.drilldownHref,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const drivers: AnalysisBriefItem[] = [
|
|
32
|
+
{
|
|
33
|
+
title: "Hold volume rose on weekend orders",
|
|
34
|
+
detail: "Manual review holds increased; capacity to clear them stayed flat.",
|
|
35
|
+
drilldownHref: "#orders",
|
|
36
|
+
evidenceRef: "evidence:orders-hold-trend",
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
const watchItems: AnalysisBriefItem[] = [
|
|
40
|
+
{
|
|
41
|
+
title: "Excise metadata gaps in new listings",
|
|
42
|
+
detail: "Recently added products are missing pack quantity or tax class.",
|
|
43
|
+
drilldownHref: "#products",
|
|
44
|
+
evidenceRef: "evidence:products-excise-gaps",
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
return { targetTenant, summaries, analysisBrief: { drivers, watchItems } };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const getReportingViewModel = memoizeByEnvelope(computeReportingViewModel);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { DEFAULT_CHECKOUT_STYLE } from "@tot/public-runtime";
|
|
2
|
+
import type { AdminTenantEnvelope } from "../../envelope";
|
|
3
|
+
import { memoizeByEnvelope } from "../../memoizeByEnvelope";
|
|
4
|
+
import { MOCK_ANCHOR_ISO, offsetIso, randomInt, seededRandom } from "../../seed";
|
|
5
|
+
import type { SettingsAuditEvent, SettingsSection, SettingsViewModel } from "../../viewmodels/settings";
|
|
6
|
+
|
|
7
|
+
const SECTION_DEFS: Omit<SettingsSection, "riskyChangeReview">[] = [
|
|
8
|
+
{ key: "store", label: "Store", summary: "Store profile, branding, and white-label copy." },
|
|
9
|
+
{ key: "checkout", label: "Checkout", summary: "Checkout assurance and identity/age verification flow." },
|
|
10
|
+
{ key: "cart_appearance", label: "Cart & checkout appearance", summary: "FoxyCart brand colors and templates, prepopulated from the theme." },
|
|
11
|
+
{ key: "payments", label: "Payments", summary: "Payment capture timing and gateway configuration." },
|
|
12
|
+
{ key: "shipping", label: "Shipping", summary: "Carriers, rates, and shipping restrictions by region." },
|
|
13
|
+
{ key: "taxes", label: "Taxes", summary: "Excise and sales tax configuration by jurisdiction." },
|
|
14
|
+
{ key: "products", label: "Products", summary: "Catalog and category-to-tax-class mappings." },
|
|
15
|
+
{ key: "customers", label: "Customers", summary: "Segmentation and support-flag rules." },
|
|
16
|
+
{ key: "notifications", label: "Notifications", summary: "Merchant and customer notification templates." },
|
|
17
|
+
{ key: "users_access", label: "Users & access", summary: "Staff roles, permissions, and internal tenant picker access." },
|
|
18
|
+
{ key: "integrations", label: "Integrations", summary: "Connected services and API credentials." },
|
|
19
|
+
{ key: "compliance", label: "Compliance", summary: "Evidence retention and reporting cadence." },
|
|
20
|
+
{ key: "ai_admin_policy", label: "AI / admin policy", summary: "AI guardrails and approval mode for assist surfaces." },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const RISKY_SECTIONS = new Set(["payments", "shipping", "taxes", "users_access", "ai_admin_policy"]);
|
|
24
|
+
|
|
25
|
+
function computeSettingsViewModel(envelope: AdminTenantEnvelope): SettingsViewModel {
|
|
26
|
+
const { targetTenant } = envelope;
|
|
27
|
+
const rng = seededRandom(`settings:${targetTenant}`);
|
|
28
|
+
|
|
29
|
+
const sections: SettingsSection[] = SECTION_DEFS.map((def) => ({
|
|
30
|
+
...def,
|
|
31
|
+
riskyChangeReview: RISKY_SECTIONS.has(def.key),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const auditTrail: SettingsAuditEvent[] = [
|
|
35
|
+
{
|
|
36
|
+
operator: "internal-staff@tokenoftrust.com",
|
|
37
|
+
targetTenant,
|
|
38
|
+
action: "settings.shipping_restrictions.updated",
|
|
39
|
+
change: "Updated shipping restriction list",
|
|
40
|
+
at: offsetIso(MOCK_ANCHOR_ISO, -randomInt(rng, 60, 5000)),
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
targetTenant,
|
|
46
|
+
sections,
|
|
47
|
+
shippingRestrictions: [
|
|
48
|
+
{ region: "Utah", reason: "Vapor product shipping restriction" },
|
|
49
|
+
{ region: "Massachusetts", reason: "Flavored tobacco restriction" },
|
|
50
|
+
],
|
|
51
|
+
aiPolicyMode: "approval_required",
|
|
52
|
+
internalTenantPickerEnabled: true,
|
|
53
|
+
auditTrail,
|
|
54
|
+
// The admin console renders from mock data; the checkout-styling editor
|
|
55
|
+
// starts from the neutral baseline (an un-styled tenant). A real published
|
|
56
|
+
// override is resolved server-side via resolveCheckoutStyle at render time
|
|
57
|
+
// once the read path is wired in — until then the editor shows the default,
|
|
58
|
+
// and SAVE is already live (POST /api/admin/checkout-style publishes to the
|
|
59
|
+
// versioned spine). Cloning keeps the shared DEFAULT_CHECKOUT_STYLE constant
|
|
60
|
+
// immutable if a caller mutates the view model.
|
|
61
|
+
checkoutStyle: {
|
|
62
|
+
...DEFAULT_CHECKOUT_STYLE,
|
|
63
|
+
colors: { ...DEFAULT_CHECKOUT_STYLE.colors },
|
|
64
|
+
typography: { ...DEFAULT_CHECKOUT_STYLE.typography },
|
|
65
|
+
button: { ...DEFAULT_CHECKOUT_STYLE.button },
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const getSettingsViewModel = memoizeByEnvelope(computeSettingsViewModel);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeded tenant registry for admin mock data. Two curated tenants give the
|
|
3
|
+
* console realistic, vertical-flavored demo data; any other `targetTenant`
|
|
4
|
+
* still gets a stable (if generic) profile via deterministic fallback, so no
|
|
5
|
+
* caller has to special-case "unknown" tenants.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type MockVertical = "vape" | "tobacco";
|
|
9
|
+
|
|
10
|
+
export interface SeededTenantProfile {
|
|
11
|
+
targetTenant: string;
|
|
12
|
+
displayName: string;
|
|
13
|
+
vertical: MockVertical;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SEEDED_TENANTS: readonly SeededTenantProfile[] = [
|
|
17
|
+
{ targetTenant: "northridge-vapor.com", displayName: "Northridge Vapor Co.", vertical: "vape" },
|
|
18
|
+
{ targetTenant: "cascade-tobacco.com", displayName: "Cascade Tobacco Exchange", vertical: "tobacco" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/** Deterministic fallback profile for any tenant not in `SEEDED_TENANTS`. */
|
|
22
|
+
function fallbackProfile(targetTenant: string): SeededTenantProfile {
|
|
23
|
+
const vertical: MockVertical = targetTenant.length % 2 === 0 ? "vape" : "tobacco";
|
|
24
|
+
return { targetTenant, displayName: targetTenant, vertical };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function tenantProfile(targetTenant: string): SeededTenantProfile {
|
|
28
|
+
return SEEDED_TENANTS.find((t) => t.targetTenant === targetTenant) ?? fallbackProfile(targetTenant);
|
|
29
|
+
}
|