@tokenoftrust/storefront-runner 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +58 -0
- package/README.md +40 -0
- package/apps/storefront/.dev.vars.example +41 -0
- package/apps/storefront/.env.example +24 -0
- package/apps/storefront/astro.config.mjs +245 -0
- package/apps/storefront/env.d.ts +155 -0
- package/apps/storefront/lib/ensure-preview-reconcile-secret.sh +31 -0
- package/apps/storefront/lib/resolve-cf-token.sh +48 -0
- package/apps/storefront/lib/resolve-tot-credentials.sh +69 -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 +41 -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/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 +35 -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/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 +116 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +181 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +181 -0
- package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
- package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -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/compliance/NicotineWarning.astro +33 -0
- package/apps/storefront/src/components/home/Hero.astro +164 -0
- package/apps/storefront/src/components/islands/AgeGate.tsx +105 -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 +378 -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/nav/Footer.astro +148 -0
- package/apps/storefront/src/components/nav/Header.astro +190 -0
- package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
- package/apps/storefront/src/components/plp/FacetSidebar.astro +146 -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/config/devTenants.ts +16 -0
- package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
- package/apps/storefront/src/config/resolver.ts +46 -0
- package/apps/storefront/src/config/tenants.ts +193 -0
- package/apps/storefront/src/layouts/Layout.astro +303 -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 +107 -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 +65 -0
- package/apps/storefront/src/lib/colors.ts +97 -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 +289 -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/ai-widget-client.js +127 -0
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +132 -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 +29 -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 +136 -0
- package/apps/storefront/src/lib/maintenance.ts +71 -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/rawMarketingHtml.ts +133 -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 +524 -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 +187 -0
- package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
- package/apps/storefront/src/lib/url.ts +119 -0
- package/apps/storefront/src/lib/wishlist.ts +63 -0
- package/apps/storefront/src/middleware/index.ts +413 -0
- package/apps/storefront/src/pages/404.astro +61 -0
- package/apps/storefront/src/pages/[...slug].astro +90 -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/content-edit/ai-edit.ts +64 -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/cli-signin-code.ts +44 -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 +154 -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/dev.astro +653 -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 +443 -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 +646 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +85 -0
- package/apps/storefront/src/pages/style-guide/index.astro +94 -0
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
- package/apps/storefront/src/styles/global.css +277 -0
- package/apps/storefront/src/themes/reference.ts +61 -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/apps/storefront/wrangler.toml +175 -0
- package/package.json +23 -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/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.ts +184 -0
- package/packages/public-runtime/src/index.ts +30 -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 +243 -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/tot-dev.mjs +439 -0
- package/tenants/bigdvapor/content/blog.json +52 -0
- package/tenants/bigdvapor/content/chrome.html +149 -0
- package/tenants/bigdvapor/content/chrome.json +47 -0
- package/tenants/bigdvapor/content/home.html +463 -0
- package/tenants/bigdvapor/content/home.json +73 -0
- package/tenants/bigdvapor/content/pages/about.json +5 -0
- package/tenants/bigdvapor/content/pages/privacy.json +6 -0
- package/tenants/bigdvapor/content/pages/shipping-returns.json +6 -0
- package/tenants/bigdvapor/content/pages-html/blogs/news.html +86 -0
- package/tenants/bigdvapor/content/pages-html/pages/about-us.html +106 -0
- package/tenants/bigdvapor/content/pages-html/pages/contact-us.html +61 -0
- package/tenants/bigdvapor/content/pages-html/pages/privacy-policy.html +72 -0
- package/tenants/bigdvapor/content/pages-html/pages/shipping-returns.html +172 -0
- package/tenants/bigdvapor/content/themes/bigd-navy.json +109 -0
- package/tenants/bigdvapor/public/img/brands/adjust-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/again-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/al-fakher-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/arro-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/cookies-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/core-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/cyclone-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/dinner-lady-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/extre-bar-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/fifty-bar-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/firerose-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/flavor-beast-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/foger-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/kk-energy-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/kumi-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/lost-vape-dispoable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/melo-labs-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/mixo-vape-disposable.webp +0 -0
- package/tenants/bigdvapor/public/img/brands/one-tank-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/oxbar-vape-disposable.webp +0 -0
- package/tenants/bigdvapor/public/img/brands/pillow-talk-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/pod-king-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/raz-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/hero-texas.jpg +0 -0
- package/tenants/bigdvapor/public/img/og.jpg +0 -0
- package/tenants/bigdvapor/public/img/pages/AdobeStock_202106312_web.jpg +0 -0
- package/tenants/bigdvapor/public/img/pages/CREW_web.jpg +0 -0
- package/tenants/bigdvapor/public/img/pages/Hero-Image.jpg +0 -0
- package/tenants/bigdvapor/public/img/tiles/adjust.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/again.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/al-fakher.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/arro.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/cookies.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/core.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/cyclone.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/disposables.svg +23 -0
- package/tenants/bigdvapor/public/logo-wordmark.png +0 -0
- package/tenants/bigdvapor/public/pages/home.css +118 -0
- package/tenants/bigdvapor/public/pages/mkt.css +185 -0
- package/tenants/bigdvapor/public/pages/page.css +148 -0
- package/tenants/bigdvapor/public/themes/bigd-navy.css +73 -0
- package/tenants/bigdvapor/theme.json +12 -0
- package/tenants/giantvapes/content/chrome.html +152 -0
- package/tenants/giantvapes/content/chrome.json +94 -0
- package/tenants/giantvapes/content/home.html +194 -0
- package/tenants/giantvapes/content/home.json +50 -0
- package/tenants/giantvapes/content/pages/about.json +10 -0
- package/tenants/giantvapes/content/pages/privacy.json +6 -0
- package/tenants/giantvapes/content/pages/shipping-returns.json +6 -0
- package/tenants/giantvapes/content/pages-html/blogs/news.html +68 -0
- package/tenants/giantvapes/content/pages-html/pages/about-us.html +95 -0
- package/tenants/giantvapes/content/pages-html/pages/contact-us.html +68 -0
- package/tenants/giantvapes/content/pages-html/pages/privacy-policy.html +65 -0
- package/tenants/giantvapes/content/pages-html/pages/shipping-returns.html +77 -0
- package/tenants/giantvapes/content/themes/giant-navy.json +73 -0
- package/tenants/giantvapes/public/img/hero-suicide-bunny.jpg +0 -0
- package/tenants/giantvapes/public/img/hero.webp +0 -0
- package/tenants/giantvapes/public/img/og.jpg +0 -0
- package/tenants/giantvapes/public/logo-wordmark.png +0 -0
- package/tenants/giantvapes/public/pages/home.css +120 -0
- package/tenants/giantvapes/public/pages/mkt.css +185 -0
- package/tenants/giantvapes/public/pages/page.css +155 -0
- package/tenants/giantvapes/public/themes/giant-navy.css +76 -0
- package/tenants/giantvapes/theme.json +39 -0
- package/tenants/home/content/chrome.json +11 -0
- package/tenants/home/content/home.html +304 -0
- package/tenants/home/content/home.json +13 -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/tenants/tokenoftrust/.tot/config.json +9 -0
- package/tenants/tokenoftrust/content/chrome.json +55 -0
- package/tenants/tokenoftrust/content/home.html +723 -0
- package/tenants/tokenoftrust/content/home.json +233 -0
- package/tenants/tokenoftrust/content/pages-html/careers.html +231 -0
- package/tenants/tokenoftrust/content/pages-html/company.html +275 -0
- package/tenants/tokenoftrust/content/pages-html/contact/contact-sales.html +188 -0
- package/tenants/tokenoftrust/content/pages-html/contact/startup-apply.html +206 -0
- package/tenants/tokenoftrust/content/pages-html/contact.html +178 -0
- package/tenants/tokenoftrust/content/pages-html/industries/adult-content-age-verification.html +286 -0
- package/tenants/tokenoftrust/content/pages-html/industries/cannabis.html +277 -0
- package/tenants/tokenoftrust/content/pages-html/pact-act-pricing.html +289 -0
- package/tenants/tokenoftrust/content/pages-html/pricing.html +483 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-estimation.html +278 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-gate.html +279 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-verification.html +265 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance.html +173 -0
- package/tenants/tokenoftrust/content/pages-html/product/compliance/aml.html +214 -0
- package/tenants/tokenoftrust/content/pages-html/product/compliance/pact-act.html +226 -0
- package/tenants/tokenoftrust/content/pages-html/product/fraud-prevention.html +254 -0
- package/tenants/tokenoftrust/content/pages-html/product/social-profile-verification.html +212 -0
- package/tenants/tokenoftrust/content/pages-html/product/tax/excise-tax.html +213 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/aml.html +259 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/biometric-selfie.html +305 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/coverage.html +254 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/document-verification.html +306 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/electronic-identity-verification.html +304 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/government-id-verification.html +308 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification.html +183 -0
- package/tenants/tokenoftrust/content/pages-html/product.html +228 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations/bigcommerce-integration.html +220 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations/shopify-integration.html +221 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations/wordpress.html +220 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations.html +172 -0
- package/tenants/tokenoftrust/content/pages-html/resources/refer-a-friend-program.html +317 -0
- package/tenants/tokenoftrust/content/pages-html/resources.html +177 -0
- package/tenants/tokenoftrust/content/pages-html/reviews.html +273 -0
- package/tenants/tokenoftrust/content/pages-html/solutions.html +292 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-alcohol.html +289 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-cigars-and-tobacco.html +286 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-firearm-accessories.html +267 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-peptides.html +273 -0
- package/tenants/tokenoftrust/content/themes/tot-navy.json +88 -0
- package/tenants/tokenoftrust/public/favicon.png +0 -0
- package/tenants/tokenoftrust/public/logo-icon.png +0 -0
- package/tenants/tokenoftrust/public/logo-wordmark.png +0 -0
- package/tenants/tokenoftrust/public/pages/company.css +108 -0
- package/tenants/tokenoftrust/public/pages/home.css +457 -0
- package/tenants/tokenoftrust/public/pages/mkt.css +482 -0
- package/tenants/tokenoftrust/public/pages/pricing.css +214 -0
- package/tenants/tokenoftrust/public/pages/solutions.css +66 -0
- package/tenants/tokenoftrust/public/themes/tot-navy-1.0.0.css +136 -0
- package/tenants/tokenoftrust/public/themes/tot-navy.css +358 -0
- package/tenants/tokenoftrust/theme.json +31 -0
- package/tsconfig.base.json +20 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* /dashboard/<appDomain> — a single vendor's console (#8425 Stage 0).
|
|
4
|
+
*
|
|
5
|
+
* Guarded by the signed-in session: the viewer must hold a membership for this
|
|
6
|
+
* appDomain (near-side authZ, deny-by-default), else bounce to the picker. The
|
|
7
|
+
* page is a thin hub — today it links to the Team page (invite developers) and
|
|
8
|
+
* the existing preview/grants surfaces for this tenant.
|
|
9
|
+
*/
|
|
10
|
+
export const prerender = false;
|
|
11
|
+
|
|
12
|
+
import { readViewerSession } from "@/lib/auth/route";
|
|
13
|
+
import { membershipFor } from "@/lib/dashboard/tenantSelection";
|
|
14
|
+
|
|
15
|
+
const cspNonce = Astro.locals.cspNonce;
|
|
16
|
+
const appDomain = decodeURIComponent(Astro.params.appDomain ?? "");
|
|
17
|
+
|
|
18
|
+
const session = await readViewerSession(Astro);
|
|
19
|
+
if (!session) {
|
|
20
|
+
return Astro.redirect(
|
|
21
|
+
`/auth/login?next=${encodeURIComponent(`/dashboard/${appDomain}`)}`,
|
|
22
|
+
302,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
// Member OR ToT-staff admitted via an explicit vendor selection (distinct path —
|
|
26
|
+
// the staff marker is not a tenants[] membership). membershipFor returns a
|
|
27
|
+
// staff-derived stand-in (role = the staffRoles capability) when the selection is live.
|
|
28
|
+
const membership = membershipFor(session.tenants, appDomain, {
|
|
29
|
+
selection: session.staffSelection,
|
|
30
|
+
nowSeconds: Math.floor(Date.now() / 1000),
|
|
31
|
+
});
|
|
32
|
+
if (!membership) {
|
|
33
|
+
// Signed in but no access to THIS vendor — back to the picker/search.
|
|
34
|
+
return Astro.redirect("/dashboard", 302);
|
|
35
|
+
}
|
|
36
|
+
const teamHref = `/dashboard/${encodeURIComponent(appDomain)}/team`;
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<html lang="en">
|
|
40
|
+
<head>
|
|
41
|
+
<meta charset="utf-8" />
|
|
42
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
43
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
44
|
+
<title>{appDomain} — vendor dashboard</title>
|
|
45
|
+
<style nonce={cspNonce}>
|
|
46
|
+
:root { color-scheme: light dark; }
|
|
47
|
+
* { box-sizing: border-box; }
|
|
48
|
+
body { font: 15px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
49
|
+
margin: 0; min-height: 100vh; padding: 2.5rem 1.25rem; background: #f5f4f0; color: #1c1c1c; }
|
|
50
|
+
@media (prefers-color-scheme: dark) { body { background: #14141a; color: #ececf1; } }
|
|
51
|
+
main { max-width: 44rem; margin-inline: auto; }
|
|
52
|
+
.top { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
|
|
53
|
+
.eyebrow { font: 600 .68rem/1 ui-monospace, monospace; letter-spacing: .12em; text-transform: uppercase; color: #8a7f66; margin: 0 0 .5rem; }
|
|
54
|
+
h1 { font-size: 1.5rem; letter-spacing: -.01em; margin: 0 0 .3rem; }
|
|
55
|
+
.role { font: 600 .68rem/1 ui-monospace, monospace; text-transform: uppercase; letter-spacing: .06em;
|
|
56
|
+
color: #4a6; border: 1px solid currentColor; border-radius: 999px; padding: .2rem .55rem; }
|
|
57
|
+
.sub { color: #6a6a6a; margin: .2rem 0 1.75rem; }
|
|
58
|
+
@media (prefers-color-scheme: dark) { .sub { color: #a2a2ad; } }
|
|
59
|
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
|
60
|
+
@media (max-width: 32rem) { .grid { grid-template-columns: 1fr; } }
|
|
61
|
+
a.card { display: block; text-decoration: none; color: inherit; background: #fff;
|
|
62
|
+
border: 1px solid #e6e4de; border-radius: 12px; padding: 1.15rem 1.25rem; }
|
|
63
|
+
@media (prefers-color-scheme: dark) { a.card { background: #1e1e28; border-color: #33333f; } }
|
|
64
|
+
a.card:hover { border-color: #b7d0ff; }
|
|
65
|
+
.card .h { font-weight: 600; margin: 0 0 .25rem; }
|
|
66
|
+
.card .d { color: #8a8a8a; font-size: .88rem; margin: 0; }
|
|
67
|
+
.who { margin-top: 2rem; font-size: .82rem; color: #8a8a8a; }
|
|
68
|
+
.who a { color: inherit; }
|
|
69
|
+
.linkbtn { background: none; border: 0; color: inherit; font: inherit; text-decoration: underline; cursor: pointer; padding: 0; }
|
|
70
|
+
.logout { display: inline; }
|
|
71
|
+
</style>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<main>
|
|
75
|
+
<div class="top">
|
|
76
|
+
<div>
|
|
77
|
+
<p class="eyebrow">Vendor dashboard</p>
|
|
78
|
+
<h1>{appDomain}</h1>
|
|
79
|
+
</div>
|
|
80
|
+
<span class="role">{membership.role}</span>
|
|
81
|
+
</div>
|
|
82
|
+
<p class="sub">Manage this vendor's developer team and preview.</p>
|
|
83
|
+
|
|
84
|
+
<div class="grid">
|
|
85
|
+
<a class="card" href={teamHref}>
|
|
86
|
+
<p class="h">Team & invites →</p>
|
|
87
|
+
<p class="d">See who has developer access and invite new developers by email.</p>
|
|
88
|
+
</a>
|
|
89
|
+
<a class="card" href={`/preview/dashboard?tenant=${encodeURIComponent(appDomain)}`}>
|
|
90
|
+
<p class="h">Preview & deploy →</p>
|
|
91
|
+
<p class="d">The tenant's shared preview: target vs actual, reconcile, preview URL.</p>
|
|
92
|
+
</a>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<p class="who">Signed in as {session.email} ·
|
|
96
|
+
<a href="/dashboard">Switch vendor</a> ·
|
|
97
|
+
<form class="logout" method="post" action="/api/auth/logout"><button type="submit" class="linkbtn">Sign out</button></form></p>
|
|
98
|
+
</main>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* /dashboard/<appDomain>/team — a vendor's developer team + invite-by-email
|
|
4
|
+
* (#8425 Stage 0).
|
|
5
|
+
*
|
|
6
|
+
* Guarded by the signed-in session (membership for this appDomain). The page is a
|
|
7
|
+
* thin client over two session-authed endpoints:
|
|
8
|
+
* GET /api/grants/team-members?tenant=<appDomain> → current developer invites
|
|
9
|
+
* POST /api/grants/team-invite {tenant, email} → invite a developer
|
|
10
|
+
* Both re-check membership server-side; the appDomain here is carried from the
|
|
11
|
+
* guarded route, and every call is authorized by the `tot_session` cookie.
|
|
12
|
+
*/
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
import { readViewerSession } from "@/lib/auth/route";
|
|
16
|
+
import { membershipFor } from "@/lib/dashboard/tenantSelection";
|
|
17
|
+
|
|
18
|
+
const cspNonce = Astro.locals.cspNonce;
|
|
19
|
+
const appDomain = decodeURIComponent(Astro.params.appDomain ?? "");
|
|
20
|
+
|
|
21
|
+
const session = await readViewerSession(Astro);
|
|
22
|
+
if (!session) {
|
|
23
|
+
return Astro.redirect(
|
|
24
|
+
`/auth/login?next=${encodeURIComponent(`/dashboard/${appDomain}/team`)}`,
|
|
25
|
+
302,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
// Member OR ToT-staff admitted via an explicit vendor selection (distinct path).
|
|
29
|
+
const membership = membershipFor(session.tenants, appDomain, {
|
|
30
|
+
selection: session.staffSelection,
|
|
31
|
+
nowSeconds: Math.floor(Date.now() / 1000),
|
|
32
|
+
});
|
|
33
|
+
if (!membership) {
|
|
34
|
+
return Astro.redirect("/dashboard", 302);
|
|
35
|
+
}
|
|
36
|
+
// #8453 (`staff-can-invite-governed`, REVERSING the earlier members-only block):
|
|
37
|
+
// a ToT-staff viewer admitted via an explicit vendor selection may now invite too —
|
|
38
|
+
// the write endpoint mints a signed broker assertion from their session email and
|
|
39
|
+
// tot20 verifies it to permit the invite. So the form shows for member OR staff
|
|
40
|
+
// (everyone admitted to this page). `isRealMember` is kept only to tailor the copy.
|
|
41
|
+
const isRealMember = !!membershipFor(session.tenants, appDomain);
|
|
42
|
+
const backHref = `/dashboard/${encodeURIComponent(appDomain)}`;
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
<html lang="en">
|
|
46
|
+
<head>
|
|
47
|
+
<meta charset="utf-8" />
|
|
48
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
49
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
50
|
+
<title>Team — {appDomain}</title>
|
|
51
|
+
<style nonce={cspNonce}>
|
|
52
|
+
:root { color-scheme: light dark; }
|
|
53
|
+
* { box-sizing: border-box; }
|
|
54
|
+
body { font: 15px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
55
|
+
margin: 0; min-height: 100vh; padding: 2.5rem 1.25rem; background: #f5f4f0; color: #1c1c1c; }
|
|
56
|
+
@media (prefers-color-scheme: dark) { body { background: #14141a; color: #ececf1; } }
|
|
57
|
+
main { max-width: 44rem; margin-inline: auto; }
|
|
58
|
+
.eyebrow { font: 600 .68rem/1 ui-monospace, monospace; letter-spacing: .12em; text-transform: uppercase; color: #8a7f66; margin: 0 0 .5rem; }
|
|
59
|
+
h1 { font-size: 1.5rem; letter-spacing: -.01em; margin: 0 0 .3rem; }
|
|
60
|
+
h2 { font-size: .95rem; text-transform: uppercase; letter-spacing: .05em; color: #777; margin: 2rem 0 .6rem; }
|
|
61
|
+
.sub { color: #6a6a6a; margin: 0 0 1rem; }
|
|
62
|
+
@media (prefers-color-scheme: dark) { .sub { color: #a2a2ad; } }
|
|
63
|
+
.card { background: #fff; border: 1px solid #e6e4de; border-radius: 12px; padding: 1.25rem; }
|
|
64
|
+
@media (prefers-color-scheme: dark) { .card { background: #1e1e28; border-color: #33333f; } }
|
|
65
|
+
label { display: block; font-weight: 600; font-size: .82rem; margin: 0 0 .4rem; }
|
|
66
|
+
label[for=role], label[for=note] { margin-top: .9rem; }
|
|
67
|
+
input[type=email], select, textarea { width: 100%; padding: .7rem .8rem; font-size: 1rem; border-radius: 9px;
|
|
68
|
+
border: 1px solid #cfccc4; background: #fff; color: inherit; font-family: inherit; }
|
|
69
|
+
select { margin-bottom: .2rem; }
|
|
70
|
+
textarea { min-height: 4.5rem; resize: vertical; line-height: 1.4; }
|
|
71
|
+
@media (prefers-color-scheme: dark) { input[type=email], select, textarea { background: #14141a; border-color: #40404d; } }
|
|
72
|
+
button { margin-top: 1rem; padding: .7rem 1.1rem; border: 0; border-radius: 9px; background: #1a1a1a;
|
|
73
|
+
color: #fff; font-weight: 600; font-size: .95rem; cursor: pointer; }
|
|
74
|
+
@media (prefers-color-scheme: dark) { button { background: #ececf1; color: #14141a; } }
|
|
75
|
+
button:disabled { opacity: .5; cursor: default; }
|
|
76
|
+
.rowbtn { margin: 0; padding: .3rem .6rem; font-size: .78rem; background: transparent; color: inherit;
|
|
77
|
+
border: 1px solid #cfccc4; border-radius: 7px; font-weight: 600; }
|
|
78
|
+
@media (prefers-color-scheme: dark) { .rowbtn { border-color: #40404d; } }
|
|
79
|
+
.rowbtn:hover:not(:disabled) { border-color: #b7d0ff; }
|
|
80
|
+
td.act { text-align: right; white-space: nowrap; }
|
|
81
|
+
table { width: 100%; border-collapse: collapse; }
|
|
82
|
+
th, td { text-align: left; padding: .5rem .4rem; border-bottom: 1px solid #e6e4de; font-size: .9rem; }
|
|
83
|
+
@media (prefers-color-scheme: dark) { th, td { border-color: #33333f; } }
|
|
84
|
+
th { font: 600 .7rem/1 ui-monospace, monospace; text-transform: uppercase; letter-spacing: .05em; color: #999; }
|
|
85
|
+
.msg { margin-top: 1rem; font-size: .9rem; border-radius: 9px; padding: .6rem .8rem; display: none; }
|
|
86
|
+
.msg.ok { display: block; background: #eaf7ee; border: 1px solid #bfe4c9; color: #1b6b34; }
|
|
87
|
+
.msg.err { display: block; background: #fdecec; border: 1px solid #f5c2c2; color: #9a1c1c; }
|
|
88
|
+
@media (prefers-color-scheme: dark) {
|
|
89
|
+
.msg.ok { background: #16311f; border-color: #2f6b41; color: #a7e0ba; }
|
|
90
|
+
.msg.err { background: #3a1f22; border-color: #6b2f34; color: #f6b9b9; }
|
|
91
|
+
}
|
|
92
|
+
.muted { color: #9a9a9a; font-size: .85rem; }
|
|
93
|
+
.who { margin-top: 2rem; font-size: .82rem; color: #8a8a8a; }
|
|
94
|
+
.who a, a.back { color: inherit; }
|
|
95
|
+
.linkbtn { background: none; border: 0; color: inherit; font: inherit; text-decoration: underline; cursor: pointer; padding: 0; margin: 0; }
|
|
96
|
+
.logout { display: inline; }
|
|
97
|
+
</style>
|
|
98
|
+
</head>
|
|
99
|
+
<body>
|
|
100
|
+
<main>
|
|
101
|
+
<p class="eyebrow"><a class="back" href={backHref}>← {appDomain}</a></p>
|
|
102
|
+
<h1>Team</h1>
|
|
103
|
+
<p class="sub">Developers with access to <strong>{appDomain}</strong>.{isRealMember
|
|
104
|
+
? " Invite by email — they'll get a branded sign-in link and become eligible on this vendor."
|
|
105
|
+
: " Viewing as Token of Trust staff — invite by email to grant a developer access."}</p>
|
|
106
|
+
|
|
107
|
+
<h2>Invite a developer</h2>
|
|
108
|
+
<div class="card">
|
|
109
|
+
<label for="email">Developer email</label>
|
|
110
|
+
<input id="email" type="email" placeholder="dev@agency.com" autocomplete="off" />
|
|
111
|
+
{!isRealMember && (
|
|
112
|
+
<>
|
|
113
|
+
{/* #8453: ToT staff may grant any role; a plain member is developer-only
|
|
114
|
+
(no selector — tot20 ignores a member's role and pins to developer).
|
|
115
|
+
Values are tot20's role vocabulary: developer | appAdmin | appOwner. */}
|
|
116
|
+
<label for="role">Role</label>
|
|
117
|
+
<select id="role">
|
|
118
|
+
<option value="developer" selected>Developer</option>
|
|
119
|
+
<option value="appAdmin">Admin</option>
|
|
120
|
+
<option value="appOwner">Owner</option>
|
|
121
|
+
</select>
|
|
122
|
+
</>
|
|
123
|
+
)}
|
|
124
|
+
<label for="note">Note to the developer (optional)</label>
|
|
125
|
+
<textarea id="note" rows="3" maxlength="1000"
|
|
126
|
+
placeholder="Add a personal message — they'll see it in the invite email."></textarea>
|
|
127
|
+
<button id="inviteBtn" type="button">Send invite</button>
|
|
128
|
+
<div id="msg" class="msg"></div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<h2>Current developers</h2>
|
|
132
|
+
<div class="card">
|
|
133
|
+
<table>
|
|
134
|
+
<thead><tr><th>Developer</th><th>Roles</th><th></th></tr></thead>
|
|
135
|
+
<tbody id="members" data-tenant={appDomain}><tr><td colspan="3" class="muted">Loading…</td></tr></tbody>
|
|
136
|
+
</table>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<p class="who">Signed in as {session.email} ·
|
|
140
|
+
<a href="/dashboard">Switch vendor</a> ·
|
|
141
|
+
<form class="logout" method="post" action="/api/auth/logout"><button type="submit" class="linkbtn">Sign out</button></form></p>
|
|
142
|
+
</main>
|
|
143
|
+
|
|
144
|
+
<!--
|
|
145
|
+
Client behavior is a STATIC same-origin file (public/js/dashboard-team.js),
|
|
146
|
+
referenced via src so the strict CSP allows it through `script-src 'self'` — no
|
|
147
|
+
nonce, no per-build content-hash. An inline <script> here is blocked: Astro's
|
|
148
|
+
`is:inline` drops the templated nonce, and a processed inline script would need
|
|
149
|
+
its hash pinned in cspStaticHashes on every edit. `is:inline` keeps Astro from
|
|
150
|
+
trying to bundle the external-src tag; `defer` runs it after the DOM parses.
|
|
151
|
+
-->
|
|
152
|
+
<script src="/js/dashboard-team.js" is:inline defer></script>
|
|
153
|
+
</body>
|
|
154
|
+
</html>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* /dashboard — the vendor console entry (#8425 Stage 0).
|
|
4
|
+
*
|
|
5
|
+
* Federated: the viewer signed in via ToT (email OTP → offline-verified id_token →
|
|
6
|
+
* `tot_session`). This page holds NO identity of its own — it reads the session's
|
|
7
|
+
* tenant memberships and routes per the L3 contract
|
|
8
|
+
* (docs/architecture/identity-and-authorization.md):
|
|
9
|
+
* 0 memberships → "nothing to manage"; 1 → straight into that vendor; ≥2 → picker.
|
|
10
|
+
*
|
|
11
|
+
* STAGE-0: tot20's id_token currently carries only the one resource signed against,
|
|
12
|
+
* so this resolves to a single vendor in practice; the picker activates unchanged
|
|
13
|
+
* once tot20 populates all memberships (flagged to the workstream lead).
|
|
14
|
+
*/
|
|
15
|
+
export const prerender = false;
|
|
16
|
+
|
|
17
|
+
import { readViewerSession } from "@/lib/auth/route";
|
|
18
|
+
import { selectTenant } from "@/lib/dashboard/tenantSelection";
|
|
19
|
+
import { isStaffSession } from "@/lib/dashboard/staffAdmission";
|
|
20
|
+
|
|
21
|
+
const cspNonce = Astro.locals.cspNonce;
|
|
22
|
+
const session = await readViewerSession(Astro);
|
|
23
|
+
if (!session) {
|
|
24
|
+
return Astro.redirect("/auth/login?next=%2Fdashboard", 302);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ToT-STAFF viewers reach ANY vendor by explicit SEARCH-and-enter (a distinct
|
|
28
|
+
// path from tenants[] membership). When staff, we always render the hub (their
|
|
29
|
+
// memberships, if any, PLUS a vendor search) and skip the single-membership
|
|
30
|
+
// auto-redirect so they can still search for a vendor they don't belong to.
|
|
31
|
+
const isStaff = isStaffSession(session);
|
|
32
|
+
const selection = selectTenant(session.tenants);
|
|
33
|
+
if (!isStaff && selection.mode === "single") {
|
|
34
|
+
return Astro.redirect(`/dashboard/${encodeURIComponent(selection.tenant.resource)}`, 302);
|
|
35
|
+
}
|
|
36
|
+
const vendors =
|
|
37
|
+
selection.mode === "picker"
|
|
38
|
+
? selection.tenants
|
|
39
|
+
: selection.mode === "single"
|
|
40
|
+
? [selection.tenant]
|
|
41
|
+
: [];
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
<html lang="en">
|
|
45
|
+
<head>
|
|
46
|
+
<meta charset="utf-8" />
|
|
47
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
48
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
49
|
+
<title>Your stores — Token of Trust</title>
|
|
50
|
+
<style nonce={cspNonce}>
|
|
51
|
+
:root { color-scheme: light dark; }
|
|
52
|
+
* { box-sizing: border-box; }
|
|
53
|
+
body { font: 15px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
54
|
+
margin: 0; min-height: 100vh; padding: 2.5rem 1.25rem; background: #f5f4f0; color: #1c1c1c; }
|
|
55
|
+
@media (prefers-color-scheme: dark) { body { background: #14141a; color: #ececf1; } }
|
|
56
|
+
main { max-width: 40rem; margin-inline: auto; }
|
|
57
|
+
.eyebrow { font: 600 .68rem/1 ui-monospace, monospace; letter-spacing: .12em; text-transform: uppercase; color: #8a7f66; margin: 0 0 .5rem; }
|
|
58
|
+
h1 { font-size: 1.5rem; letter-spacing: -.01em; margin: 0 0 .3rem; }
|
|
59
|
+
.sub { color: #6a6a6a; margin: 0 0 1.75rem; }
|
|
60
|
+
@media (prefers-color-scheme: dark) { .sub { color: #a2a2ad; } }
|
|
61
|
+
ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .75rem; }
|
|
62
|
+
a.vendor { display: flex; align-items: center; justify-content: space-between; text-decoration: none;
|
|
63
|
+
background: #fff; border: 1px solid #e6e4de; border-radius: 12px; padding: 1rem 1.25rem; color: inherit; }
|
|
64
|
+
@media (prefers-color-scheme: dark) { a.vendor { background: #1e1e28; border-color: #33333f; } }
|
|
65
|
+
a.vendor:hover { border-color: #b7d0ff; }
|
|
66
|
+
.name { font-weight: 600; }
|
|
67
|
+
.role { font: 600 .68rem/1 ui-monospace, monospace; text-transform: uppercase; letter-spacing: .06em;
|
|
68
|
+
color: #4a6; border: 1px solid currentColor; border-radius: 999px; padding: .2rem .55rem; }
|
|
69
|
+
.who { margin-top: 2rem; font-size: .82rem; color: #8a8a8a; }
|
|
70
|
+
.who a { color: inherit; }
|
|
71
|
+
.linkbtn { background: none; border: 0; color: inherit; font: inherit; text-decoration: underline; cursor: pointer; padding: 0; }
|
|
72
|
+
.logout { display: inline; }
|
|
73
|
+
.empty { background: #fff; border: 1px solid #e6e4de; border-radius: 12px; padding: 1.5rem; }
|
|
74
|
+
@media (prefers-color-scheme: dark) { .empty { background: #1e1e28; border-color: #33333f; } }
|
|
75
|
+
/* Staff vendor search */
|
|
76
|
+
.staff { margin-top: 2.25rem; }
|
|
77
|
+
h2 { font-size: .95rem; text-transform: uppercase; letter-spacing: .05em; color: #777; margin: 0 0 .3rem; }
|
|
78
|
+
.staff .hint { color: #8a8a8a; font-size: .85rem; margin: 0 0 .7rem; }
|
|
79
|
+
.staff .hint code { font: .8em ui-monospace, monospace; background: #ece9e1; color: #4a4a4a;
|
|
80
|
+
padding: .1em .35em; border-radius: 5px; }
|
|
81
|
+
@media (prefers-color-scheme: dark) { .staff .hint code { background: #2a2a33; color: #cfcfda; } }
|
|
82
|
+
.search { position: relative; }
|
|
83
|
+
#vendorSearch { width: 100%; padding: .75rem .85rem; font-size: 1rem; border-radius: 10px;
|
|
84
|
+
border: 1px solid #cfccc4; background: #fff; color: inherit; }
|
|
85
|
+
@media (prefers-color-scheme: dark) { #vendorSearch { background: #14141a; border-color: #40404d; } }
|
|
86
|
+
#vendorResults { list-style: none; margin: .6rem 0 0; padding: 0; display: grid; gap: .5rem; }
|
|
87
|
+
#vendorResults button { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 1rem;
|
|
88
|
+
text-align: left; background: #fff; border: 1px solid #e6e4de; border-radius: 10px; padding: .8rem 1rem;
|
|
89
|
+
color: inherit; font: inherit; cursor: pointer; }
|
|
90
|
+
@media (prefers-color-scheme: dark) { #vendorResults button { background: #1e1e28; border-color: #33333f; } }
|
|
91
|
+
#vendorResults button:hover, #vendorResults button:focus-visible { border-color: #b7d0ff; }
|
|
92
|
+
#vendorResults button:disabled { opacity: .5; cursor: default; }
|
|
93
|
+
#vendorResults .dn { font-weight: 600; }
|
|
94
|
+
#vendorResults .ad { font: 600 .68rem/1 ui-monospace, monospace; color: #8a8a8a; }
|
|
95
|
+
#vendorSearchMsg { margin-top: .6rem; font-size: .88rem; color: #8a8a8a; min-height: 1.1em; }
|
|
96
|
+
#vendorSearchMsg.err { color: #9a1c1c; }
|
|
97
|
+
@media (prefers-color-scheme: dark) { #vendorSearchMsg.err { color: #f6b9b9; } }
|
|
98
|
+
.badge { display: inline-block; font: 600 .6rem/1 ui-monospace, monospace; letter-spacing: .08em;
|
|
99
|
+
text-transform: uppercase; color: #8a7f66; border: 1px solid currentColor; border-radius: 999px;
|
|
100
|
+
padding: .18rem .5rem; margin-left: .5rem; vertical-align: middle; }
|
|
101
|
+
</style>
|
|
102
|
+
</head>
|
|
103
|
+
<body>
|
|
104
|
+
<main>
|
|
105
|
+
<p class="eyebrow">Token of Trust</p>
|
|
106
|
+
<h1>Your stores{isStaff && <span class="badge">ToT staff</span>}</h1>
|
|
107
|
+
{vendors.length ? (
|
|
108
|
+
<>
|
|
109
|
+
<p class="sub">Choose a store to manage its team and preview.</p>
|
|
110
|
+
<ul>
|
|
111
|
+
{vendors.map((v) => (
|
|
112
|
+
<li>
|
|
113
|
+
<a class="vendor" href={`/dashboard/${encodeURIComponent(v.resource)}`}>
|
|
114
|
+
<span class="name">{v.resource}</span>
|
|
115
|
+
<span class="role">{v.role}</span>
|
|
116
|
+
</a>
|
|
117
|
+
</li>
|
|
118
|
+
))}
|
|
119
|
+
</ul>
|
|
120
|
+
</>
|
|
121
|
+
) : (
|
|
122
|
+
!isStaff && (
|
|
123
|
+
<div class="empty">
|
|
124
|
+
<p style="margin:0">You're signed in as <strong>{session.email}</strong>, but
|
|
125
|
+
you don't have access to any store yet. Ask a store's merchant to invite you.</p>
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
{isStaff && (
|
|
131
|
+
<section class="staff">
|
|
132
|
+
<h2>Find a store</h2>
|
|
133
|
+
<p class="hint">As Token of Trust staff you can enter any store's console. Search by
|
|
134
|
+
domain, then select one to enter — every admission is logged.</p>
|
|
135
|
+
<p class="hint">Tip: <code>^</code> matches the start, <code>$</code> the end — e.g.
|
|
136
|
+
<code>^acme.com$</code> for an exact match, <code>acme$</code> to end with “acme”.</p>
|
|
137
|
+
<div class="search">
|
|
138
|
+
<input id="vendorSearch" type="text" placeholder="acme.com"
|
|
139
|
+
autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false"
|
|
140
|
+
aria-label="Search stores by domain" />
|
|
141
|
+
<ul id="vendorResults"></ul>
|
|
142
|
+
<p id="vendorSearchMsg" aria-live="polite"></p>
|
|
143
|
+
</div>
|
|
144
|
+
</section>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
<p class="who">Signed in as {session.email} ·
|
|
148
|
+
<form class="logout" method="post" action="/api/auth/logout"><button type="submit" class="linkbtn">Sign out</button></form></p>
|
|
149
|
+
</main>
|
|
150
|
+
|
|
151
|
+
<!--
|
|
152
|
+
Staff vendor-search behavior is a STATIC same-origin file
|
|
153
|
+
(public/js/dashboard-vendor-search.js), referenced via src so the strict CSP
|
|
154
|
+
allows it through `script-src 'self'` — no nonce, no per-build content-hash
|
|
155
|
+
(see public/js/dashboard-team.js for the proven pattern). The file no-ops when
|
|
156
|
+
#vendorSearch is absent (non-staff), so it's safe to always include.
|
|
157
|
+
-->
|
|
158
|
+
<script src="/js/dashboard-vendor-search.js" is:inline defer></script>
|
|
159
|
+
</body>
|
|
160
|
+
</html>
|