@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.
Files changed (109) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +333 -0
  3. package/assets/templates/app/app/[[...slug]]/page.tsx +17 -0
  4. package/assets/templates/app/app/[[...slug]]/renderer.tsx +10 -0
  5. package/assets/templates/app/app/globals.css +101 -0
  6. package/assets/templates/app/app/layout.tsx +35 -0
  7. package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +88 -0
  8. package/assets/templates/app/lib/__STOREFRONT__/handlers.ts +42 -0
  9. package/assets/templates/app/lib/__STOREFRONT__/mappers.ts +47 -0
  10. package/assets/templates/app/lib/__STOREFRONT__/registry.tsx +98 -0
  11. package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +17 -0
  12. package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +48 -0
  13. package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +26 -0
  14. package/assets/templates/app/lib/__STOREFRONT__/spec/types.ts +14 -0
  15. package/assets/templates/app/lib/__STOREFRONT__/state.ts +35 -0
  16. package/assets/templates/app/lib/__STOREFRONT__/views/index.ts +7 -0
  17. package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.tsx +29 -0
  18. package/assets/templates/app/lib/__STOREFRONT__/views/sections/logoCloud.tsx +29 -0
  19. package/assets/templates/app/lib/__STOREFRONT__/views/sections/pricing.tsx +33 -0
  20. package/assets/templates/app/lib/__STOREFRONT__/views/sections/statistics.tsx +29 -0
  21. package/assets/templates/app/lib/__STOREFRONT__/views/sections/testimonials.tsx +28 -0
  22. package/assets/templates/app/lib/__STOREFRONT__/views/types.ts +46 -0
  23. package/assets/templates/views/home/index.tsx +142 -0
  24. package/assets/templates/views/home/registry.tsx +65 -0
  25. package/bin/storefront.js +531 -0
  26. package/commands/sf-add-view.md +26 -0
  27. package/commands/sf-theme.md +20 -0
  28. package/commands/sf-view.md +25 -0
  29. package/package.json +48 -0
  30. package/recipes/archetype.schema.json +39 -0
  31. package/recipes/archetypes.json +148 -0
  32. package/recipes/recipe.schema.json +59 -0
  33. package/recipes/recipes.json +90 -0
  34. package/recipes/sections.json +46 -0
  35. package/recipes/validate.mjs +190 -0
  36. package/scripts/render-templates.mjs +255 -0
  37. package/scripts/sync-assets.mjs +87 -0
  38. package/skills/building-storefronts/SKILL.md +178 -0
  39. package/skills/building-storefronts/references/frontend-integration.md +229 -0
  40. package/skills/json-render-core/SKILL.md +291 -0
  41. package/skills/json-render-next/SKILL.md +194 -0
  42. package/skills/json-render-react/SKILL.md +298 -0
  43. package/skills/json-render-remotion/SKILL.md +111 -0
  44. package/skills/json-render-shadcn/SKILL.md +159 -0
  45. package/skills/json-render-solid/SKILL.md +204 -0
  46. package/skills/nextjs-shadcn/SKILL.md +367 -0
  47. package/skills/nextjs-shadcn/references/architecture.md +499 -0
  48. package/skills/nextjs-shadcn/references/project-setup.md +162 -0
  49. package/skills/nextjs-shadcn/references/shadcn-platform.md +261 -0
  50. package/skills/nextjs-shadcn/references/sidebar.md +274 -0
  51. package/skills/nextjs-shadcn/references/styling.md +555 -0
  52. package/skills/sf-theme-gen/SKILL.md +61 -0
  53. package/skills/sf-view-gen/SKILL.md +116 -0
  54. package/skills/sf-view-gen/assets/recipes/archetypes.json +148 -0
  55. package/skills/sf-view-gen/assets/recipes/recipes.json +90 -0
  56. package/skills/sf-view-gen/assets/recipes/sections.json +46 -0
  57. package/skills/sf-view-gen/assets/templates/views/category/index.tsx +86 -0
  58. package/skills/sf-view-gen/assets/templates/views/category/registry.tsx +48 -0
  59. package/skills/sf-view-gen/assets/templates/views/checkout/index.tsx +192 -0
  60. package/skills/sf-view-gen/assets/templates/views/checkout/registry.tsx +45 -0
  61. package/skills/sf-view-gen/assets/templates/views/product/index.tsx +122 -0
  62. package/skills/sf-view-gen/assets/templates/views/product/registry.tsx +49 -0
  63. package/skills/sf-view-gen/assets/templates/views/sections/faq.client.tsx +20 -0
  64. package/skills/sf-view-gen/assets/templates/views/sections/faq.tsx +21 -0
  65. package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.client.tsx +18 -0
  66. package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.tsx +21 -0
  67. package/skills/sf-view-gen/assets/templates/views/sections/pricing.client.tsx +20 -0
  68. package/skills/sf-view-gen/assets/templates/views/sections/pricing.tsx +23 -0
  69. package/skills/sf-view-gen/assets/templates/views/sections/statistics.client.tsx +18 -0
  70. package/skills/sf-view-gen/assets/templates/views/sections/statistics.tsx +21 -0
  71. package/skills/sf-view-gen/assets/templates/views/sections/testimonials.client.tsx +19 -0
  72. package/skills/sf-view-gen/assets/templates/views/sections/testimonials.tsx +19 -0
  73. package/skills/sf-view-gen/assets/templates/views/user-panel/index.tsx +168 -0
  74. package/skills/sf-view-gen/assets/templates/views/user-panel/registry.tsx +49 -0
  75. package/skills/sf-view-gen/references/views.md +69 -0
  76. package/skills/sf-view-gen/scripts/validate.mjs +190 -0
  77. package/skills/shadcn-component-discovery/SKILL.md +273 -0
  78. package/skills/shadcn-component-discovery/references/registries.md +226 -0
  79. package/skills/shadcn-theming/SKILL.md +104 -0
  80. package/skills/shadcn-theming/references/templates/theme-setup.md +109 -0
  81. package/skills/shadcn-theming/references/theming-guide.md +90 -0
  82. package/skills/storefront-best-practices/SKILL.md +421 -0
  83. package/skills/storefront-best-practices/reference/components/breadcrumbs.md +123 -0
  84. package/skills/storefront-best-practices/reference/components/cart-popup.md +189 -0
  85. package/skills/storefront-best-practices/reference/components/country-selector.md +298 -0
  86. package/skills/storefront-best-practices/reference/components/footer.md +112 -0
  87. package/skills/storefront-best-practices/reference/components/hero.md +241 -0
  88. package/skills/storefront-best-practices/reference/components/megamenu.md +239 -0
  89. package/skills/storefront-best-practices/reference/components/navbar.md +397 -0
  90. package/skills/storefront-best-practices/reference/components/popups.md +221 -0
  91. package/skills/storefront-best-practices/reference/components/product-card.md +125 -0
  92. package/skills/storefront-best-practices/reference/components/product-reviews.md +217 -0
  93. package/skills/storefront-best-practices/reference/components/product-slider.md +174 -0
  94. package/skills/storefront-best-practices/reference/components/search.md +101 -0
  95. package/skills/storefront-best-practices/reference/connecting-to-backend.md +391 -0
  96. package/skills/storefront-best-practices/reference/design.md +388 -0
  97. package/skills/storefront-best-practices/reference/features/promotions.md +307 -0
  98. package/skills/storefront-best-practices/reference/features/wishlist.md +230 -0
  99. package/skills/storefront-best-practices/reference/layouts/account.md +380 -0
  100. package/skills/storefront-best-practices/reference/layouts/cart.md +316 -0
  101. package/skills/storefront-best-practices/reference/layouts/checkout.md +486 -0
  102. package/skills/storefront-best-practices/reference/layouts/home-page.md +264 -0
  103. package/skills/storefront-best-practices/reference/layouts/order-confirmation.md +231 -0
  104. package/skills/storefront-best-practices/reference/layouts/product-details.md +527 -0
  105. package/skills/storefront-best-practices/reference/layouts/product-listing.md +520 -0
  106. package/skills/storefront-best-practices/reference/layouts/static-pages.md +356 -0
  107. package/skills/storefront-best-practices/reference/medusa.md +307 -0
  108. package/skills/storefront-best-practices/reference/mobile-responsiveness.md +183 -0
  109. 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
+ }