@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,229 @@
|
|
|
1
|
+
// Thor Commerce view schema fragment: checkout
|
|
2
|
+
// Zod mirrors of the block contracts for this view — the single source for the
|
|
3
|
+
// catalog entries and the client-side registry parsing (json-render does not
|
|
4
|
+
// validate props against the catalog itself, so the registry parses here).
|
|
5
|
+
// Kept free of React and server imports so the Server Component module and the
|
|
6
|
+
// 'use client' registry can share one contract. Block zod contracts are
|
|
7
|
+
// mirrored, not imported: @thorprovider/blocks ships zod v3 while the scaffold
|
|
8
|
+
// runs zod v4 and cross-major schema assignment breaks the catalog contract.
|
|
9
|
+
|
|
10
|
+
import { z } from 'zod'
|
|
11
|
+
|
|
12
|
+
// Canonical Money: a decimal string in major units, never cents.
|
|
13
|
+
const moneySchema = z.object({
|
|
14
|
+
amount: z.string(),
|
|
15
|
+
currencyCode: z.string(),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// Mirrors the CheckoutForm1FormData block contract. Card fields stay in the
|
|
19
|
+
// shape (the block requires every key) but are never collected at launch.
|
|
20
|
+
export const checkoutFormDataSchema = z.object({
|
|
21
|
+
email: z.string(),
|
|
22
|
+
firstName: z.string(),
|
|
23
|
+
lastName: z.string(),
|
|
24
|
+
phone: z.string(),
|
|
25
|
+
address: z.string(),
|
|
26
|
+
city: z.string(),
|
|
27
|
+
state: z.string(),
|
|
28
|
+
zipCode: z.string(),
|
|
29
|
+
country: z.string(),
|
|
30
|
+
paymentMethod: z.string(),
|
|
31
|
+
cardNumber: z.string(),
|
|
32
|
+
expiryDate: z.string(),
|
|
33
|
+
cvv: z.string(),
|
|
34
|
+
cardName: z.string(),
|
|
35
|
+
saveInfo: z.boolean(),
|
|
36
|
+
sameAsBilling: z.boolean(),
|
|
37
|
+
newsletter: z.boolean(),
|
|
38
|
+
promoCode: z.string(),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Mirrors CheckoutForm1OrderSummaryItem: backend line total preferred, legacy
|
|
42
|
+
// numeric price accepted only as a fallback; optional image omitted when absent.
|
|
43
|
+
export const orderSummaryItemSchema = z.object({
|
|
44
|
+
id: z.union([z.string(), z.number()]),
|
|
45
|
+
name: z.string(),
|
|
46
|
+
variant: z.string().optional(),
|
|
47
|
+
quantity: z.number(),
|
|
48
|
+
lineTotal: moneySchema.optional(),
|
|
49
|
+
price: z.number().optional(),
|
|
50
|
+
image: z.string().optional(),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Mirrors CheckoutForm1Totals (backend-authoritative).
|
|
54
|
+
export const checkoutTotalsSchema = z.object({
|
|
55
|
+
subtotal: moneySchema,
|
|
56
|
+
discount: moneySchema.optional(),
|
|
57
|
+
tax: moneySchema.optional(),
|
|
58
|
+
shipping: moneySchema.optional(),
|
|
59
|
+
total: moneySchema,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
export const orderSummarySchema = z.object({
|
|
63
|
+
items: z.array(orderSummaryItemSchema),
|
|
64
|
+
totals: checkoutTotalsSchema.optional(),
|
|
65
|
+
shipping: z.number().optional(),
|
|
66
|
+
tax: z.number().optional(),
|
|
67
|
+
discount: z.number().optional(),
|
|
68
|
+
promoDiscount: z.number().optional(),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
export const countryOptionSchema = z.object({
|
|
72
|
+
value: z.string(),
|
|
73
|
+
label: z.string(),
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Mirrors CheckoutForm1ShippingOption (backend shipping method id).
|
|
77
|
+
export const shippingOptionSchema = z.object({
|
|
78
|
+
id: z.string(),
|
|
79
|
+
name: z.string(),
|
|
80
|
+
price: moneySchema.optional(),
|
|
81
|
+
description: z.string().optional(),
|
|
82
|
+
estimatedDays: z.string().optional(),
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// Mirrors CheckoutFieldErrors: per-field structured messages.
|
|
86
|
+
export const checkoutFieldErrorsSchema = z.record(
|
|
87
|
+
z.string(),
|
|
88
|
+
z.array(z.object({ message: z.string().optional() })),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// Mirrors the PaymentMethod contract from @thorprovider/types.
|
|
92
|
+
export const paymentMethodSchema = z.object({
|
|
93
|
+
id: z.string(),
|
|
94
|
+
name: z.string(),
|
|
95
|
+
type: z.enum([
|
|
96
|
+
'card',
|
|
97
|
+
'wallet',
|
|
98
|
+
'bank_transfer',
|
|
99
|
+
'buy_now_pay_later',
|
|
100
|
+
'cash_on_delivery',
|
|
101
|
+
'crypto',
|
|
102
|
+
'other',
|
|
103
|
+
]),
|
|
104
|
+
isEnabled: z.boolean(),
|
|
105
|
+
requiresSetup: z.boolean(),
|
|
106
|
+
description: z.string().optional(),
|
|
107
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
108
|
+
features: z
|
|
109
|
+
.object({
|
|
110
|
+
saveForLater: z.boolean().optional(),
|
|
111
|
+
installmentPayment: z.boolean().optional(),
|
|
112
|
+
recurring: z.boolean().optional(),
|
|
113
|
+
})
|
|
114
|
+
.optional(),
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
export const checkoutPropsSchema = z.object({
|
|
118
|
+
title: z.string().optional(),
|
|
119
|
+
description: z.string().optional(),
|
|
120
|
+
orderSummary: orderSummarySchema.optional(),
|
|
121
|
+
countries: z.array(countryOptionSchema).optional(),
|
|
122
|
+
shippingOptions: z.array(shippingOptionSchema).optional(),
|
|
123
|
+
// Nullable: the state initializes to `null` and the block prop is optional.
|
|
124
|
+
selectedShippingOptionId: z.string().nullable().optional(),
|
|
125
|
+
paymentMethods: z.array(paymentMethodSchema).optional(),
|
|
126
|
+
values: checkoutFormDataSchema,
|
|
127
|
+
fieldErrors: checkoutFieldErrorsSchema.optional(),
|
|
128
|
+
fieldDescriptions: z.record(z.string(), z.string()).optional(),
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// The empty-cart surface has no props; declared so the catalog is complete.
|
|
132
|
+
export const checkoutEmptyPropsSchema = z.object({})
|
|
133
|
+
|
|
134
|
+
export const fulfilmentStageSchema = z.enum(['Confirmed', 'Packed', 'Shipped', 'Delivered'])
|
|
135
|
+
|
|
136
|
+
const orderAddressSchema = z.object({
|
|
137
|
+
name: z.string(),
|
|
138
|
+
lines: z.array(z.string()),
|
|
139
|
+
method: z.string().optional(),
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const orderTotalsSchema = z.object({
|
|
143
|
+
subtotal: moneySchema.optional(),
|
|
144
|
+
discount: moneySchema.optional(),
|
|
145
|
+
shipping: moneySchema.optional(),
|
|
146
|
+
tax: moneySchema.optional(),
|
|
147
|
+
total: moneySchema.optional(),
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Mirrors the CheckoutConfirmation Order block contract: backend Money totals,
|
|
151
|
+
// optional fields rendered only when the backend supplies them (no fabricated
|
|
152
|
+
// estimate or stage).
|
|
153
|
+
export const storefrontOrderSchema = z.object({
|
|
154
|
+
number: z.string(),
|
|
155
|
+
placedAt: z.string(),
|
|
156
|
+
email: z.string().optional(),
|
|
157
|
+
currentStage: fulfilmentStageSchema.optional(),
|
|
158
|
+
estimatedDelivery: z.string().nullable().optional(),
|
|
159
|
+
trackingHref: z.string().optional(),
|
|
160
|
+
shipping: orderAddressSchema.optional(),
|
|
161
|
+
billing: orderAddressSchema.optional(),
|
|
162
|
+
totals: orderTotalsSchema,
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
export const orderLineSchema = z.object({
|
|
166
|
+
id: z.string(),
|
|
167
|
+
name: z.string(),
|
|
168
|
+
variant: z.string().optional(),
|
|
169
|
+
quantity: z.number(),
|
|
170
|
+
lineTotal: moneySchema,
|
|
171
|
+
unitPrice: moneySchema.optional(),
|
|
172
|
+
image: z.string().optional(),
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
export const fulfilmentStageDetailSchema = z.object({
|
|
176
|
+
stage: fulfilmentStageSchema,
|
|
177
|
+
summary: z.string(),
|
|
178
|
+
location: z.string(),
|
|
179
|
+
at: z.string().nullable(),
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// The atomic receipt: the order and its backend lines arrive together.
|
|
183
|
+
export const receiptSchema = z.object({
|
|
184
|
+
order: storefrontOrderSchema,
|
|
185
|
+
lines: z.array(orderLineSchema),
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
// Mirrors the CheckoutConfirmation props, including every neutral label the
|
|
189
|
+
// host forwards. Labels are declared here so `z.object` does not strip them;
|
|
190
|
+
// the registry forwards them to the block instead of relying on its defaults.
|
|
191
|
+
export const checkoutConfirmationPropsSchema = z.object({
|
|
192
|
+
receipt: receiptSchema,
|
|
193
|
+
fulfilmentDetails: z.record(z.string(), fulfilmentStageDetailSchema).optional(),
|
|
194
|
+
titleLabel: z.string().optional(),
|
|
195
|
+
orderPlacedLabel: z.string().optional(),
|
|
196
|
+
orderPlacedNoEmailLabel: z.string().optional(),
|
|
197
|
+
itemsLabel: z.string().optional(),
|
|
198
|
+
quantityLabel: z.string().optional(),
|
|
199
|
+
subtotalLabel: z.string().optional(),
|
|
200
|
+
discountLabel: z.string().optional(),
|
|
201
|
+
shippingLabel: z.string().optional(),
|
|
202
|
+
taxLabel: z.string().optional(),
|
|
203
|
+
totalLabel: z.string().optional(),
|
|
204
|
+
freeLabel: z.string().optional(),
|
|
205
|
+
amountUnavailableLabel: z.string().optional(),
|
|
206
|
+
shippingAddressLabel: z.string().optional(),
|
|
207
|
+
billingAddressLabel: z.string().optional(),
|
|
208
|
+
continueShoppingLabel: z.string().optional(),
|
|
209
|
+
contactSupportLabel: z.string().optional(),
|
|
210
|
+
supportLabel: z.string().optional(),
|
|
211
|
+
progressLabel: z.string().optional(),
|
|
212
|
+
estimatedDeliveryLabel: z.string().optional(),
|
|
213
|
+
currentStageLabel: z.string().optional(),
|
|
214
|
+
inProgressLabel: z.string().optional(),
|
|
215
|
+
expectedByLabel: z.string().optional(),
|
|
216
|
+
completedLabel: z.string().optional(),
|
|
217
|
+
statusLabel: z.string().optional(),
|
|
218
|
+
locationLabel: z.string().optional(),
|
|
219
|
+
trackOrderLabel: z.string().optional(),
|
|
220
|
+
continueShoppingHref: z.string().optional(),
|
|
221
|
+
supportHref: z.string().optional(),
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
export type CheckoutFormData = z.infer<typeof checkoutFormDataSchema>
|
|
225
|
+
export type OrderSummary = z.infer<typeof orderSummarySchema>
|
|
226
|
+
export type CheckoutProps = z.infer<typeof checkoutPropsSchema>
|
|
227
|
+
export type StorefrontOrder = z.infer<typeof storefrontOrderSchema>
|
|
228
|
+
export type OrderLine = z.infer<typeof orderLineSchema>
|
|
229
|
+
export type CheckoutConfirmationProps = z.infer<typeof checkoutConfirmationPropsSchema>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"view": "home",
|
|
3
|
+
"slots": [
|
|
4
|
+
{ "id": "home.hero", "slot": "hero", "default": "StorefrontHero1" },
|
|
5
|
+
{ "id": "home.benefits", "slot": "benefits", "default": "Feature1" },
|
|
6
|
+
{ "id": "home.categories", "slot": "categories", "default": "ProductCategory2" }
|
|
7
|
+
],
|
|
8
|
+
"sections": {
|
|
9
|
+
"faq": {
|
|
10
|
+
"elementType": "Faq",
|
|
11
|
+
"registryModule": "../sections/faq.client",
|
|
12
|
+
"registryExport": "faqRegistry",
|
|
13
|
+
"propsModule": "./content",
|
|
14
|
+
"defaultProps": "{ items: [] }"
|
|
15
|
+
},
|
|
16
|
+
"cta": {
|
|
17
|
+
"elementType": "Cta",
|
|
18
|
+
"registryModule": "../sections/cta.client",
|
|
19
|
+
"registryExport": "ctaRegistry",
|
|
20
|
+
"propsModule": "./content",
|
|
21
|
+
"defaultProps": "{}"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
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 } 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 { selectHeroProducts, toNavCategories } from './data'
|
|
15
|
+
import { toCategoryCard, toFeaturedProduct } from '../../mappers'
|
|
16
|
+
import {
|
|
17
|
+
categoryCardsPropsSchema,
|
|
18
|
+
featurePropsSchema,
|
|
19
|
+
storefrontHeroPropsSchema,
|
|
20
|
+
} from './schema'@@SECTION_IMPORTS@@
|
|
21
|
+
|
|
22
|
+
// The element tree is derived wiring; the rendered props come from the
|
|
23
|
+
// user-owned ./props.ts module so they survive sync and unit removal.
|
|
24
|
+
const elements: Record<string, StorefrontElement> = {
|
|
25
|
+
@@ELEMENTS@@
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const catalog = {
|
|
29
|
+
@@CATALOG@@
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const home: ViewModule = {
|
|
33
|
+
name: 'home',
|
|
34
|
+
elements,
|
|
35
|
+
catalog,
|
|
36
|
+
registry,
|
|
37
|
+
route: {
|
|
38
|
+
path: '/',
|
|
39
|
+
loaderName: 'home',
|
|
40
|
+
layout: 'default',
|
|
41
|
+
metadata: {
|
|
42
|
+
title: 'Inicio',
|
|
43
|
+
description: 'Descubre los productos de __SITE_NAME__.',
|
|
44
|
+
},
|
|
45
|
+
entry: pageRoot('home', [@@SLOT_IDS@@], elements),
|
|
46
|
+
},
|
|
47
|
+
loader: {
|
|
48
|
+
home: async () => {
|
|
49
|
+
try {
|
|
50
|
+
const { products, categories, source } = await getCatalog()
|
|
51
|
+
return {
|
|
52
|
+
heroProducts: selectHeroProducts(products).map(toFeaturedProduct),
|
|
53
|
+
categories: categories.map(toCategoryCard),
|
|
54
|
+
navCategories: toNavCategories(categories),
|
|
55
|
+
dataSource: source,
|
|
56
|
+
dataError: null,
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// A real-mode failure is surfaced as a visible error, never as fictional
|
|
60
|
+
// data. Mock mode is labelled by `dataSource`.
|
|
61
|
+
return {
|
|
62
|
+
heroProducts: [],
|
|
63
|
+
categories: [],
|
|
64
|
+
navCategories: [],
|
|
65
|
+
dataSource: getDataSource(),
|
|
66
|
+
dataError:
|
|
67
|
+
error instanceof Error ? error.message : 'No se pudo cargar el catálogo.',
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
}
|
|
@@ -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
|
+
// No first-variant shortcut: open the detail so a variant is chosen.
|
|
16
|
+
set('/lastClicked', product)
|
|
17
|
+
emit('openProduct')
|
|
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,15 @@
|
|
|
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
|
+
}
|