@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.
- package/CHANGELOG.md +23 -0
- package/README.md +248 -99
- package/assets/templates/app/app/[[...slug]]/page.tsx +13 -2
- package/assets/templates/app/app/layout.tsx +18 -4
- package/assets/templates/app/app/robots.ts +13 -0
- package/assets/templates/app/app/sitemap.ts +12 -0
- package/assets/templates/app/lib/__STOREFRONT__/actions.ts +542 -0
- package/assets/templates/app/lib/__STOREFRONT__/cart-state.ts +66 -0
- package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +40 -57
- package/assets/templates/app/lib/__STOREFRONT__/commerce.ts +608 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.core.ts +165 -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 +232 -51
- package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +3 -3
- package/assets/templates/app/lib/__STOREFRONT__/schema.ts +121 -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 +96 -0
- package/assets/templates/app/lib/__STOREFRONT__/shell/props.ts +34 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +100 -11
- package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +5 -1
- package/assets/templates/app/lib/__STOREFRONT__/spec/seo.ts +26 -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 +142 -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 +17 -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/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/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 +153 -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 +36 -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 +378 -0
- package/assets/templates/views/account/schema.ts +52 -0
- package/assets/templates/views/category/assembly/assembly.json +28 -0
- package/assets/templates/views/category/assembly/index.skeleton.tsx +193 -0
- package/assets/templates/views/category/assembly/registry.skeleton.tsx +33 -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 +230 -0
- package/assets/templates/views/category/props.ts +16 -0
- package/assets/templates/views/category/registry.tsx +103 -0
- package/assets/templates/views/category/schema.ts +78 -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 +24 -0
- package/assets/templates/views/home/assembly/index.skeleton.tsx +72 -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 +15 -0
- package/assets/templates/views/home/index.tsx +38 -84
- 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 +48 -0
- package/assets/templates/views/product/index.tsx +104 -0
- package/assets/templates/views/product/props.ts +20 -0
- package/assets/templates/views/product/registry.tsx +78 -0
- package/assets/templates/views/product/schema.ts +108 -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/{skills/sf-view-gen/assets → assets}/templates/views/user-panel/index.tsx +8 -70
- package/{skills/sf-view-gen/assets → assets}/templates/views/user-panel/registry.tsx +23 -18
- package/assets/templates/views/user-panel/schema.ts +79 -0
- package/bin/storefront.js +1278 -79
- package/commands/sf-add-view.md +44 -13
- package/commands/sf-feature.md +28 -0
- package/commands/sf-theme.md +14 -8
- package/commands/sf-view.md +54 -12
- package/package.json +6 -4
- package/recipes/archetypes.json +28 -1
- package/recipes/catalog.json +361 -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 +139 -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 +60 -5
- package/recipes/sections.json +24 -5
- package/recipes/validate.mjs +784 -32
- package/scripts/candidate.mjs +236 -0
- package/scripts/composers.mjs +140 -0
- package/scripts/file-classes.mjs +116 -0
- package/scripts/logical-controls.mjs +61 -0
- package/scripts/manifest.mjs +298 -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 +504 -0
- package/scripts/plan-apply.mjs +843 -0
- package/scripts/preflight.mjs +51 -0
- package/scripts/reconcile.mjs +511 -0
- package/scripts/render-templates.mjs +245 -28
- package/scripts/schema-validate.mjs +132 -0
- package/scripts/section-compat.mjs +300 -0
- package/scripts/section-operations.mjs +508 -0
- package/scripts/sync-assets.mjs +22 -15
- package/scripts/view-assembly.mjs +291 -0
- package/scripts/view-selection.mjs +211 -0
- package/skills/sf-feature/SKILL.md +67 -0
- package/skills/sf-theme-gen/SKILL.md +12 -5
- package/skills/sf-view-gen/SKILL.md +123 -93
- package/skills/sf-view-gen/assets/recipes/archetypes.json +28 -1
- package/skills/sf-view-gen/assets/recipes/catalog.json +361 -0
- package/skills/sf-view-gen/assets/recipes/catalog.schema.json +64 -0
- package/skills/sf-view-gen/assets/recipes/features.json +139 -0
- package/skills/sf-view-gen/assets/recipes/recipes.json +60 -5
- package/skills/sf-view-gen/assets/recipes/sections.json +24 -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/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.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/sf-view-gen/references/views.md +44 -10
- 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 +784 -32
- package/skills/storefront-workflow/SKILL.md +116 -0
- 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
|
@@ -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
|
-
}
|