@thorprovider/storefront 0.3.1
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 +61 -0
- package/README.md +333 -0
- package/assets/templates/app/app/[[...slug]]/page.tsx +17 -0
- package/assets/templates/app/app/[[...slug]]/renderer.tsx +10 -0
- package/assets/templates/app/app/globals.css +101 -0
- package/assets/templates/app/app/layout.tsx +35 -0
- package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +88 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.ts +42 -0
- package/assets/templates/app/lib/__STOREFRONT__/mappers.ts +47 -0
- package/assets/templates/app/lib/__STOREFRONT__/registry.tsx +98 -0
- package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +17 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +48 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +26 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/types.ts +14 -0
- package/assets/templates/app/lib/__STOREFRONT__/state.ts +35 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/index.ts +7 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.tsx +29 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/logoCloud.tsx +29 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/pricing.tsx +33 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/statistics.tsx +29 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/testimonials.tsx +28 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/types.ts +46 -0
- package/assets/templates/views/home/index.tsx +142 -0
- package/assets/templates/views/home/registry.tsx +65 -0
- package/bin/storefront.js +531 -0
- package/commands/sf-add-view.md +26 -0
- package/commands/sf-theme.md +20 -0
- package/commands/sf-view.md +25 -0
- package/package.json +48 -0
- package/recipes/archetype.schema.json +39 -0
- package/recipes/archetypes.json +148 -0
- package/recipes/recipe.schema.json +59 -0
- package/recipes/recipes.json +90 -0
- package/recipes/sections.json +46 -0
- package/recipes/validate.mjs +190 -0
- package/scripts/render-templates.mjs +255 -0
- package/scripts/sync-assets.mjs +87 -0
- package/skills/building-storefronts/SKILL.md +178 -0
- package/skills/building-storefronts/references/frontend-integration.md +229 -0
- package/skills/json-render-core/SKILL.md +291 -0
- package/skills/json-render-next/SKILL.md +194 -0
- package/skills/json-render-react/SKILL.md +298 -0
- package/skills/json-render-remotion/SKILL.md +111 -0
- package/skills/json-render-shadcn/SKILL.md +159 -0
- package/skills/json-render-solid/SKILL.md +204 -0
- package/skills/nextjs-shadcn/SKILL.md +367 -0
- package/skills/nextjs-shadcn/references/architecture.md +499 -0
- package/skills/nextjs-shadcn/references/project-setup.md +162 -0
- package/skills/nextjs-shadcn/references/shadcn-platform.md +261 -0
- package/skills/nextjs-shadcn/references/sidebar.md +274 -0
- package/skills/nextjs-shadcn/references/styling.md +555 -0
- package/skills/sf-theme-gen/SKILL.md +61 -0
- package/skills/sf-view-gen/SKILL.md +116 -0
- package/skills/sf-view-gen/assets/recipes/archetypes.json +148 -0
- package/skills/sf-view-gen/assets/recipes/recipes.json +90 -0
- package/skills/sf-view-gen/assets/recipes/sections.json +46 -0
- package/skills/sf-view-gen/assets/templates/views/category/index.tsx +86 -0
- package/skills/sf-view-gen/assets/templates/views/category/registry.tsx +48 -0
- package/skills/sf-view-gen/assets/templates/views/checkout/index.tsx +192 -0
- package/skills/sf-view-gen/assets/templates/views/checkout/registry.tsx +45 -0
- package/skills/sf-view-gen/assets/templates/views/product/index.tsx +122 -0
- package/skills/sf-view-gen/assets/templates/views/product/registry.tsx +49 -0
- package/skills/sf-view-gen/assets/templates/views/sections/faq.client.tsx +20 -0
- package/skills/sf-view-gen/assets/templates/views/sections/faq.tsx +21 -0
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.client.tsx +18 -0
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.tsx +21 -0
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.client.tsx +20 -0
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.tsx +23 -0
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.client.tsx +18 -0
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.tsx +21 -0
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.client.tsx +19 -0
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.tsx +19 -0
- package/skills/sf-view-gen/assets/templates/views/user-panel/index.tsx +168 -0
- package/skills/sf-view-gen/assets/templates/views/user-panel/registry.tsx +49 -0
- package/skills/sf-view-gen/references/views.md +69 -0
- package/skills/sf-view-gen/scripts/validate.mjs +190 -0
- package/skills/shadcn-component-discovery/SKILL.md +273 -0
- package/skills/shadcn-component-discovery/references/registries.md +226 -0
- package/skills/shadcn-theming/SKILL.md +104 -0
- package/skills/shadcn-theming/references/templates/theme-setup.md +109 -0
- package/skills/shadcn-theming/references/theming-guide.md +90 -0
- package/skills/storefront-best-practices/SKILL.md +421 -0
- package/skills/storefront-best-practices/reference/components/breadcrumbs.md +123 -0
- package/skills/storefront-best-practices/reference/components/cart-popup.md +189 -0
- package/skills/storefront-best-practices/reference/components/country-selector.md +298 -0
- package/skills/storefront-best-practices/reference/components/footer.md +112 -0
- package/skills/storefront-best-practices/reference/components/hero.md +241 -0
- package/skills/storefront-best-practices/reference/components/megamenu.md +239 -0
- package/skills/storefront-best-practices/reference/components/navbar.md +397 -0
- package/skills/storefront-best-practices/reference/components/popups.md +221 -0
- package/skills/storefront-best-practices/reference/components/product-card.md +125 -0
- package/skills/storefront-best-practices/reference/components/product-reviews.md +217 -0
- package/skills/storefront-best-practices/reference/components/product-slider.md +174 -0
- package/skills/storefront-best-practices/reference/components/search.md +101 -0
- package/skills/storefront-best-practices/reference/connecting-to-backend.md +391 -0
- package/skills/storefront-best-practices/reference/design.md +388 -0
- package/skills/storefront-best-practices/reference/features/promotions.md +307 -0
- package/skills/storefront-best-practices/reference/features/wishlist.md +230 -0
- package/skills/storefront-best-practices/reference/layouts/account.md +380 -0
- package/skills/storefront-best-practices/reference/layouts/cart.md +316 -0
- package/skills/storefront-best-practices/reference/layouts/checkout.md +486 -0
- package/skills/storefront-best-practices/reference/layouts/home-page.md +264 -0
- package/skills/storefront-best-practices/reference/layouts/order-confirmation.md +231 -0
- package/skills/storefront-best-practices/reference/layouts/product-details.md +527 -0
- package/skills/storefront-best-practices/reference/layouts/product-listing.md +520 -0
- package/skills/storefront-best-practices/reference/layouts/static-pages.md +356 -0
- package/skills/storefront-best-practices/reference/medusa.md +307 -0
- package/skills/storefront-best-practices/reference/mobile-responsiveness.md +183 -0
- package/skills/storefront-best-practices/reference/seo.md +195 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/handlers.ts)
|
|
4
|
+
// Registers json-render action handlers. Recipe-added views extend this file.
|
|
5
|
+
|
|
6
|
+
import { useEffect } from 'react'
|
|
7
|
+
import { useActions, useStateStore } from '@json-render/react'
|
|
8
|
+
import { useNextApp } from '@json-render/next'
|
|
9
|
+
|
|
10
|
+
export function useStorefrontHandlers() {
|
|
11
|
+
const { set } = useStateStore()
|
|
12
|
+
const { navigate } = useNextApp()
|
|
13
|
+
const { registerHandler } = useActions()
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const handlers: Record<string, () => void> = {
|
|
17
|
+
addToCart: () => {},
|
|
18
|
+
openCart: () => {},
|
|
19
|
+
openProduct: () => navigate('/productos'),
|
|
20
|
+
openCategory: () => {
|
|
21
|
+
const handle = ''
|
|
22
|
+
navigate(handle ? `/categorias/${handle}` : '/categorias')
|
|
23
|
+
},
|
|
24
|
+
goHome: () => navigate('/'),
|
|
25
|
+
applyFilters: () => {},
|
|
26
|
+
placeOrder: () => navigate('/checkout'),
|
|
27
|
+
continueShopping: () => navigate('/'),
|
|
28
|
+
viewOrder: () => {},
|
|
29
|
+
downloadInvoice: () => {},
|
|
30
|
+
submitReview: () => {},
|
|
31
|
+
toggleReviewLike: () => {},
|
|
32
|
+
newsletterSubmit: () => {},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const [name, handler] of Object.entries(handlers)) {
|
|
36
|
+
registerHandler(name, handler)
|
|
37
|
+
}
|
|
38
|
+
void set
|
|
39
|
+
// registerHandler uses functional state updates; store/navigate captured once.
|
|
40
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
|
+
}, [])
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/mappers.ts)
|
|
2
|
+
// Data adapters between the storefront's canonical shapes and the block contracts.
|
|
3
|
+
// Kept out of the views so a view module stays a pure server spec; mappers are
|
|
4
|
+
// data-only and imported where the loader builds block props.
|
|
5
|
+
|
|
6
|
+
import type { CardProduct, FeaturedProduct, ProductCategory2Category } from '@thorprovider/blocks'
|
|
7
|
+
import type { ProductCategory } from '@thorprovider/types'
|
|
8
|
+
|
|
9
|
+
export const CATEGORY_IMAGE_FALLBACK =
|
|
10
|
+
"data:image/svg+xml;charset=utf-8," +
|
|
11
|
+
encodeURIComponent(
|
|
12
|
+
`<svg xmlns='http://www.w3.org/2000/svg' width='600' height='480'><rect width='100%' height='100%' fill='#e2e8f0'/><g fill='none' stroke='#94a3b8' stroke-width='12' stroke-linecap='round'><path d='M120 240h360M300 120v240'/><path d='M200 180h50M200 240h50M200 300h50'/><path d='M350 180h50M350 240h50M350 300h50'/></g></svg>`,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
/** CardProduct → FeaturedProduct (storefront-hero), with defaults for the fields
|
|
16
|
+
* the card canon does not carry. The hero block requires every featured field
|
|
17
|
+
* (including `colors`) to be defined at render time. */
|
|
18
|
+
export function toFeaturedProduct(card: CardProduct): FeaturedProduct {
|
|
19
|
+
return {
|
|
20
|
+
id: String(card.id),
|
|
21
|
+
name: card.title,
|
|
22
|
+
brand: '',
|
|
23
|
+
price: card.price,
|
|
24
|
+
originalPrice: card.originalPrice ?? card.price,
|
|
25
|
+
rating: 0,
|
|
26
|
+
reviewCount: 0,
|
|
27
|
+
inStock: true,
|
|
28
|
+
isNew: false,
|
|
29
|
+
isOnSale: Boolean(card.originalPrice),
|
|
30
|
+
image: card.image ?? '',
|
|
31
|
+
colors: [],
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** ProductCategory (types canon) → ProductCategory2Category (block), with
|
|
36
|
+
* fallbacks for description, image, and itemCount the backend may omit. */
|
|
37
|
+
export function toCategoryCard(category: ProductCategory): ProductCategory2Category {
|
|
38
|
+
return {
|
|
39
|
+
id: category.id,
|
|
40
|
+
handle: category.handle,
|
|
41
|
+
name: category.name,
|
|
42
|
+
description: category.description ?? '',
|
|
43
|
+
itemCount: category.productCount ?? 0,
|
|
44
|
+
image: category.image ?? category.metadata?.image ?? CATEGORY_IMAGE_FALLBACK,
|
|
45
|
+
trending: false,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/registry.tsx)
|
|
4
|
+
// Base registry for the app shell plus every registered view's registry fragment.
|
|
5
|
+
|
|
6
|
+
import type { Components } from '@json-render/react'
|
|
7
|
+
import { defineRegistry, useStateStore } from '@json-render/react'
|
|
8
|
+
import { Navbar1, ProductCard1 } from '@thorprovider/blocks'
|
|
9
|
+
import { catalog } from './catalog'
|
|
10
|
+
import { useStorefrontHandlers } from './handlers'
|
|
11
|
+
import { views } from './views'
|
|
12
|
+
|
|
13
|
+
const baseComponents = {
|
|
14
|
+
Layout: ({ children }: { children?: React.ReactNode }) => {
|
|
15
|
+
useStorefrontHandlers()
|
|
16
|
+
return <div className="flex min-h-screen flex-col">{children}</div>
|
|
17
|
+
},
|
|
18
|
+
Navbar: ({ props, emit }: { props: Record<string, unknown>; emit: (event: string) => void }) => (
|
|
19
|
+
<Navbar1
|
|
20
|
+
navLinks={props.navLinks as never}
|
|
21
|
+
logoText={props.logoText as string}
|
|
22
|
+
logoHref={props.logoHref as string}
|
|
23
|
+
currentPath={props.currentPath as string}
|
|
24
|
+
signIn={props.signIn as never}
|
|
25
|
+
cta={props.cta as never}
|
|
26
|
+
cartCount={props.cartCount as number}
|
|
27
|
+
onOpenCart={() => emit('openCart')}
|
|
28
|
+
/>
|
|
29
|
+
),
|
|
30
|
+
ProductCard: ({
|
|
31
|
+
props,
|
|
32
|
+
emit,
|
|
33
|
+
}: {
|
|
34
|
+
props: Record<string, unknown>
|
|
35
|
+
emit: (event: string) => void
|
|
36
|
+
}) => {
|
|
37
|
+
const { set } = useStateStore()
|
|
38
|
+
const select = (product: unknown) => {
|
|
39
|
+
set('/lastClicked', product)
|
|
40
|
+
}
|
|
41
|
+
return (
|
|
42
|
+
<ProductCard1
|
|
43
|
+
products={props.products as never}
|
|
44
|
+
heading={props.heading as string}
|
|
45
|
+
maxItems={props.maxItems as number}
|
|
46
|
+
onAddToCart={(product) => {
|
|
47
|
+
select(product)
|
|
48
|
+
emit('addToCart')
|
|
49
|
+
}}
|
|
50
|
+
onProductClick={(product) => {
|
|
51
|
+
select(product)
|
|
52
|
+
emit('openProduct')
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
},
|
|
57
|
+
Footer: ({ props }: { props: Record<string, unknown> }) => {
|
|
58
|
+
const brandName = props.brandName as string
|
|
59
|
+
const brandDescription = props.brandDescription as string | undefined
|
|
60
|
+
const linkColumns =
|
|
61
|
+
(props.linkColumns as { title: string; links: { label: string; href?: string }[] }[]) ?? []
|
|
62
|
+
return (
|
|
63
|
+
<footer className="mt-auto border-t bg-background">
|
|
64
|
+
<div className="mx-auto grid max-w-6xl gap-8 px-4 py-12 sm:grid-cols-2 lg:grid-cols-4">
|
|
65
|
+
<div className="space-y-2">
|
|
66
|
+
<p className="text-lg font-semibold">{brandName}</p>
|
|
67
|
+
{brandDescription ? (
|
|
68
|
+
<p className="text-sm text-muted-foreground">{brandDescription}</p>
|
|
69
|
+
) : null}
|
|
70
|
+
</div>
|
|
71
|
+
{linkColumns.map((column) => (
|
|
72
|
+
<div key={column.title} className="space-y-2">
|
|
73
|
+
<p className="text-sm font-medium">{column.title}</p>
|
|
74
|
+
<ul className="space-y-1">
|
|
75
|
+
{column.links.map((link) => (
|
|
76
|
+
<li key={link.label}>
|
|
77
|
+
<a className="text-sm text-muted-foreground hover:text-foreground" href={link.href ?? '#'}>
|
|
78
|
+
{link.label}
|
|
79
|
+
</a>
|
|
80
|
+
</li>
|
|
81
|
+
))}
|
|
82
|
+
</ul>
|
|
83
|
+
</div>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
</footer>
|
|
87
|
+
)
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const viewComponents = Object.assign({}, ...views.map((view) => view.registry))
|
|
92
|
+
|
|
93
|
+
export const { registry } = defineRegistry(catalog, {
|
|
94
|
+
components: {
|
|
95
|
+
...baseComponents,
|
|
96
|
+
...viewComponents,
|
|
97
|
+
} as unknown as Components<typeof catalog>,
|
|
98
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/runtime.ts)
|
|
2
|
+
// Wires the json-render app to the Medusa backend via @thorprovider/adapters
|
|
3
|
+
// (through @thorprovider/runtime/commerce). Loaders come from each view module.
|
|
4
|
+
|
|
5
|
+
import type { LoaderFn } from '@json-render/next'
|
|
6
|
+
import { createStorefrontApp } from '@thorprovider/runtime/render-core/app'
|
|
7
|
+
import { __STOREFRONT_CAMEL__Spec } from './spec'
|
|
8
|
+
import { views } from './views'
|
|
9
|
+
|
|
10
|
+
const loaders: Record<string, LoaderFn> = Object.assign({}, ...views.map((view) => view.loader))
|
|
11
|
+
|
|
12
|
+
export const __STOREFRONT_CAMEL__App = createStorefrontApp({
|
|
13
|
+
spec: __STOREFRONT_CAMEL__Spec,
|
|
14
|
+
loaders,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export type { LoaderFn }
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/spec/index.ts)
|
|
2
|
+
// Assembles the json-render app spec from the registered views.
|
|
3
|
+
// Routes are derived from the view barrel; adding a view never edits this file.
|
|
4
|
+
|
|
5
|
+
import type { NextAppSpec } from '@json-render/next'
|
|
6
|
+
import { views } from '../views'
|
|
7
|
+
import type { StorefrontElement } from './types'
|
|
8
|
+
|
|
9
|
+
export type { StorefrontElement }
|
|
10
|
+
|
|
11
|
+
const routes = Object.fromEntries(
|
|
12
|
+
views.map((view) => [
|
|
13
|
+
view.route.path,
|
|
14
|
+
{
|
|
15
|
+
layout: view.route.layout,
|
|
16
|
+
loader: view.route.loaderName,
|
|
17
|
+
metadata: view.route.metadata,
|
|
18
|
+
...view.route.entry,
|
|
19
|
+
},
|
|
20
|
+
]),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
export const __STOREFRONT_CAMEL__Spec: NextAppSpec = {
|
|
24
|
+
metadata: {
|
|
25
|
+
title: {
|
|
26
|
+
default: '__SITE_NAME__',
|
|
27
|
+
template: '%s · __SITE_NAME__',
|
|
28
|
+
},
|
|
29
|
+
description: 'Tienda online de __SITE_NAME__.',
|
|
30
|
+
},
|
|
31
|
+
state: {},
|
|
32
|
+
layouts: {
|
|
33
|
+
default: {
|
|
34
|
+
root: 'layout',
|
|
35
|
+
elements: {
|
|
36
|
+
layout: {
|
|
37
|
+
type: 'Layout',
|
|
38
|
+
props: {},
|
|
39
|
+
children: ['slot'],
|
|
40
|
+
},
|
|
41
|
+
// The page content is injected at the Slot element by @json-render/next.
|
|
42
|
+
// Without it the rendered layout drops the page and the store looks empty.
|
|
43
|
+
slot: { type: 'Slot', props: {}, children: [] },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
routes: routes as NextAppSpec['routes'],
|
|
48
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts)
|
|
2
|
+
// Builds a route's page tree. Shared by every view module so each can assemble
|
|
3
|
+
// its own route entry without importing the shell spec (avoids a cycle).
|
|
4
|
+
|
|
5
|
+
import type { StorefrontElement } from './types'
|
|
6
|
+
|
|
7
|
+
export interface PageRoot {
|
|
8
|
+
page: {
|
|
9
|
+
root: string
|
|
10
|
+
elements: Record<string, StorefrontElement>
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const pageRoot = (
|
|
15
|
+
id: string,
|
|
16
|
+
children: string[],
|
|
17
|
+
elements: Record<string, StorefrontElement>,
|
|
18
|
+
): PageRoot => ({
|
|
19
|
+
page: {
|
|
20
|
+
root: id,
|
|
21
|
+
elements: {
|
|
22
|
+
[id]: { type: 'Layout', props: {}, children },
|
|
23
|
+
...elements,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/spec/types.ts)
|
|
2
|
+
// Shared element contract for generated view specs.
|
|
3
|
+
|
|
4
|
+
import type { VisibilityCondition } from '@json-render/core'
|
|
5
|
+
|
|
6
|
+
export interface StorefrontElement {
|
|
7
|
+
type: string
|
|
8
|
+
props: Record<string, unknown>
|
|
9
|
+
children?: string[]
|
|
10
|
+
slots?: Record<string, string[]>
|
|
11
|
+
visible?: VisibilityCondition
|
|
12
|
+
repeat?: { statePath: string; key?: string }
|
|
13
|
+
on?: Record<string, { action: string }>
|
|
14
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/state.ts)
|
|
2
|
+
// Initial storefront state. Extended by recipes as views are added.
|
|
3
|
+
|
|
4
|
+
import type { CardProduct, ProductCategory2Category } from '@thorprovider/blocks'
|
|
5
|
+
|
|
6
|
+
export interface StoreOverviewProduct {
|
|
7
|
+
id: string
|
|
8
|
+
brand: string
|
|
9
|
+
name: string
|
|
10
|
+
description: string
|
|
11
|
+
price: number
|
|
12
|
+
currency: string
|
|
13
|
+
rating: number
|
|
14
|
+
images: { id: number; src: string; alt: string }[]
|
|
15
|
+
sizes: number[]
|
|
16
|
+
colors: { name: string; value: string }[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface StorefrontState {
|
|
20
|
+
heroProducts: CardProduct[]
|
|
21
|
+
categories: ProductCategory2Category[]
|
|
22
|
+
selectedProduct: StoreOverviewProduct | null
|
|
23
|
+
lastClicked: CardProduct | null
|
|
24
|
+
lastCategory: string | null
|
|
25
|
+
bannerDismissed: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const storefrontInitialState: StorefrontState = {
|
|
29
|
+
heroProducts: [],
|
|
30
|
+
categories: [],
|
|
31
|
+
selectedProduct: null,
|
|
32
|
+
lastClicked: null,
|
|
33
|
+
lastCategory: null,
|
|
34
|
+
bannerDismissed: false,
|
|
35
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/views/index.ts)
|
|
2
|
+
// View registry (barrel). A freshly scaffolded store registers only `home`.
|
|
3
|
+
// Adding a view appends one import and one entry here; shared files never change.
|
|
4
|
+
|
|
5
|
+
import type { ViewModule } from './types'
|
|
6
|
+
|
|
7
|
+
export const views: ViewModule[] = []
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Optional section fragment: faq (mirrors recipes/sections.json)
|
|
2
|
+
|
|
3
|
+
import { FaqBlock1 as FaqBlock } from '@thorprovider/blocks'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
export const faqSection = {
|
|
7
|
+
elementType: 'Faq',
|
|
8
|
+
catalog: {
|
|
9
|
+
Faq: {
|
|
10
|
+
props: z.object({
|
|
11
|
+
title: z.string().optional(),
|
|
12
|
+
description: z.string().optional(),
|
|
13
|
+
items: z.array(z.record(z.string(), z.unknown())),
|
|
14
|
+
accordionType: z.enum(['single', 'multiple']).optional(),
|
|
15
|
+
}),
|
|
16
|
+
description: 'Frequently asked questions accordion.',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
registry: {
|
|
20
|
+
Faq: ({ props }: { props: Record<string, unknown> }) => (
|
|
21
|
+
<FaqBlock
|
|
22
|
+
title={(props.title as string) ?? 'Preguntas frecuentes'}
|
|
23
|
+
description={props.description as string}
|
|
24
|
+
items={props.items as never}
|
|
25
|
+
accordionType={(props.accordionType as 'single' | 'multiple') ?? 'single'}
|
|
26
|
+
/>
|
|
27
|
+
),
|
|
28
|
+
},
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Optional section fragment: logoCloud (mirrors recipes/sections.json)
|
|
2
|
+
|
|
3
|
+
import { LogoCloud1 as LogoCloudBlock } from '@thorprovider/blocks'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
export const logoCloudSection = {
|
|
7
|
+
elementType: 'LogoCloud',
|
|
8
|
+
catalog: {
|
|
9
|
+
LogoCloud: {
|
|
10
|
+
props: z.object({
|
|
11
|
+
title: z.string().optional(),
|
|
12
|
+
description: z.string().optional(),
|
|
13
|
+
moreLabel: z.string().optional(),
|
|
14
|
+
platforms: z.array(z.record(z.string(), z.unknown())),
|
|
15
|
+
}),
|
|
16
|
+
description: 'Brand or partner logo strip.',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
registry: {
|
|
20
|
+
LogoCloud: ({ props }: { props: Record<string, unknown> }) => (
|
|
21
|
+
<LogoCloudBlock
|
|
22
|
+
title={(props.title as string) ?? 'Marcas que confían en nosotros'}
|
|
23
|
+
description={props.description as string}
|
|
24
|
+
moreLabel={props.moreLabel as string}
|
|
25
|
+
platforms={props.platforms as never}
|
|
26
|
+
/>
|
|
27
|
+
),
|
|
28
|
+
},
|
|
29
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Optional section fragment: pricing (mirrors recipes/sections.json)
|
|
2
|
+
|
|
3
|
+
import { PricingSection1 as PricingBlock } from '@thorprovider/blocks'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
export const pricingSection = {
|
|
7
|
+
elementType: 'Pricing',
|
|
8
|
+
catalog: {
|
|
9
|
+
Pricing: {
|
|
10
|
+
props: z.object({
|
|
11
|
+
sectionTitle: z.string().optional(),
|
|
12
|
+
sectionLabel: z.string().optional(),
|
|
13
|
+
currencySymbol: z.string().optional(),
|
|
14
|
+
popularBadgeLabel: z.string().optional(),
|
|
15
|
+
getStartedLabel: z.string().optional(),
|
|
16
|
+
tiers: z.array(z.record(z.string(), z.unknown())),
|
|
17
|
+
}),
|
|
18
|
+
description: 'Pricing tiers or plans.',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
registry: {
|
|
22
|
+
Pricing: ({ props }: { props: Record<string, unknown> }) => (
|
|
23
|
+
<PricingBlock
|
|
24
|
+
sectionTitle={(props.sectionTitle as string) ?? 'Planes y precios'}
|
|
25
|
+
sectionLabel={props.sectionLabel as string}
|
|
26
|
+
currencySymbol={props.currencySymbol as string}
|
|
27
|
+
popularBadgeLabel={(props.popularBadgeLabel as string) ?? 'Popular'}
|
|
28
|
+
getStartedLabel={(props.getStartedLabel as string) ?? 'Empezar'}
|
|
29
|
+
tiers={props.tiers as never}
|
|
30
|
+
/>
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Optional section fragment: statistics (mirrors recipes/sections.json)
|
|
2
|
+
|
|
3
|
+
import { Statistic1 as StatisticBlock } from '@thorprovider/blocks'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
export const statisticsSection = {
|
|
7
|
+
elementType: 'Statistics',
|
|
8
|
+
catalog: {
|
|
9
|
+
Statistics: {
|
|
10
|
+
props: z.object({
|
|
11
|
+
title: z.string().optional(),
|
|
12
|
+
badge: z.string().optional(),
|
|
13
|
+
description: z.string().optional(),
|
|
14
|
+
items: z.array(z.record(z.string(), z.unknown())),
|
|
15
|
+
}),
|
|
16
|
+
description: 'Key metrics or trust statistics.',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
registry: {
|
|
20
|
+
Statistics: ({ props }: { props: Record<string, unknown> }) => (
|
|
21
|
+
<StatisticBlock
|
|
22
|
+
badge={props.badge as string}
|
|
23
|
+
title={(props.title as string) ?? 'Números que nos respaldan'}
|
|
24
|
+
description={props.description as string}
|
|
25
|
+
items={props.items as never}
|
|
26
|
+
/>
|
|
27
|
+
),
|
|
28
|
+
},
|
|
29
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Optional section fragment: testimonials (mirrors recipes/sections.json)
|
|
2
|
+
|
|
3
|
+
import { Testimonial1 as TestimonialBlock } from '@thorprovider/blocks'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
export const testimonialsSection = {
|
|
7
|
+
elementType: 'Testimonials',
|
|
8
|
+
catalog: {
|
|
9
|
+
Testimonials: {
|
|
10
|
+
props: z.object({
|
|
11
|
+
title: z.string().optional(),
|
|
12
|
+
items: z.array(z.record(z.string(), z.unknown())),
|
|
13
|
+
}),
|
|
14
|
+
description: 'Customer testimonials or social proof.',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
registry: {
|
|
18
|
+
Testimonials: ({ props }: { props: Record<string, unknown> }) => (
|
|
19
|
+
<TestimonialBlock
|
|
20
|
+
title={(props.title as string) ?? 'Lo que dicen nuestros clientes'}
|
|
21
|
+
items={props.items as never}
|
|
22
|
+
prevLabel={(props.prevLabel as string) ?? 'Anterior'}
|
|
23
|
+
nextLabel={(props.nextLabel as string) ?? 'Siguiente'}
|
|
24
|
+
slideAriaLabel={(props.slideAriaLabel as string) ?? 'Testimonio'}
|
|
25
|
+
/>
|
|
26
|
+
),
|
|
27
|
+
},
|
|
28
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/lib/__STOREFRONT__/views/types.ts)
|
|
2
|
+
// Contract every view module under views/<view>/ exports. Shared files
|
|
3
|
+
// (catalog.ts, registry.tsx, runtime.ts, spec/index.ts) compose the registered
|
|
4
|
+
// views; they never reference an individual view by name.
|
|
5
|
+
|
|
6
|
+
import type { ReactNode } from 'react'
|
|
7
|
+
import type { BaseComponentProps } from '@json-render/react'
|
|
8
|
+
import type { LoaderFn } from '@json-render/next'
|
|
9
|
+
import type { ZodType } from 'zod'
|
|
10
|
+
import type { StorefrontElement } from '../spec/types'
|
|
11
|
+
|
|
12
|
+
export interface ViewCatalogEntry {
|
|
13
|
+
props: ZodType
|
|
14
|
+
slots?: readonly string[]
|
|
15
|
+
description?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ViewRoute {
|
|
19
|
+
path: string
|
|
20
|
+
loaderName: string
|
|
21
|
+
layout: string
|
|
22
|
+
metadata: { title: string; description?: string }
|
|
23
|
+
entry: { page: { root: string; elements: Record<string, StorefrontElement> } }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A view's component render functions, keyed by element `type`. Each receives
|
|
28
|
+
* `BaseComponentProps` ({ props, emit }) — the shape `defineRegistry` passes to
|
|
29
|
+
* component functions. This fragment MUST be defined in a `'use client'` module
|
|
30
|
+
* (see `views/<view>/registry.tsx`): `@json-render/react` calls `createContext`
|
|
31
|
+
* at module scope and has no `'use client'`, so importing it as a value from a
|
|
32
|
+
* Server Component module breaks the RSC graph (`createContext is not a function`).
|
|
33
|
+
*/
|
|
34
|
+
export type ViewRegistry = Record<
|
|
35
|
+
string,
|
|
36
|
+
(ctx: BaseComponentProps<Record<string, unknown>>) => ReactNode
|
|
37
|
+
>
|
|
38
|
+
|
|
39
|
+
export interface ViewModule {
|
|
40
|
+
name: string
|
|
41
|
+
elements: Record<string, StorefrontElement>
|
|
42
|
+
catalog: Record<string, ViewCatalogEntry>
|
|
43
|
+
registry: ViewRegistry
|
|
44
|
+
route: ViewRoute
|
|
45
|
+
loader: Record<string, LoaderFn>
|
|
46
|
+
}
|