@thorprovider/storefront 0.3.1 → 0.4.0-mvp.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/README.md +321 -152
- package/VENDOR.md +69 -0
- package/assets/openspec/config.yaml +38 -0
- package/assets/templates/AGENTS.md +76 -0
- package/assets/templates/app/app/[[...slug]]/page.tsx +54 -2
- package/assets/templates/app/app/[[...slug]]/renderer.tsx +13 -2
- package/assets/templates/app/app/globals.css +5 -8
- package/assets/templates/app/app/layout.tsx +17 -7
- package/assets/templates/app/app/robots.ts +19 -0
- package/assets/templates/app/app/sitemap.ts +71 -0
- package/assets/templates/app/lib/__STOREFRONT__/actions.ts +612 -0
- package/assets/templates/app/lib/__STOREFRONT__/cart-state.ts +66 -0
- package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +50 -57
- package/assets/templates/app/lib/__STOREFRONT__/commerce.ts +679 -0
- package/assets/templates/app/lib/__STOREFRONT__/composition.ts +26 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.core.ts +197 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.ts +5 -36
- package/assets/templates/app/lib/__STOREFRONT__/icon-map.ts +27 -0
- package/assets/templates/app/lib/__STOREFRONT__/mappers.ts +13 -11
- package/assets/templates/app/lib/__STOREFRONT__/registry.tsx +264 -51
- package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +6 -3
- package/assets/templates/app/lib/__STOREFRONT__/schema.ts +136 -0
- package/assets/templates/app/lib/__STOREFRONT__/section-observation.ts +182 -0
- package/assets/templates/app/lib/__STOREFRONT__/section-sources.ts +130 -0
- package/assets/templates/app/lib/__STOREFRONT__/session.core.ts +470 -0
- package/assets/templates/app/lib/__STOREFRONT__/session.ts +169 -0
- package/assets/templates/app/lib/__STOREFRONT__/shell/content.ts +120 -0
- package/assets/templates/app/lib/__STOREFRONT__/shell/props.ts +38 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +129 -10
- package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +5 -1
- package/assets/templates/app/lib/__STOREFRONT__/spec/seo.ts +191 -0
- package/assets/templates/app/lib/__STOREFRONT__/state.core.ts +257 -0
- package/assets/templates/app/lib/__STOREFRONT__/state.ts +7 -31
- package/assets/templates/app/lib/__STOREFRONT__/store-context.ts +161 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.client.tsx +25 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.tsx +16 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.client.tsx +24 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.tsx +5 -18
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/index.ts +18 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/logoCloud.tsx +26 -14
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/pricing.tsx +36 -18
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/products.client.tsx +100 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/products.tsx +19 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.cta.ts +19 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.faq.ts +18 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.products.ts +39 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/statistics.tsx +35 -14
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/testimonials.tsx +31 -13
- package/assets/templates/app/lib/__STOREFRONT__/views/types.ts +5 -3
- package/assets/templates/features/checkout/handlers.ts +159 -0
- package/assets/templates/features/checkout/state.ts +61 -0
- package/assets/templates/features/customer-auth/handlers.ts +59 -0
- package/assets/templates/features/customer-auth/state.ts +45 -0
- package/assets/templates/features/recommendations/state.ts +11 -0
- package/assets/templates/views/account/content.ts +35 -0
- package/assets/templates/views/account/index.tsx +114 -0
- package/assets/templates/views/account/registry.tsx +439 -0
- package/assets/templates/views/account/schema.ts +53 -0
- package/assets/templates/views/cart/content.ts +11 -0
- package/assets/templates/views/cart/data.ts +23 -0
- package/assets/templates/views/cart/index.tsx +53 -0
- package/assets/templates/views/cart/props.ts +12 -0
- package/assets/templates/views/cart/registry.tsx +81 -0
- package/assets/templates/views/cart/schema.ts +52 -0
- package/assets/templates/views/category/assembly/assembly.json +29 -0
- package/assets/templates/views/category/assembly/index.skeleton.tsx +251 -0
- package/assets/templates/views/category/assembly/registry.skeleton.tsx +34 -0
- package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/binding.json +5 -0
- package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/catalog.part +4 -0
- package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/element.part +7 -0
- package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/registry.part +18 -0
- package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/catalog.part +5 -0
- package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/element.part +34 -0
- package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/registry.part +71 -0
- package/assets/templates/views/category/content.ts +30 -0
- package/assets/templates/views/category/data.ts +12 -0
- package/assets/templates/views/category/index.tsx +299 -0
- package/assets/templates/views/category/props.ts +23 -0
- package/assets/templates/views/category/registry.tsx +122 -0
- package/assets/templates/views/category/schema.ts +91 -0
- package/assets/templates/views/checkout/content.ts +105 -0
- package/assets/templates/views/checkout/data.ts +23 -0
- package/assets/templates/views/checkout/index.tsx +111 -0
- package/assets/templates/views/checkout/props.ts +49 -0
- package/assets/templates/views/checkout/registry.tsx +217 -0
- package/assets/templates/views/checkout/schema.ts +229 -0
- package/assets/templates/views/home/assembly/assembly.json +33 -0
- package/assets/templates/views/home/assembly/index.skeleton.tsx +90 -0
- package/assets/templates/views/home/assembly/registry.skeleton.tsx +25 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/element.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/registry.part +11 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/element.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/registry.part +10 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/element.part +5 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/registry.part +16 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/element.part +5 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/registry.part +26 -0
- package/assets/templates/views/home/content.ts +70 -0
- package/assets/templates/views/home/data.ts +24 -0
- package/assets/templates/views/home/index.tsx +57 -85
- package/assets/templates/views/home/props.ts +22 -0
- package/assets/templates/views/home/registry.tsx +38 -22
- package/assets/templates/views/home/schema.ts +93 -0
- package/assets/templates/views/product/content.ts +25 -0
- package/assets/templates/views/product/data.ts +50 -0
- package/assets/templates/views/product/index.tsx +148 -0
- package/assets/templates/views/product/props.ts +27 -0
- package/assets/templates/views/product/registry.tsx +103 -0
- package/assets/templates/views/product/schema.ts +121 -0
- package/assets/templates/views/static/content.ts +99 -0
- package/assets/templates/views/static/index.tsx +60 -0
- package/assets/templates/views/static/registry.tsx +66 -0
- package/assets/templates/views/static/schema.ts +45 -0
- package/bin/storefront.js +2424 -148
- package/examples/merchandising/category-led.json +37 -0
- package/examples/merchandising/collection-led.json +37 -0
- package/package.json +15 -9
- package/recipes/archetypes.json +28 -14
- package/recipes/catalog.json +400 -0
- package/recipes/catalog.mjs +128 -0
- package/recipes/catalog.schema.json +64 -0
- package/recipes/feature.schema.json +89 -0
- package/recipes/features.json +162 -0
- package/recipes/manifest.schema.json +69 -0
- package/recipes/operations.schema.json +43 -0
- package/recipes/recipe.schema.json +11 -2
- package/recipes/recipes.json +76 -19
- package/recipes/sections.json +34 -5
- package/recipes/validate.mjs +838 -32
- package/scripts/candidate.mjs +236 -0
- package/scripts/composers.mjs +160 -0
- package/scripts/env-file.mjs +86 -0
- package/scripts/file-classes.mjs +121 -0
- package/scripts/logical-controls.mjs +61 -0
- package/scripts/manifest.mjs +298 -0
- package/scripts/migrate-english-routes.mjs +276 -0
- package/scripts/operation-registry.mjs +256 -0
- package/scripts/operations.mjs +720 -0
- package/scripts/ownership-conflict.mjs +356 -0
- package/scripts/pinning.mjs +573 -0
- package/scripts/plan-apply.mjs +987 -0
- package/scripts/preflight.mjs +51 -0
- package/scripts/reconcile.mjs +549 -0
- package/scripts/render-templates.mjs +338 -68
- package/scripts/schema-validate.mjs +132 -0
- package/scripts/section-compat.mjs +300 -0
- package/scripts/section-config.mjs +253 -0
- package/scripts/section-operations.mjs +508 -0
- package/scripts/starter.mjs +17 -0
- package/scripts/sync-assets.mjs +22 -15
- package/scripts/theme/color.mjs +185 -0
- package/scripts/theme/css-theme.mjs +233 -0
- package/scripts/theme/fonts.mjs +123 -0
- package/scripts/theme/import-export.mjs +125 -0
- package/scripts/theme/preset.mjs +93 -0
- package/scripts/theme/theme-plan.mjs +292 -0
- package/scripts/theme/tokens.mjs +137 -0
- package/scripts/view-assembly.mjs +337 -0
- package/scripts/view-selection.mjs +211 -0
- package/skills/sf-feature/SKILL.md +80 -0
- package/skills/sf-setup/SKILL.md +79 -0
- package/skills/sf-theme-gen/SKILL.md +100 -30
- package/skills/sf-theme-gen/references/shadcn-theming.md +87 -0
- package/skills/{shadcn-theming/references/templates → sf-theme-gen/references}/theme-setup.md +15 -19
- package/skills/{shadcn-theming → sf-theme-gen}/references/theming-guide.md +6 -11
- package/skills/sf-view-gen/SKILL.md +146 -93
- package/skills/sf-view-gen/assets/recipes/archetypes.json +28 -14
- package/skills/sf-view-gen/assets/recipes/catalog.json +400 -0
- package/skills/sf-view-gen/assets/recipes/catalog.schema.json +64 -0
- package/skills/sf-view-gen/assets/recipes/features.json +162 -0
- package/skills/sf-view-gen/assets/recipes/recipes.json +76 -19
- package/skills/sf-view-gen/assets/recipes/sections.json +34 -5
- package/skills/sf-view-gen/assets/templates/views/sections/cta.client.tsx +25 -0
- package/skills/sf-view-gen/assets/templates/views/sections/cta.tsx +16 -0
- package/skills/sf-view-gen/assets/templates/views/sections/faq.client.tsx +13 -9
- package/skills/sf-view-gen/assets/templates/views/sections/faq.tsx +2 -7
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.client.tsx +12 -8
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.tsx +2 -7
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.client.tsx +14 -10
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.tsx +2 -9
- package/skills/sf-view-gen/assets/templates/views/sections/products.client.tsx +100 -0
- package/skills/sf-view-gen/assets/templates/views/sections/products.tsx +19 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.cta.ts +19 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.faq.ts +18 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.logoCloud.ts +19 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.pricing.ts +26 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.products.ts +39 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.statistics.ts +25 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.testimonials.ts +23 -0
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.client.tsx +16 -8
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.tsx +2 -7
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.client.tsx +13 -9
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.tsx +2 -5
- package/skills/{json-render-core/SKILL.md → sf-view-gen/references/json-render-core.md} +4 -27
- package/skills/{json-render-next/SKILL.md → sf-view-gen/references/json-render-next.md} +5 -8
- package/skills/{json-render-react/SKILL.md → sf-view-gen/references/json-render-react.md} +3 -5
- package/skills/{json-render-shadcn/SKILL.md → sf-view-gen/references/json-render-shadcn.md} +3 -5
- package/skills/sf-view-gen/references/views.md +58 -33
- package/skills/sf-view-gen/scripts/catalog.mjs +128 -0
- package/skills/sf-view-gen/scripts/schema-validate.mjs +132 -0
- package/skills/sf-view-gen/scripts/validate.mjs +838 -32
- package/skills/storefront-workflow/SKILL.md +179 -0
- package/skills/{storefront-best-practices/reference/design.md → storefront-workflow/references/design-consistency.md} +13 -29
- package/CHANGELOG.md +0 -61
- package/commands/sf-add-view.md +0 -26
- package/commands/sf-theme.md +0 -20
- package/commands/sf-view.md +0 -25
- package/skills/building-storefronts/SKILL.md +0 -178
- package/skills/building-storefronts/references/frontend-integration.md +0 -229
- package/skills/json-render-remotion/SKILL.md +0 -111
- package/skills/json-render-solid/SKILL.md +0 -204
- package/skills/nextjs-shadcn/SKILL.md +0 -367
- package/skills/nextjs-shadcn/references/architecture.md +0 -499
- package/skills/nextjs-shadcn/references/project-setup.md +0 -162
- package/skills/nextjs-shadcn/references/shadcn-platform.md +0 -261
- package/skills/nextjs-shadcn/references/sidebar.md +0 -274
- package/skills/nextjs-shadcn/references/styling.md +0 -555
- package/skills/sf-view-gen/assets/templates/views/category/index.tsx +0 -86
- package/skills/sf-view-gen/assets/templates/views/category/registry.tsx +0 -48
- package/skills/sf-view-gen/assets/templates/views/checkout/index.tsx +0 -192
- package/skills/sf-view-gen/assets/templates/views/checkout/registry.tsx +0 -45
- package/skills/sf-view-gen/assets/templates/views/product/index.tsx +0 -122
- package/skills/sf-view-gen/assets/templates/views/product/registry.tsx +0 -49
- package/skills/sf-view-gen/assets/templates/views/user-panel/index.tsx +0 -168
- package/skills/sf-view-gen/assets/templates/views/user-panel/registry.tsx +0 -49
- package/skills/shadcn-component-discovery/SKILL.md +0 -273
- package/skills/shadcn-component-discovery/references/registries.md +0 -226
- package/skills/shadcn-theming/SKILL.md +0 -104
- package/skills/storefront-best-practices/SKILL.md +0 -421
- package/skills/storefront-best-practices/reference/components/breadcrumbs.md +0 -123
- package/skills/storefront-best-practices/reference/components/cart-popup.md +0 -189
- package/skills/storefront-best-practices/reference/components/country-selector.md +0 -298
- package/skills/storefront-best-practices/reference/components/footer.md +0 -112
- package/skills/storefront-best-practices/reference/components/hero.md +0 -241
- package/skills/storefront-best-practices/reference/components/megamenu.md +0 -239
- package/skills/storefront-best-practices/reference/components/navbar.md +0 -397
- package/skills/storefront-best-practices/reference/components/popups.md +0 -221
- package/skills/storefront-best-practices/reference/components/product-card.md +0 -125
- package/skills/storefront-best-practices/reference/components/product-reviews.md +0 -217
- package/skills/storefront-best-practices/reference/components/product-slider.md +0 -174
- package/skills/storefront-best-practices/reference/components/search.md +0 -101
- package/skills/storefront-best-practices/reference/connecting-to-backend.md +0 -391
- package/skills/storefront-best-practices/reference/features/promotions.md +0 -307
- package/skills/storefront-best-practices/reference/features/wishlist.md +0 -230
- package/skills/storefront-best-practices/reference/layouts/account.md +0 -380
- package/skills/storefront-best-practices/reference/layouts/cart.md +0 -316
- package/skills/storefront-best-practices/reference/layouts/checkout.md +0 -486
- package/skills/storefront-best-practices/reference/layouts/home-page.md +0 -264
- package/skills/storefront-best-practices/reference/layouts/order-confirmation.md +0 -231
- package/skills/storefront-best-practices/reference/layouts/product-details.md +0 -527
- package/skills/storefront-best-practices/reference/layouts/product-listing.md +0 -520
- package/skills/storefront-best-practices/reference/layouts/static-pages.md +0 -356
- package/skills/storefront-best-practices/reference/medusa.md +0 -307
- /package/skills/{storefront-best-practices/reference → storefront-workflow/references}/mobile-responsiveness.md +0 -0
- /package/skills/{storefront-best-practices/reference → storefront-workflow/references}/seo.md +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Thor Commerce view module: home
|
|
2
|
+
// Server Component module: elements + catalog entries + route + loader only.
|
|
3
|
+
// The client-side component mappings live in ./registry.tsx ('use client'); this
|
|
4
|
+
// module imports them as data so the RSC graph never evaluates @json-render/react
|
|
5
|
+
// on the server. Registered by importing it in ../index.ts. Recipe: recipes.json -> "home".
|
|
6
|
+
|
|
7
|
+
import { getCatalog } from '@thorprovider/runtime/commerce'
|
|
8
|
+
import { getDataSource, getStoreContext } from '../../store-context'
|
|
9
|
+
import { pageRoot } from '../../spec/page-root'
|
|
10
|
+
import type { StorefrontElement } from '../../spec/types'
|
|
11
|
+
import type { ViewModule } from '../types'
|
|
12
|
+
import { registry } from './registry'
|
|
13
|
+
import { benefitsProps, categoriesProps, heroProps } from './props'
|
|
14
|
+
import * as homeData from './data'
|
|
15
|
+
import { toCategoryCard, toFeaturedProduct } from '../../mappers'
|
|
16
|
+
import { resolveSectionSources, resolveSectionViewStates } from '../../section-sources'
|
|
17
|
+
import {
|
|
18
|
+
categoryCardsPropsSchema,
|
|
19
|
+
featurePropsSchema,
|
|
20
|
+
storefrontHeroPropsSchema,
|
|
21
|
+
} from './schema'@@SECTION_IMPORTS@@
|
|
22
|
+
|
|
23
|
+
// The element tree is derived wiring; the rendered props come from the
|
|
24
|
+
// user-owned ./props.ts module so they survive sync and unit removal.
|
|
25
|
+
const elements: Record<string, StorefrontElement> = {
|
|
26
|
+
@@ELEMENTS@@
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const catalog = {
|
|
30
|
+
@@CATALOG@@
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const home: ViewModule = {
|
|
34
|
+
name: 'home',
|
|
35
|
+
elements,
|
|
36
|
+
catalog,
|
|
37
|
+
registry,
|
|
38
|
+
route: {
|
|
39
|
+
path: '/',
|
|
40
|
+
loaderName: 'home',
|
|
41
|
+
layout: 'default',
|
|
42
|
+
metadata: {
|
|
43
|
+
title: 'Inicio',
|
|
44
|
+
description: 'Descubre los productos de __SITE_NAME__.',
|
|
45
|
+
},
|
|
46
|
+
entry: pageRoot('home', [@@SLOT_IDS@@], elements),
|
|
47
|
+
},
|
|
48
|
+
loader: {
|
|
49
|
+
home: async () => {
|
|
50
|
+
// Data-bound sections resolve independently through the canonical catalog
|
|
51
|
+
// bridge; equal queries share request-local work and differing contexts
|
|
52
|
+
// stay isolated. A section failure never erases its siblings, and it is
|
|
53
|
+
// resolved separately so a catalog failure still yields truthful
|
|
54
|
+
// per-section state instead of a missing one.
|
|
55
|
+
let sections: Record<string, unknown> = {}
|
|
56
|
+
try {
|
|
57
|
+
sections = await resolveSectionViewStates(
|
|
58
|
+
@@SECTION_LOADERS@@,
|
|
59
|
+
resolveSectionSources(homeData),
|
|
60
|
+
getStoreContext(),
|
|
61
|
+
)
|
|
62
|
+
} catch {
|
|
63
|
+
sections = {}
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const { products, categories, source } = await getCatalog()
|
|
67
|
+
return {
|
|
68
|
+
heroProducts: homeData.selectHeroProducts(products).map(toFeaturedProduct),
|
|
69
|
+
categories: categories.map(toCategoryCard),
|
|
70
|
+
navCategories: homeData.toNavCategories(categories),
|
|
71
|
+
dataSource: source,
|
|
72
|
+
dataError: null,
|
|
73
|
+
sections,
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
// A real-mode failure is surfaced as a visible error, never as fictional
|
|
77
|
+
// data. Mock mode is labelled by `dataSource`.
|
|
78
|
+
return {
|
|
79
|
+
heroProducts: [],
|
|
80
|
+
categories: [],
|
|
81
|
+
navCategories: [],
|
|
82
|
+
dataSource: getDataSource(),
|
|
83
|
+
dataError:
|
|
84
|
+
error instanceof Error ? error.message : 'No se pudo cargar el catálogo.',
|
|
85
|
+
sections,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
// Thor Commerce view registry fragment: home
|
|
4
|
+
// Client boundary for the home view. Imports value bindings from
|
|
5
|
+
// @json-render/react and components from @thorprovider/blocks — both must stay
|
|
6
|
+
// on the client side of the RSC graph. Kept separate from index.tsx so the view
|
|
7
|
+
// spec (elements/catalog/route/loader) stays a pure Server Component module.
|
|
8
|
+
//
|
|
9
|
+
// Props arrive as untyped spec data: every entry parses them through the view's
|
|
10
|
+
// schema (./schema.ts) before binding, so a contract mismatch fails loudly at
|
|
11
|
+
// the boundary instead of rendering undefined fields or literal icon tags.
|
|
12
|
+
|
|
13
|
+
@@BLOCK_IMPORTS@@@@SECTION_REGISTRY_IMPORTS@@
|
|
14
|
+
import { useStateStore } from '@json-render/react'
|
|
15
|
+
import { resolveIcon } from '../../icon-map'
|
|
16
|
+
import type { ViewRegistry } from '../types'
|
|
17
|
+
import {
|
|
18
|
+
categoryCardsPropsSchema,
|
|
19
|
+
featurePropsSchema,
|
|
20
|
+
storefrontHeroPropsSchema,
|
|
21
|
+
} from './schema'
|
|
22
|
+
|
|
23
|
+
export const registry: ViewRegistry = {
|
|
24
|
+
@@REGISTRY@@
|
|
25
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Feature: ({ props }) => {
|
|
2
|
+
const feature = featurePropsSchema.parse(props)
|
|
3
|
+
return (
|
|
4
|
+
<FeatureBlock
|
|
5
|
+
items={feature.items.map((item) => ({ ...item, icon: resolveIcon(item.icon) }))}
|
|
6
|
+
badge={feature.badge}
|
|
7
|
+
title={feature.title}
|
|
8
|
+
description={feature.description}
|
|
9
|
+
/>
|
|
10
|
+
)
|
|
11
|
+
},
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
CategoryCards: ({ props, emit }) => {
|
|
2
|
+
const { set } = useStateStore()
|
|
3
|
+
const cards = categoryCardsPropsSchema.parse(props)
|
|
4
|
+
return (
|
|
5
|
+
<CategoryCardsBlock
|
|
6
|
+
title={cards.title}
|
|
7
|
+
description={cards.description}
|
|
8
|
+
categories={cards.categories}
|
|
9
|
+
cta={cards.cta}
|
|
10
|
+
onBrowse={(category) => {
|
|
11
|
+
set('/lastCategory', category.handle ?? String(category.id))
|
|
12
|
+
emit('openCategory')
|
|
13
|
+
}}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
},
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
StorefrontHero: ({ props, emit }) => {
|
|
2
|
+
const { set } = useStateStore()
|
|
3
|
+
const hero = storefrontHeroPropsSchema.parse(props)
|
|
4
|
+
return (
|
|
5
|
+
<StorefrontHeroBlock
|
|
6
|
+
title={hero.title}
|
|
7
|
+
description={hero.description}
|
|
8
|
+
products={hero.products}
|
|
9
|
+
titleAccent={hero.titleAccent}
|
|
10
|
+
badge={hero.badge}
|
|
11
|
+
primaryCta={hero.primaryCta}
|
|
12
|
+
secondaryCta={hero.secondaryCta}
|
|
13
|
+
heroImage={hero.heroImage}
|
|
14
|
+
onAddToCart={(product) => {
|
|
15
|
+
// Product-card shortcut: add the first purchasable variant.
|
|
16
|
+
set('/lastClicked', product)
|
|
17
|
+
emit('addClickedToCart')
|
|
18
|
+
}}
|
|
19
|
+
onBuyNow={(product) => {
|
|
20
|
+
set('/lastClicked', product)
|
|
21
|
+
emit('openProduct')
|
|
22
|
+
}}
|
|
23
|
+
onSelectColor={() => {}}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
},
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Thor Commerce view content: home
|
|
2
|
+
// User-owned view content — the copy, CTA labels, and feature items rendered by
|
|
3
|
+
// this view. Reconcile seeds this file once and never rewrites it; the derived
|
|
4
|
+
// view module (./index.tsx) imports it and wires it into the element tree, so
|
|
5
|
+
// your edits here survive `storefront sync` and unit removal. Plain data only:
|
|
6
|
+
// no React and no server imports.
|
|
7
|
+
|
|
8
|
+
export interface HeroImageContent {
|
|
9
|
+
src: string
|
|
10
|
+
alt: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Optional editorial hero image. Leave `undefined` to render no image; never
|
|
15
|
+
* set a blank `src`. This is static editorial imagery only — never a product
|
|
16
|
+
* image or backend data.
|
|
17
|
+
*/
|
|
18
|
+
export const heroImage: HeroImageContent | undefined = undefined
|
|
19
|
+
|
|
20
|
+
export const heroContent = {
|
|
21
|
+
badge: 'Bienvenido',
|
|
22
|
+
title: '__SITE_NAME__',
|
|
23
|
+
titleAccent: 'para cada proyecto',
|
|
24
|
+
// Neutral: no delivery/shipping promise.
|
|
25
|
+
description: 'Productos seleccionados con precios claros y compra sencilla.',
|
|
26
|
+
primaryCta: { label: 'Comprar ahora' },
|
|
27
|
+
secondaryCta: { label: 'Ver categorías' },
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const benefitsContent = {
|
|
31
|
+
badge: '__SITE_NAME__',
|
|
32
|
+
title: 'Compra con confianza',
|
|
33
|
+
// Neutral: no delivery/shipping promise.
|
|
34
|
+
description: 'Ventajas pensadas para una compra clara y segura.',
|
|
35
|
+
items: [
|
|
36
|
+
{
|
|
37
|
+
id: 'pago',
|
|
38
|
+
title: 'Pago protegido',
|
|
39
|
+
description: 'Compra con confianza, múltiples opciones de pago.',
|
|
40
|
+
icon: 'shield-check',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'catalogo',
|
|
44
|
+
title: 'Catálogo variado',
|
|
45
|
+
description: 'Productos para tu hogar y tu negocio.',
|
|
46
|
+
icon: 'star',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'soporte',
|
|
50
|
+
title: 'Soporte local',
|
|
51
|
+
description: 'Atención personalizada en todo el país.',
|
|
52
|
+
icon: 'headphones',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const categoriesContent = {
|
|
58
|
+
title: 'Compra por categoría',
|
|
59
|
+
description: 'Explora nuestro catálogo completo.',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Editorial section instance content, keyed by the section instance id the
|
|
64
|
+
* manifest persists (`views[].sections[].id`, e.g. `home-faq-1`). The derived
|
|
65
|
+
* view module reads `sectionContent[id]`, so adding, reordering, or removing a
|
|
66
|
+
* section never rewrites this file and your copy survives `storefront sync`.
|
|
67
|
+
* Plain data only, matching the section fragment's props; an unset id renders
|
|
68
|
+
* the section's neutral default.
|
|
69
|
+
*/
|
|
70
|
+
export const sectionContent: Record<string, Record<string, unknown>> = {}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Thor Commerce view data: home
|
|
2
|
+
// User-owned loader shaping for the home view. Reconcile seeds this file once and
|
|
3
|
+
// never rewrites it; the derived view module (./index.tsx) imports these helpers
|
|
4
|
+
// and calls them from its loader, so edits here survive `storefront sync` and
|
|
5
|
+
// unit removal. Pure functions only: no React and no server imports.
|
|
6
|
+
|
|
7
|
+
export function selectHeroProducts<T>(products: readonly T[]): T[] {
|
|
8
|
+
return products.slice(0, 4)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function toNavCategories(
|
|
12
|
+
categories: readonly { label: string; href: string }[],
|
|
13
|
+
): { label: string; href: string }[] {
|
|
14
|
+
return categories.map((category) => ({ label: category.label, href: category.href }))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Per-instance selection for data-bound sections (for example `products`), keyed
|
|
18
|
+
// by the section's stable id from the composition manifest. Each value is a
|
|
19
|
+
// canonical catalog selection (category/collection/search/ordering) that the
|
|
20
|
+
// generated host loader resolves through the runtime catalog bridge. Left empty
|
|
21
|
+
// by default: a data-bound section with no entry renders an `unconfigured`
|
|
22
|
+
// state. Entries whose id is no longer referenced by the composition are kept
|
|
23
|
+
// but never queried, so re-adding the section with the same id restores them.
|
|
24
|
+
export const sectionSources: Record<string, unknown> = {}
|
|
@@ -5,115 +5,51 @@
|
|
|
5
5
|
// on the server. Registered by importing it in ../index.ts. Recipe: recipes.json -> "home".
|
|
6
6
|
|
|
7
7
|
import { getCatalog } from '@thorprovider/runtime/commerce'
|
|
8
|
-
import {
|
|
8
|
+
import { getDataSource, getStoreContext } from '../../store-context'
|
|
9
9
|
import { pageRoot } from '../../spec/page-root'
|
|
10
10
|
import type { StorefrontElement } from '../../spec/types'
|
|
11
11
|
import type { ViewModule } from '../types'
|
|
12
12
|
import { registry } from './registry'
|
|
13
|
-
import {
|
|
13
|
+
import { benefitsProps, categoriesProps, heroProps } from './props'
|
|
14
|
+
import * as homeData from './data'
|
|
15
|
+
import { toCategoryCard, toFeaturedProduct } from '../../mappers'
|
|
16
|
+
import { resolveSectionSources, resolveSectionViewStates } from '../../section-sources'
|
|
17
|
+
import {
|
|
18
|
+
categoryCardsPropsSchema,
|
|
19
|
+
featurePropsSchema,
|
|
20
|
+
storefrontHeroPropsSchema,
|
|
21
|
+
} from './schema'
|
|
14
22
|
|
|
23
|
+
// The element tree is derived wiring; the rendered props come from the
|
|
24
|
+
// user-owned ./props.ts module so they survive sync and unit removal.
|
|
15
25
|
const elements: Record<string, StorefrontElement> = {
|
|
16
26
|
'home.hero': {
|
|
17
27
|
type: 'StorefrontHero',
|
|
18
|
-
props:
|
|
19
|
-
|
|
20
|
-
title: '__SITE_NAME__',
|
|
21
|
-
titleAccent: 'para cada proyecto',
|
|
22
|
-
description: 'Productos seleccionados con precios justos y entrega a tu puerta.',
|
|
23
|
-
products: { $state: '/heroProducts' },
|
|
24
|
-
primaryCta: { label: 'Comprar ahora' },
|
|
25
|
-
secondaryCta: { label: 'Ver categorías' },
|
|
26
|
-
},
|
|
27
|
-
on: { addToCart: { action: 'addToCart' }, openProduct: { action: 'openProduct' } },
|
|
28
|
+
props: heroProps,
|
|
29
|
+
on: { addClickedToCart: { action: 'addClickedToCart' }, openProduct: { action: 'openProduct' } },
|
|
28
30
|
},
|
|
29
31
|
'home.benefits': {
|
|
30
32
|
type: 'Feature',
|
|
31
|
-
props:
|
|
32
|
-
badge: '__SITE_NAME__',
|
|
33
|
-
title: 'Compra con confianza',
|
|
34
|
-
description: 'Beneficios pensados para que tu compra llegue segura y a tiempo.',
|
|
35
|
-
items: [
|
|
36
|
-
{
|
|
37
|
-
id: 'entrega',
|
|
38
|
-
title: 'Entrega segura',
|
|
39
|
-
description: 'Seguimiento de tu pedido hasta la puerta de tu casa.',
|
|
40
|
-
icon: 'truck',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: 'pago',
|
|
44
|
-
title: 'Pago protegido',
|
|
45
|
-
description: 'Compra con confianza, múltiples opciones de pago.',
|
|
46
|
-
icon: 'shield-check',
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
id: 'soporte',
|
|
50
|
-
title: 'Soporte local',
|
|
51
|
-
description: 'Atención personalizada en todo el país.',
|
|
52
|
-
icon: 'headphones',
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
},
|
|
33
|
+
props: benefitsProps,
|
|
56
34
|
},
|
|
57
35
|
'home.categories': {
|
|
58
36
|
type: 'CategoryCards',
|
|
59
|
-
props:
|
|
60
|
-
title: 'Compra por categoría',
|
|
61
|
-
description: 'Explora nuestro catálogo completo.',
|
|
62
|
-
categories: { $state: '/categories' },
|
|
63
|
-
},
|
|
37
|
+
props: categoriesProps,
|
|
64
38
|
on: { openCategory: { action: 'openCategory' } },
|
|
65
39
|
},
|
|
66
40
|
}
|
|
67
41
|
|
|
68
|
-
const ctaSchema = z.object({ label: z.string(), href: z.string().optional() })
|
|
69
|
-
|
|
70
|
-
// Mirrors the StorefrontHero block contract (FeaturedProduct) so a product-shape
|
|
71
|
-
// mismatch is a type error at the catalog boundary, not a runtime crash. The
|
|
72
|
-
// block requires every field — including `colors` — to be defined at render.
|
|
73
|
-
const featuredProductSchema = z.object({
|
|
74
|
-
id: z.string(),
|
|
75
|
-
name: z.string(),
|
|
76
|
-
brand: z.string(),
|
|
77
|
-
price: z.number(),
|
|
78
|
-
originalPrice: z.number(),
|
|
79
|
-
rating: z.number(),
|
|
80
|
-
reviewCount: z.number(),
|
|
81
|
-
inStock: z.boolean(),
|
|
82
|
-
isNew: z.boolean(),
|
|
83
|
-
isOnSale: z.boolean(),
|
|
84
|
-
image: z.string(),
|
|
85
|
-
colors: z.array(z.string()),
|
|
86
|
-
})
|
|
87
|
-
|
|
88
42
|
const catalog = {
|
|
89
43
|
StorefrontHero: {
|
|
90
|
-
props:
|
|
91
|
-
title: z.string(),
|
|
92
|
-
description: z.string(),
|
|
93
|
-
titleAccent: z.string().optional(),
|
|
94
|
-
badge: z.string().optional(),
|
|
95
|
-
products: z.array(featuredProductSchema),
|
|
96
|
-
primaryCta: ctaSchema.optional(),
|
|
97
|
-
secondaryCta: ctaSchema.optional(),
|
|
98
|
-
}),
|
|
44
|
+
props: storefrontHeroPropsSchema,
|
|
99
45
|
description: 'Landing hero.',
|
|
100
46
|
},
|
|
101
47
|
Feature: {
|
|
102
|
-
props:
|
|
103
|
-
items: z.array(z.record(z.string(), z.unknown())),
|
|
104
|
-
badge: z.string().optional(),
|
|
105
|
-
title: z.string().optional(),
|
|
106
|
-
description: z.string().optional(),
|
|
107
|
-
}),
|
|
48
|
+
props: featurePropsSchema,
|
|
108
49
|
description: 'Benefit highlights.',
|
|
109
50
|
},
|
|
110
51
|
CategoryCards: {
|
|
111
|
-
props:
|
|
112
|
-
title: z.string().optional(),
|
|
113
|
-
description: z.string().optional(),
|
|
114
|
-
categories: z.array(z.record(z.string(), z.unknown())),
|
|
115
|
-
cta: ctaSchema.optional(),
|
|
116
|
-
}),
|
|
52
|
+
props: categoryCardsPropsSchema,
|
|
117
53
|
description: 'Category grid.',
|
|
118
54
|
},
|
|
119
55
|
}
|
|
@@ -135,8 +71,44 @@ export const home: ViewModule = {
|
|
|
135
71
|
},
|
|
136
72
|
loader: {
|
|
137
73
|
home: async () => {
|
|
138
|
-
|
|
139
|
-
|
|
74
|
+
// Data-bound sections resolve independently through the canonical catalog
|
|
75
|
+
// bridge; equal queries share request-local work and differing contexts
|
|
76
|
+
// stay isolated. A section failure never erases its siblings, and it is
|
|
77
|
+
// resolved separately so a catalog failure still yields truthful
|
|
78
|
+
// per-section state instead of a missing one.
|
|
79
|
+
let sections: Record<string, unknown> = {}
|
|
80
|
+
try {
|
|
81
|
+
sections = await resolveSectionViewStates(
|
|
82
|
+
[],
|
|
83
|
+
resolveSectionSources(homeData),
|
|
84
|
+
getStoreContext(),
|
|
85
|
+
)
|
|
86
|
+
} catch {
|
|
87
|
+
sections = {}
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const { products, categories, source } = await getCatalog()
|
|
91
|
+
return {
|
|
92
|
+
heroProducts: homeData.selectHeroProducts(products).map(toFeaturedProduct),
|
|
93
|
+
categories: categories.map(toCategoryCard),
|
|
94
|
+
navCategories: homeData.toNavCategories(categories),
|
|
95
|
+
dataSource: source,
|
|
96
|
+
dataError: null,
|
|
97
|
+
sections,
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// A real-mode failure is surfaced as a visible error, never as fictional
|
|
101
|
+
// data. Mock mode is labelled by `dataSource`.
|
|
102
|
+
return {
|
|
103
|
+
heroProducts: [],
|
|
104
|
+
categories: [],
|
|
105
|
+
navCategories: [],
|
|
106
|
+
dataSource: getDataSource(),
|
|
107
|
+
dataError:
|
|
108
|
+
error instanceof Error ? error.message : 'No se pudo cargar el catálogo.',
|
|
109
|
+
sections,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
140
112
|
},
|
|
141
113
|
},
|
|
142
114
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Thor Commerce view props: home
|
|
2
|
+
// User-owned element props for the home view. Reconcile seeds this file once and
|
|
3
|
+
// never rewrites it; the derived view module (./index.tsx) imports these objects
|
|
4
|
+
// and binds them into the element tree, so edits here survive `storefront sync`
|
|
5
|
+
// and unit removal. Plain data only: no React and no server imports.
|
|
6
|
+
|
|
7
|
+
import { benefitsContent, categoriesContent, heroContent, heroImage } from './content'
|
|
8
|
+
|
|
9
|
+
export const heroProps = {
|
|
10
|
+
...heroContent,
|
|
11
|
+
products: { $state: '/heroProducts' },
|
|
12
|
+
heroImage,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const benefitsProps = {
|
|
16
|
+
...benefitsContent,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const categoriesProps = {
|
|
20
|
+
...categoriesContent,
|
|
21
|
+
categories: { $state: '/categories' },
|
|
22
|
+
}
|
|
@@ -5,31 +5,43 @@
|
|
|
5
5
|
// @json-render/react and components from @thorprovider/blocks — both must stay
|
|
6
6
|
// on the client side of the RSC graph. Kept separate from index.tsx so the view
|
|
7
7
|
// spec (elements/catalog/route/loader) stays a pure Server Component module.
|
|
8
|
+
//
|
|
9
|
+
// Props arrive as untyped spec data: every entry parses them through the view's
|
|
10
|
+
// schema (./schema.ts) before binding, so a contract mismatch fails loudly at
|
|
11
|
+
// the boundary instead of rendering undefined fields or literal icon tags.
|
|
8
12
|
|
|
9
13
|
import {
|
|
10
14
|
Feature1 as FeatureBlock,
|
|
11
15
|
ProductCategory2 as CategoryCardsBlock,
|
|
12
16
|
StorefrontHero1 as StorefrontHeroBlock,
|
|
13
17
|
} from '@thorprovider/blocks'
|
|
14
|
-
import type { FeaturedProduct } from '@thorprovider/blocks'
|
|
15
18
|
import { useStateStore } from '@json-render/react'
|
|
19
|
+
import { resolveIcon } from '../../icon-map'
|
|
16
20
|
import type { ViewRegistry } from '../types'
|
|
21
|
+
import {
|
|
22
|
+
categoryCardsPropsSchema,
|
|
23
|
+
featurePropsSchema,
|
|
24
|
+
storefrontHeroPropsSchema,
|
|
25
|
+
} from './schema'
|
|
17
26
|
|
|
18
27
|
export const registry: ViewRegistry = {
|
|
19
28
|
StorefrontHero: ({ props, emit }) => {
|
|
20
29
|
const { set } = useStateStore()
|
|
30
|
+
const hero = storefrontHeroPropsSchema.parse(props)
|
|
21
31
|
return (
|
|
22
32
|
<StorefrontHeroBlock
|
|
23
|
-
title={
|
|
24
|
-
description={
|
|
25
|
-
products={
|
|
26
|
-
titleAccent={
|
|
27
|
-
badge={
|
|
28
|
-
primaryCta={
|
|
29
|
-
secondaryCta={
|
|
33
|
+
title={hero.title}
|
|
34
|
+
description={hero.description}
|
|
35
|
+
products={hero.products}
|
|
36
|
+
titleAccent={hero.titleAccent}
|
|
37
|
+
badge={hero.badge}
|
|
38
|
+
primaryCta={hero.primaryCta}
|
|
39
|
+
secondaryCta={hero.secondaryCta}
|
|
40
|
+
heroImage={hero.heroImage}
|
|
30
41
|
onAddToCart={(product) => {
|
|
42
|
+
// Product-card shortcut: add the first purchasable variant.
|
|
31
43
|
set('/lastClicked', product)
|
|
32
|
-
emit('
|
|
44
|
+
emit('addClickedToCart')
|
|
33
45
|
}}
|
|
34
46
|
onBuyNow={(product) => {
|
|
35
47
|
set('/lastClicked', product)
|
|
@@ -39,24 +51,28 @@ export const registry: ViewRegistry = {
|
|
|
39
51
|
/>
|
|
40
52
|
)
|
|
41
53
|
},
|
|
42
|
-
Feature: ({ props }) =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
Feature: ({ props }) => {
|
|
55
|
+
const feature = featurePropsSchema.parse(props)
|
|
56
|
+
return (
|
|
57
|
+
<FeatureBlock
|
|
58
|
+
items={feature.items.map((item) => ({ ...item, icon: resolveIcon(item.icon) }))}
|
|
59
|
+
badge={feature.badge}
|
|
60
|
+
title={feature.title}
|
|
61
|
+
description={feature.description}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
},
|
|
50
65
|
CategoryCards: ({ props, emit }) => {
|
|
51
66
|
const { set } = useStateStore()
|
|
67
|
+
const cards = categoryCardsPropsSchema.parse(props)
|
|
52
68
|
return (
|
|
53
69
|
<CategoryCardsBlock
|
|
54
|
-
title={
|
|
55
|
-
description={
|
|
56
|
-
categories={
|
|
57
|
-
cta={
|
|
70
|
+
title={cards.title}
|
|
71
|
+
description={cards.description}
|
|
72
|
+
categories={cards.categories}
|
|
73
|
+
cta={cards.cta}
|
|
58
74
|
onBrowse={(category) => {
|
|
59
|
-
set('/lastCategory',
|
|
75
|
+
set('/lastCategory', category.handle ?? String(category.id))
|
|
60
76
|
emit('openCategory')
|
|
61
77
|
}}
|
|
62
78
|
/>
|