@thorprovider/storefront 0.3.1 → 0.4.0-mvp.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +248 -99
  3. package/assets/templates/app/app/[[...slug]]/page.tsx +13 -2
  4. package/assets/templates/app/app/layout.tsx +18 -4
  5. package/assets/templates/app/app/robots.ts +13 -0
  6. package/assets/templates/app/app/sitemap.ts +12 -0
  7. package/assets/templates/app/lib/__STOREFRONT__/actions.ts +542 -0
  8. package/assets/templates/app/lib/__STOREFRONT__/cart-state.ts +66 -0
  9. package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +40 -57
  10. package/assets/templates/app/lib/__STOREFRONT__/commerce.ts +608 -0
  11. package/assets/templates/app/lib/__STOREFRONT__/handlers.core.ts +165 -0
  12. package/assets/templates/app/lib/__STOREFRONT__/handlers.ts +5 -36
  13. package/assets/templates/app/lib/__STOREFRONT__/icon-map.ts +27 -0
  14. package/assets/templates/app/lib/__STOREFRONT__/mappers.ts +13 -11
  15. package/assets/templates/app/lib/__STOREFRONT__/registry.tsx +232 -51
  16. package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +3 -3
  17. package/assets/templates/app/lib/__STOREFRONT__/schema.ts +121 -0
  18. package/assets/templates/app/lib/__STOREFRONT__/session.core.ts +470 -0
  19. package/assets/templates/app/lib/__STOREFRONT__/session.ts +169 -0
  20. package/assets/templates/app/lib/__STOREFRONT__/shell/content.ts +96 -0
  21. package/assets/templates/app/lib/__STOREFRONT__/shell/props.ts +34 -0
  22. package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +100 -11
  23. package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +5 -1
  24. package/assets/templates/app/lib/__STOREFRONT__/spec/seo.ts +26 -0
  25. package/assets/templates/app/lib/__STOREFRONT__/state.core.ts +257 -0
  26. package/assets/templates/app/lib/__STOREFRONT__/state.ts +7 -31
  27. package/assets/templates/app/lib/__STOREFRONT__/store-context.ts +142 -0
  28. package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.client.tsx +25 -0
  29. package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.tsx +16 -0
  30. package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.client.tsx +24 -0
  31. package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.tsx +5 -18
  32. package/assets/templates/app/lib/__STOREFRONT__/views/sections/index.ts +17 -0
  33. package/assets/templates/app/lib/__STOREFRONT__/views/sections/logoCloud.tsx +26 -14
  34. package/assets/templates/app/lib/__STOREFRONT__/views/sections/pricing.tsx +36 -18
  35. package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.cta.ts +19 -0
  36. package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.faq.ts +18 -0
  37. package/assets/templates/app/lib/__STOREFRONT__/views/sections/statistics.tsx +35 -14
  38. package/assets/templates/app/lib/__STOREFRONT__/views/sections/testimonials.tsx +31 -13
  39. package/assets/templates/app/lib/__STOREFRONT__/views/types.ts +5 -3
  40. package/assets/templates/features/checkout/handlers.ts +153 -0
  41. package/assets/templates/features/checkout/state.ts +61 -0
  42. package/assets/templates/features/customer-auth/handlers.ts +59 -0
  43. package/assets/templates/features/customer-auth/state.ts +36 -0
  44. package/assets/templates/features/recommendations/state.ts +11 -0
  45. package/assets/templates/views/account/content.ts +35 -0
  46. package/assets/templates/views/account/index.tsx +114 -0
  47. package/assets/templates/views/account/registry.tsx +378 -0
  48. package/assets/templates/views/account/schema.ts +52 -0
  49. package/assets/templates/views/category/assembly/assembly.json +28 -0
  50. package/assets/templates/views/category/assembly/index.skeleton.tsx +193 -0
  51. package/assets/templates/views/category/assembly/registry.skeleton.tsx +33 -0
  52. package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/catalog.part +5 -0
  53. package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/element.part +34 -0
  54. package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/registry.part +71 -0
  55. package/assets/templates/views/category/content.ts +30 -0
  56. package/assets/templates/views/category/data.ts +12 -0
  57. package/assets/templates/views/category/index.tsx +230 -0
  58. package/assets/templates/views/category/props.ts +16 -0
  59. package/assets/templates/views/category/registry.tsx +103 -0
  60. package/assets/templates/views/category/schema.ts +78 -0
  61. package/assets/templates/views/checkout/content.ts +105 -0
  62. package/assets/templates/views/checkout/data.ts +23 -0
  63. package/assets/templates/views/checkout/index.tsx +111 -0
  64. package/assets/templates/views/checkout/props.ts +49 -0
  65. package/assets/templates/views/checkout/registry.tsx +217 -0
  66. package/assets/templates/views/checkout/schema.ts +229 -0
  67. package/assets/templates/views/home/assembly/assembly.json +24 -0
  68. package/assets/templates/views/home/assembly/index.skeleton.tsx +72 -0
  69. package/assets/templates/views/home/assembly/registry.skeleton.tsx +25 -0
  70. package/assets/templates/views/home/assembly/slots/benefits/Feature1/binding.json +5 -0
  71. package/assets/templates/views/home/assembly/slots/benefits/Feature1/catalog.part +4 -0
  72. package/assets/templates/views/home/assembly/slots/benefits/Feature1/element.part +4 -0
  73. package/assets/templates/views/home/assembly/slots/benefits/Feature1/registry.part +11 -0
  74. package/assets/templates/views/home/assembly/slots/benefits/Feature2/binding.json +5 -0
  75. package/assets/templates/views/home/assembly/slots/benefits/Feature2/catalog.part +4 -0
  76. package/assets/templates/views/home/assembly/slots/benefits/Feature2/element.part +4 -0
  77. package/assets/templates/views/home/assembly/slots/benefits/Feature2/registry.part +10 -0
  78. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/binding.json +5 -0
  79. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/catalog.part +4 -0
  80. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/element.part +5 -0
  81. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/registry.part +16 -0
  82. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/binding.json +5 -0
  83. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/catalog.part +4 -0
  84. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/element.part +5 -0
  85. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/registry.part +26 -0
  86. package/assets/templates/views/home/content.ts +70 -0
  87. package/assets/templates/views/home/data.ts +15 -0
  88. package/assets/templates/views/home/index.tsx +38 -84
  89. package/assets/templates/views/home/props.ts +22 -0
  90. package/assets/templates/views/home/registry.tsx +38 -22
  91. package/assets/templates/views/home/schema.ts +93 -0
  92. package/assets/templates/views/product/content.ts +25 -0
  93. package/assets/templates/views/product/data.ts +48 -0
  94. package/assets/templates/views/product/index.tsx +104 -0
  95. package/assets/templates/views/product/props.ts +20 -0
  96. package/assets/templates/views/product/registry.tsx +78 -0
  97. package/assets/templates/views/product/schema.ts +108 -0
  98. package/assets/templates/views/static/content.ts +99 -0
  99. package/assets/templates/views/static/index.tsx +60 -0
  100. package/assets/templates/views/static/registry.tsx +66 -0
  101. package/assets/templates/views/static/schema.ts +45 -0
  102. package/{skills/sf-view-gen/assets → assets}/templates/views/user-panel/index.tsx +8 -70
  103. package/{skills/sf-view-gen/assets → assets}/templates/views/user-panel/registry.tsx +23 -18
  104. package/assets/templates/views/user-panel/schema.ts +79 -0
  105. package/bin/storefront.js +1278 -79
  106. package/commands/sf-add-view.md +44 -13
  107. package/commands/sf-feature.md +28 -0
  108. package/commands/sf-theme.md +14 -8
  109. package/commands/sf-view.md +54 -12
  110. package/package.json +6 -4
  111. package/recipes/archetypes.json +28 -1
  112. package/recipes/catalog.json +361 -0
  113. package/recipes/catalog.mjs +128 -0
  114. package/recipes/catalog.schema.json +64 -0
  115. package/recipes/feature.schema.json +89 -0
  116. package/recipes/features.json +139 -0
  117. package/recipes/manifest.schema.json +69 -0
  118. package/recipes/operations.schema.json +43 -0
  119. package/recipes/recipe.schema.json +11 -2
  120. package/recipes/recipes.json +60 -5
  121. package/recipes/sections.json +24 -5
  122. package/recipes/validate.mjs +784 -32
  123. package/scripts/candidate.mjs +236 -0
  124. package/scripts/composers.mjs +140 -0
  125. package/scripts/file-classes.mjs +116 -0
  126. package/scripts/logical-controls.mjs +61 -0
  127. package/scripts/manifest.mjs +298 -0
  128. package/scripts/operation-registry.mjs +256 -0
  129. package/scripts/operations.mjs +720 -0
  130. package/scripts/ownership-conflict.mjs +356 -0
  131. package/scripts/pinning.mjs +504 -0
  132. package/scripts/plan-apply.mjs +843 -0
  133. package/scripts/preflight.mjs +51 -0
  134. package/scripts/reconcile.mjs +511 -0
  135. package/scripts/render-templates.mjs +245 -28
  136. package/scripts/schema-validate.mjs +132 -0
  137. package/scripts/section-compat.mjs +300 -0
  138. package/scripts/section-operations.mjs +508 -0
  139. package/scripts/sync-assets.mjs +22 -15
  140. package/scripts/view-assembly.mjs +291 -0
  141. package/scripts/view-selection.mjs +211 -0
  142. package/skills/sf-feature/SKILL.md +67 -0
  143. package/skills/sf-theme-gen/SKILL.md +12 -5
  144. package/skills/sf-view-gen/SKILL.md +123 -93
  145. package/skills/sf-view-gen/assets/recipes/archetypes.json +28 -1
  146. package/skills/sf-view-gen/assets/recipes/catalog.json +361 -0
  147. package/skills/sf-view-gen/assets/recipes/catalog.schema.json +64 -0
  148. package/skills/sf-view-gen/assets/recipes/features.json +139 -0
  149. package/skills/sf-view-gen/assets/recipes/recipes.json +60 -5
  150. package/skills/sf-view-gen/assets/recipes/sections.json +24 -5
  151. package/skills/sf-view-gen/assets/templates/views/sections/cta.client.tsx +25 -0
  152. package/skills/sf-view-gen/assets/templates/views/sections/cta.tsx +16 -0
  153. package/skills/sf-view-gen/assets/templates/views/sections/faq.client.tsx +13 -9
  154. package/skills/sf-view-gen/assets/templates/views/sections/faq.tsx +2 -7
  155. package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.client.tsx +12 -8
  156. package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.tsx +2 -7
  157. package/skills/sf-view-gen/assets/templates/views/sections/pricing.client.tsx +14 -10
  158. package/skills/sf-view-gen/assets/templates/views/sections/pricing.tsx +2 -9
  159. package/skills/sf-view-gen/assets/templates/views/sections/schema.cta.ts +19 -0
  160. package/skills/sf-view-gen/assets/templates/views/sections/schema.faq.ts +18 -0
  161. package/skills/sf-view-gen/assets/templates/views/sections/schema.logoCloud.ts +19 -0
  162. package/skills/sf-view-gen/assets/templates/views/sections/schema.pricing.ts +26 -0
  163. package/skills/sf-view-gen/assets/templates/views/sections/schema.statistics.ts +25 -0
  164. package/skills/sf-view-gen/assets/templates/views/sections/schema.testimonials.ts +23 -0
  165. package/skills/sf-view-gen/assets/templates/views/sections/statistics.client.tsx +16 -8
  166. package/skills/sf-view-gen/assets/templates/views/sections/statistics.tsx +2 -7
  167. package/skills/sf-view-gen/assets/templates/views/sections/testimonials.client.tsx +13 -9
  168. package/skills/sf-view-gen/assets/templates/views/sections/testimonials.tsx +2 -5
  169. package/skills/sf-view-gen/references/views.md +44 -10
  170. package/skills/sf-view-gen/scripts/catalog.mjs +128 -0
  171. package/skills/sf-view-gen/scripts/schema-validate.mjs +132 -0
  172. package/skills/sf-view-gen/scripts/validate.mjs +784 -32
  173. package/skills/storefront-workflow/SKILL.md +116 -0
  174. package/skills/sf-view-gen/assets/templates/views/category/index.tsx +0 -86
  175. package/skills/sf-view-gen/assets/templates/views/category/registry.tsx +0 -48
  176. package/skills/sf-view-gen/assets/templates/views/checkout/index.tsx +0 -192
  177. package/skills/sf-view-gen/assets/templates/views/checkout/registry.tsx +0 -45
  178. package/skills/sf-view-gen/assets/templates/views/product/index.tsx +0 -122
  179. package/skills/sf-view-gen/assets/templates/views/product/registry.tsx +0 -49
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: storefront-workflow
3
+ description: The Thor Commerce storefront workflow model — bootstrap, compose, customize, verify — and the CLI/manifest authority. Read when working in a generated storefront, before adding/removing views or features, changing the theme, or editing storefront files.
4
+ ---
5
+
6
+ # Storefront workflow model
7
+
8
+ A generated storefront is defined by a **manifest** (`storefront.config.json` at
9
+ the project root) and produced by the `storefront` CLI. The manifest is the
10
+ structural source of truth; the CLI is the only writer of derived files; the
11
+ developer (or agent) edits only user-owned files.
12
+
13
+ Run every command from inside the project (the CLI resolves the project from the
14
+ current directory). The CLI is installed as a dependency of the project, so
15
+ invoke it with `npx @thorprovider/storefront <command>` or `yarn storefront`.
16
+
17
+ ## The four phases
18
+
19
+ **bootstrap → compose → customize → verify**
20
+
21
+ ### 1. Bootstrap
22
+
23
+ Create the project. The CLI scaffolds Next.js, installs the pinned candidate
24
+ toolset, writes the initial manifest, and reconciles.
25
+
26
+ ```bash
27
+ npx @thorprovider/storefront create <dir> --name <name> --preset <preset>
28
+ ```
29
+
30
+ Authority: **CLI only.** Do not scaffold or copy templates by hand.
31
+
32
+ ### 2. Compose
33
+
34
+ Change the structural composition. Each command validates, updates the manifest,
35
+ and reconciles the derived files.
36
+
37
+ ```bash
38
+ npx @thorprovider/storefront view add|remove <name> [--intent slot=intent] [--variant slot=Variant]
39
+ npx @thorprovider/storefront feature add|remove <name>
40
+ npx @thorprovider/storefront theme --mode light|dark|system
41
+ npx @thorprovider/storefront plan --operations <file> [--out <file>] [--json]
42
+ npx @thorprovider/storefront apply --plan <file> [--json]
43
+ ```
44
+
45
+ Section composition (`section.add` / `section.remove` / `section.reorder` for
46
+ `faq` / `cta`) goes through the same operations envelope
47
+ (`{ "formatVersion": 1, "operations": [ ... ] }`): `plan` is read-only and
48
+ validates the whole intended state, `apply --plan` is the only writer.
49
+
50
+ Authority: **CLI only.** Agents choose *what* to compose by reading
51
+ `views --json` / `features --json` / `recipes --json` — including each view's
52
+ `registeredSlots` / `allowedSections` / `maxSections` and the top-level
53
+ `sectionTypes` list — and then delegate the write to the CLI. The user states the
54
+ change in natural language and never types a command. Held section types
55
+ (`testimonials`, `statistics`, `logoCloud`, `pricing`) are refused with a bounded
56
+ reason; only the executable types (`faq`, `cta`) may be composed.
57
+
58
+ ### 3. Customize
59
+
60
+ Edit the **user-owned** files (content, props, data, local schema, mappers).
61
+ Reconcile never rewrites them; it regenerates the thin derived wrappers around
62
+ them.
63
+
64
+ Authority: **agent/developer.** Hand-edit only user-owned files.
65
+
66
+ ### 4. Verify
67
+
68
+ Regenerate and check.
69
+
70
+ ```bash
71
+ npx @thorprovider/storefront sync # reconcile derived files to the manifest
72
+ npx @thorprovider/storefront doctor # composition drift + truthful version/BOM
73
+ ```
74
+
75
+ Authority: **CLI.** A build (`yarn build`) is the final acceptance; `doctor`
76
+ must report no drift.
77
+
78
+ ## File ownership
79
+
80
+ | Class | Examples | Who writes it |
81
+ |---|---|---|
82
+ | **Derived** | `views/index.ts`, `views/registry.ts`, `index.tsx`, `registry.tsx`, `catalog.ts`, `runtime.ts`, `spec/index.ts`, `state.ts`, `handlers.ts`, `features/*/adapter.ts` | CLI (reconcile); never hand-edited |
83
+ | **User-owned** | `views/<view>/content.ts`, `props.ts`, `data.ts`, `schema.ts`, `mappers.ts` | developer/agent |
84
+ | **External** | `app/layout.tsx`, `app/globals.css`, other create-next-app/shadcn files | create-next-app/shadcn; the CLI only appends idempotent markers |
85
+
86
+ Removing a unit removes its derived wrapper but **preserves** any customized
87
+ user-owned file and reports its location.
88
+
89
+ ## Manifest authority
90
+
91
+ - `storefront.config.json` declares the name, theme, installed views, installed
92
+ features, and per-slot intents. Every structural command edits it and then
93
+ reconciles.
94
+ - `.opencode/storefront.json` is the machine-managed install record (suite
95
+ version, declared compatibility ranges, resolved versions, lockfile hash,
96
+ owned asset BOM). `doctor` validates it against the live project.
97
+ - A **legacy** (pre-manifest) project is detected and refused with a no-write
98
+ diagnostic; automatic migration is held post-MVP.
99
+
100
+ ## Curated MVP defaults
101
+
102
+ - Standalone views: `home`, `category`, `product`.
103
+ - Feature: `checkout` (owns the `checkout` view; `view add checkout` is
104
+ rejected — use `feature add checkout`).
105
+ - Held post-MVP and rejected before writes: `user-panel` and recommendations.
106
+ - Composable sections: `faq` and `cta` are executable per view; held types
107
+ (`testimonials`, `statistics`, `logoCloud`, `pricing`) are refused with a
108
+ bounded reason.
109
+
110
+ ## Pinning and prerequisites
111
+
112
+ - Node `>=20.9.0`, Yarn 4, `node-modules` linker. The generated project pins the
113
+ exact candidate toolset; `doctor` reports a linker/version mismatch non-zero.
114
+ - The `checkout` feature ships structural wiring only; the purchase flow is
115
+ owned by the separate `single-tenant-commerce-mvp` change. A generated
116
+ storefront is not a complete commerce implementation on its own.
@@ -1,86 +0,0 @@
1
- // Thor Commerce view module: category
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 -> "category".
6
-
7
- import {
8
- getCatalog,
9
- getCategoryProductsByHandleWithSource,
10
- } from '@thorprovider/runtime/commerce'
11
- import { z } from 'zod'
12
- import { pageRoot } from '../../spec/page-root'
13
- import type { StorefrontElement } from '../../spec/types'
14
- import type { ViewModule } from '../types'
15
- import { registry } from './registry'
16
-
17
- const elements: Record<string, StorefrontElement> = {
18
- 'category.filter': {
19
- type: 'CategoryFilter',
20
- props: {
21
- products: { $state: '/categoryProducts' },
22
- sortOptions: [
23
- { value: 'newest', label: 'Más recientes' },
24
- { value: 'price-asc', label: 'Precio: de menor a mayor' },
25
- { value: 'price-desc', label: 'Precio: de mayor a menor' },
26
- ],
27
- },
28
- on: {
29
- openProduct: { action: 'openProduct' },
30
- applyFilters: { action: 'applyFilters' },
31
- },
32
- },
33
- 'category.grid': {
34
- type: 'ProductCard',
35
- props: {
36
- heading: 'Productos',
37
- products: { $state: '/categoryProducts' },
38
- },
39
- on: {
40
- addToCart: { action: 'addToCart' },
41
- openProduct: { action: 'openProduct' },
42
- },
43
- },
44
- }
45
-
46
- const catalog = {
47
- CategoryFilter: {
48
- props: z.object({
49
- products: z.array(z.record(z.string(), z.unknown())),
50
- sortOptions: z
51
- .array(z.object({ value: z.string(), label: z.string() }))
52
- .optional(),
53
- categories: z.array(z.record(z.string(), z.unknown())).optional(),
54
- pagination: z.array(z.union([z.number(), z.string()])).optional(),
55
- }),
56
- description: 'Product listing filters, sorting, and results.',
57
- },
58
- }
59
-
60
- export const category: ViewModule = {
61
- name: 'category',
62
- elements,
63
- catalog,
64
- registry,
65
- route: {
66
- path: '/categorias/[[...slug]]',
67
- loaderName: 'category',
68
- layout: 'default',
69
- metadata: {
70
- title: 'Categoría',
71
- description: 'Explora los productos de esta categoría en __SITE_NAME__.',
72
- },
73
- entry: pageRoot('category', ['category.filter', 'category.grid'], elements),
74
- },
75
- loader: {
76
- category: async (params) => {
77
- const slug = Array.isArray(params.slug) ? params.slug[0] : params.slug
78
- const { products, categories } = await getCatalog()
79
- const categoryResult = slug
80
- ? await getCategoryProductsByHandleWithSource(slug)
81
- : null
82
- const categoryProducts = categoryResult ? categoryResult.products : products
83
- return { categoryProducts, storeCategories: categories }
84
- },
85
- },
86
- }
@@ -1,48 +0,0 @@
1
- 'use client'
2
-
3
- // Thor Commerce view registry fragment: category
4
- // Client boundary for the category 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
- import { CategoryFilter2 as CategoryFilterBlock } from '@thorprovider/blocks'
10
- import type { CardProduct, CategoryFilter2Filters, FilterProduct } from '@thorprovider/blocks'
11
- import { useStateStore } from '@json-render/react'
12
- import type { ViewRegistry } from '../types'
13
-
14
- export const registry: ViewRegistry = {
15
- CategoryFilter: ({ props, emit }) => {
16
- const { set } = useStateStore()
17
- const products: FilterProduct[] = ((props.products ?? []) as CardProduct[]).map(
18
- (p) => ({
19
- id: String(p.id),
20
- name: p.title,
21
- price: `$${p.price.toFixed(2)}`,
22
- image: p.image,
23
- }),
24
- )
25
- return (
26
- <CategoryFilterBlock
27
- products={products}
28
- sortOptions={props.sortOptions as never}
29
- categories={props.categories as never}
30
- pagination={props.pagination as never}
31
- onProductClick={(product) => {
32
- set('/lastClicked', product)
33
- emit('openProduct')
34
- }}
35
- onPageChange={(page) => {
36
- set('/categoryPage', page)
37
- }}
38
- onSortChange={(sort) => {
39
- set('/categorySort', sort)
40
- }}
41
- onFilterChange={(filters: CategoryFilter2Filters) => {
42
- set('/filters', filters)
43
- emit('applyFilters')
44
- }}
45
- />
46
- )
47
- },
48
- }
@@ -1,192 +0,0 @@
1
- // Thor Commerce view module: checkout
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 -> "checkout".
6
-
7
- import {
8
- getPaymentMethodsByRegion,
9
- getRegionCountries,
10
- } from '@thorprovider/runtime/commerce'
11
- import { z } from 'zod'
12
- import { pageRoot } from '../../spec/page-root'
13
- import type { StorefrontElement } from '../../spec/types'
14
- import type { ViewModule } from '../types'
15
- import { registry } from './registry'
16
-
17
- const elements: Record<string, StorefrontElement> = {
18
- 'checkout.view': {
19
- type: 'Checkout',
20
- visible: { $state: '/lastOrder', not: true },
21
- props: {
22
- title: 'Finalizar compra',
23
- description: 'Completa tus datos para recibir tu pedido.',
24
- orderSummary: {
25
- items: { $state: '/cart' },
26
- shipping: { $state: '/cartTotals/shipping' },
27
- tax: { $state: '/cartTotals/tax' },
28
- discount: { $state: '/cartTotals/discount' },
29
- promoDiscount: { $state: '/cartTotals/promoDiscount' },
30
- },
31
- countries: { $state: '/checkoutCountries' },
32
- paymentMethods: { $state: '/checkoutPaymentMethods' },
33
- values: {
34
- email: { $state: '/checkout/email' },
35
- firstName: { $state: '/checkout/firstName' },
36
- lastName: { $state: '/checkout/lastName' },
37
- phone: { $state: '/checkout/phone' },
38
- address: { $state: '/checkout/address' },
39
- city: { $state: '/checkout/city' },
40
- state: { $state: '/checkout/state' },
41
- zipCode: { $state: '/checkout/zipCode' },
42
- country: { $state: '/checkout/country' },
43
- paymentMethod: { $state: '/checkout/paymentMethod' },
44
- cardNumber: { $state: '/checkout/cardNumber' },
45
- expiryDate: { $state: '/checkout/expiryDate' },
46
- cvv: { $state: '/checkout/cvv' },
47
- cardName: { $state: '/checkout/cardName' },
48
- saveInfo: { $state: '/checkout/saveInfo' },
49
- sameAsBilling: { $state: '/checkout/sameAsBilling' },
50
- newsletter: { $state: '/checkout/newsletter' },
51
- promoCode: { $state: '/checkout/promoCode' },
52
- },
53
- validation: {
54
- email: {
55
- validateOn: 'blur',
56
- checks: [
57
- { type: 'required', message: 'Ingresa tu correo electrónico.' },
58
- { type: 'email', message: 'Ingresa un correo válido.' },
59
- ],
60
- },
61
- firstName: {
62
- checks: [{ type: 'required', message: 'Ingresa tu nombre.' }],
63
- },
64
- lastName: {
65
- checks: [{ type: 'required', message: 'Ingresa tus apellidos.' }],
66
- },
67
- phone: {
68
- checks: [
69
- {
70
- type: 'pattern',
71
- args: { pattern: '^[+]?[0-9]{7,15}$' },
72
- message: 'Ingresa un teléfono válido.',
73
- },
74
- ],
75
- },
76
- address: {
77
- checks: [{ type: 'required', message: 'Ingresa tu dirección.' }],
78
- },
79
- city: {
80
- checks: [{ type: 'required', message: 'Ingresa la ciudad.' }],
81
- },
82
- state: {
83
- checks: [{ type: 'required', message: 'Ingresa la provincia.' }],
84
- },
85
- country: {
86
- checks: [{ type: 'required', message: 'Selecciona tu país.' }],
87
- },
88
- zipCode: {
89
- checks: [
90
- { type: 'required', message: 'Ingresa el código postal.' },
91
- {
92
- type: 'pattern',
93
- args: { pattern: '^[0-9]{4,5}$' },
94
- message: 'Código postal inválido.',
95
- },
96
- ],
97
- },
98
- paymentMethod: {
99
- checks: [{ type: 'required', message: 'Selecciona un método de pago.' }],
100
- },
101
- },
102
- fieldDescriptions: {
103
- email: 'Usaremos este correo para el seguimiento del pedido.',
104
- phone: 'Te contactaremos para coordinar la entrega.',
105
- promoCode: 'Aplica códigos promocionales válidos.',
106
- },
107
- },
108
- on: { placeOrder: { action: 'placeOrder' } },
109
- },
110
- 'checkout.confirmation': {
111
- type: 'CheckoutConfirmation',
112
- visible: { $state: '/lastOrder' },
113
- props: {
114
- order: { $state: '/lastOrder' },
115
- orderLines: { $state: '/lastOrderLines' },
116
- fulfilmentDetails: {
117
- Confirmed: {
118
- stage: 'Confirmed',
119
- summary: 'Pago autorizado y el pedido fue enviado a preparación.',
120
- location: 'Centro de distribución',
121
- at: { $state: '/orderPlaced' },
122
- },
123
- Packed: {
124
- stage: 'Packed',
125
- summary: 'Los artículos fueron seleccionados y empacados.',
126
- location: 'Centro de distribución',
127
- at: null,
128
- },
129
- Shipped: {
130
- stage: 'Shipped',
131
- summary: 'El paquete fue entregado al transportista.',
132
- location: 'En tránsito',
133
- at: null,
134
- },
135
- Delivered: {
136
- stage: 'Delivered',
137
- summary: 'Entregado en tu dirección.',
138
- location: 'Tu dirección',
139
- at: null,
140
- },
141
- },
142
- },
143
- on: { continueShopping: { action: 'continueShopping' } },
144
- },
145
- }
146
-
147
- const catalog = {
148
- Checkout: {
149
- props: z.record(z.string(), z.unknown()),
150
- description: 'Checkout form with order summary, shipping and payment steps.',
151
- },
152
- CheckoutConfirmation: {
153
- props: z.record(z.string(), z.unknown()),
154
- description: 'Order confirmation with fulfilment progress.',
155
- },
156
- }
157
-
158
- export const checkout: ViewModule = {
159
- name: 'checkout',
160
- elements,
161
- catalog,
162
- registry,
163
- route: {
164
- path: '/checkout',
165
- loaderName: 'checkout',
166
- layout: 'default',
167
- metadata: {
168
- title: 'Finalizar compra',
169
- description: 'Completa tus datos de envío y pago para recibir tu pedido.',
170
- },
171
- entry: pageRoot(
172
- 'checkout',
173
- ['checkout.view', 'checkout.confirmation'],
174
- elements,
175
- ),
176
- },
177
- loader: {
178
- checkout: async () => {
179
- const [countries, paymentMethods] = await Promise.all([
180
- getRegionCountries(),
181
- getPaymentMethodsByRegion(),
182
- ])
183
- return {
184
- checkoutCountries: countries.map((country) => ({
185
- value: country.code,
186
- label: country.name,
187
- })),
188
- checkoutPaymentMethods: paymentMethods,
189
- }
190
- },
191
- },
192
- }
@@ -1,45 +0,0 @@
1
- 'use client'
2
-
3
- // Thor Commerce view registry fragment: checkout
4
- // Client boundary for the checkout 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
- import {
10
- CheckoutForm1 as CheckoutBlock,
11
- CheckoutConfirmation as CheckoutConfirmationBlock,
12
- } from '@thorprovider/blocks'
13
- import { useStateStore } from '@json-render/react'
14
- import type { ViewRegistry } from '../types'
15
-
16
- export const registry: ViewRegistry = {
17
- Checkout: ({ props, emit }) => {
18
- const { state, set } = useStateStore()
19
- return (
20
- <CheckoutBlock
21
- title={props.title as string}
22
- description={props.description as string}
23
- orderSummary={props.orderSummary as never}
24
- countries={props.countries as never}
25
- paymentMethods={props.paymentMethods as never}
26
- values={props.values as never}
27
- fieldDescriptions={props.fieldDescriptions as never}
28
- onChange={(field, value) => set(`/checkout/${field}`, value)}
29
- onSubmit={() => emit('placeOrder')}
30
- submitting={Boolean(state.checkoutPending)}
31
- submitError={(state.checkoutError as string | null) ?? undefined}
32
- />
33
- )
34
- },
35
- CheckoutConfirmation: ({ props, emit }) =>
36
- props.order ? (
37
- <CheckoutConfirmationBlock
38
- order={props.order as never}
39
- orderLines={props.orderLines as never}
40
- fulfilmentDetails={props.fulfilmentDetails as never}
41
- continueShoppingHref="#"
42
- onContinueShopping={() => emit('continueShopping')}
43
- />
44
- ) : null,
45
- }
@@ -1,122 +0,0 @@
1
- // Thor Commerce view module: product
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 -> "product".
6
-
7
- import { getProductByHandle } from '@thorprovider/runtime/commerce'
8
- import { z } from 'zod'
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
-
14
- const elements: Record<string, StorefrontElement> = {
15
- 'product.overview': {
16
- type: 'ProductOverview',
17
- props: {
18
- product: { $state: '/selectedProduct' },
19
- },
20
- on: { addToCart: { action: 'addToCart' } },
21
- },
22
- 'product.reviews': {
23
- type: 'Reviews',
24
- props: {
25
- pageTitle: 'Opiniones de clientes',
26
- pageDescription: 'Lo que dicen quienes ya compraron este producto.',
27
- reviews: [
28
- {
29
- id: 1,
30
- userName: 'Carlos M.',
31
- date: '2026-07-12',
32
- rating: 5,
33
- content: 'Excelente calidad y llegó antes de lo esperado. Muy buena compra.',
34
- likes: 12,
35
- comments: 2,
36
- },
37
- {
38
- id: 2,
39
- userName: 'Ana R.',
40
- date: '2026-07-03',
41
- rating: 4,
42
- content: 'Funciona muy bien y la atención fue rápida. Lo recomiendo.',
43
- likes: 7,
44
- comments: 1,
45
- },
46
- {
47
- id: 3,
48
- userName: 'Luis P.',
49
- date: '2026-06-28',
50
- rating: 5,
51
- content: 'Producto robusto, tal como aparece en la descripción.',
52
- likes: 5,
53
- comments: 0,
54
- },
55
- ],
56
- form: {
57
- title: 'Deja tu opinión',
58
- description: 'Tu experiencia ayuda a otros clientes.',
59
- nameLabel: 'Nombre',
60
- emailLabel: 'Correo electrónico',
61
- reviewLabel: 'Tu reseña',
62
- namePlaceholder: 'Tu nombre',
63
- emailPlaceholder: 'tucorreo@ejemplo.cu',
64
- reviewPlaceholder: 'Cuéntanos tu experiencia...',
65
- submitButtonText: 'Publicar reseña',
66
- submittingButtonText: 'Publicando...',
67
- },
68
- reviewsPerPage: 3,
69
- },
70
- on: {
71
- submitReview: { action: 'submitReview' },
72
- toggleReviewLike: { action: 'toggleReviewLike' },
73
- },
74
- },
75
- }
76
-
77
- const catalog = {
78
- ProductOverview: {
79
- props: z.object({
80
- product: z.record(z.string(), z.unknown()),
81
- sizesLabel: z.string().optional(),
82
- colorLabel: z.string().optional(),
83
- reviewsLabel: z.string().optional(),
84
- addToCartLabel: z.string().optional(),
85
- }),
86
- description: 'Detalle de producto: galería, precio, variantes y añadir al carrito.',
87
- },
88
- Reviews: {
89
- props: z.object({
90
- pageTitle: z.string().optional(),
91
- pageDescription: z.string().optional(),
92
- reviews: z.array(z.record(z.string(), z.unknown())),
93
- form: z.record(z.string(), z.unknown()).optional(),
94
- reviewsPerPage: z.number().optional(),
95
- }),
96
- description: 'Opiniones de clientes con resumen de valoración.',
97
- },
98
- }
99
-
100
- export const product: ViewModule = {
101
- name: 'product',
102
- elements,
103
- catalog,
104
- registry,
105
- route: {
106
- path: '/productos/[id]',
107
- loaderName: 'product',
108
- layout: 'default',
109
- metadata: {
110
- title: 'Producto',
111
- description: 'Detalles del producto seleccionado.',
112
- },
113
- entry: pageRoot('product', ['product.overview', 'product.reviews'], elements),
114
- },
115
- loader: {
116
- product: async (params) => {
117
- const id = Array.isArray(params.id) ? params.id[0] : params.id
118
- const selectedProduct = await getProductByHandle(String(id))
119
- return { selectedProduct }
120
- },
121
- },
122
- }
@@ -1,49 +0,0 @@
1
- 'use client'
2
-
3
- // Thor Commerce view registry fragment: product
4
- // Client boundary for the product 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
- import {
10
- ProductOverview1 as ProductOverviewBlock,
11
- ReviewRating1 as ReviewsBlock,
12
- } from '@thorprovider/blocks'
13
- import { useStateStore } from '@json-render/react'
14
- import type { ViewRegistry } from '../types'
15
-
16
- export const registry: ViewRegistry = {
17
- ProductOverview: ({ props, emit }) => {
18
- const { set } = useStateStore()
19
- return (
20
- <ProductOverviewBlock
21
- product={props.product as never}
22
- onAddToCart={(product) => {
23
- set('/lastClicked', product)
24
- emit('addToCart')
25
- }}
26
- onToggleWishlist={() => {}}
27
- />
28
- )
29
- },
30
- Reviews: ({ props, emit }) => {
31
- const { set } = useStateStore()
32
- return (
33
- <ReviewsBlock
34
- pageTitle={props.pageTitle as string}
35
- pageDescription={props.pageDescription as string}
36
- reviews={props.reviews as never}
37
- form={props.form as never}
38
- reviewsPerPage={props.reviewsPerPage as number}
39
- onToggleReviewLike={() => {
40
- emit('toggleReviewLike')
41
- }}
42
- onSubmitReview={(data) => {
43
- set('/lastReview', data)
44
- emit('submitReview')
45
- }}
46
- />
47
- )
48
- },
49
- }