@ecomiq/storefront 2.0.7 → 2.0.8
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/package.json +2 -1
- package/src/codegen.mjs +6 -1
- package/src/pages/pages.$pageSlug.tsx +74 -0
- package/src/server.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecomiq/storefront",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "Micro framework SSR de Ecomiq para storefronts. Páginas, datos y deploy sin configurar nada.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"./internal/checkout": "./src/pages/checkout.tsx",
|
|
42
42
|
"./internal/checkout-session": "./src/pages/checkout.$checkoutId.tsx",
|
|
43
43
|
"./internal/thank-you": "./src/pages/thank-you.tsx",
|
|
44
|
+
"./internal/cms-page": "./src/pages/pages.$pageSlug.tsx",
|
|
44
45
|
"./package.json": "./package.json"
|
|
45
46
|
},
|
|
46
47
|
"files": [
|
package/src/codegen.mjs
CHANGED
|
@@ -57,6 +57,11 @@ const FRAMEWORK_LOCKED_PAGES = [
|
|
|
57
57
|
routeId: '/thank-you',
|
|
58
58
|
importFrom: '@ecomiq/storefront/internal/thank-you',
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
rel: 'pages.$pageSlug.tsx',
|
|
62
|
+
routeId: '/pages/$pageSlug',
|
|
63
|
+
importFrom: '@ecomiq/storefront/internal/cms-page',
|
|
64
|
+
},
|
|
60
65
|
]
|
|
61
66
|
|
|
62
67
|
export async function generateApp({ cwd, appDir, frameworkRoot, config }) {
|
|
@@ -487,7 +492,7 @@ function assertNoFrameworkPageOverrides(pageEntries) {
|
|
|
487
492
|
if (!reserved.has(routeId)) continue
|
|
488
493
|
throw new Error(
|
|
489
494
|
`La ruta ${routeId} es reservada por Ecomiq (página cerrada). ` +
|
|
490
|
-
`Elimina pages/${entry.rel};
|
|
495
|
+
`Elimina pages/${entry.rel}; vive en @ecomiq/storefront (checkout, thank-you, CMS).`,
|
|
491
496
|
)
|
|
492
497
|
}
|
|
493
498
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Página CMS cerrada (`/pages/$pageSlug`).
|
|
3
|
+
* Resuelve contenido publicado vía GET /v1/pages/{slug}.
|
|
4
|
+
*/
|
|
5
|
+
import type { CmsPageDetailsDto } from '@ecomiq/core'
|
|
6
|
+
import {
|
|
7
|
+
definePage,
|
|
8
|
+
sanitizeHtml,
|
|
9
|
+
serverApi,
|
|
10
|
+
stripHtml,
|
|
11
|
+
z,
|
|
12
|
+
} from '../index'
|
|
13
|
+
import { StorefrontError } from '../ui'
|
|
14
|
+
|
|
15
|
+
const ContentPageSettingsSchema = z.object({
|
|
16
|
+
title: z.string().optional(),
|
|
17
|
+
html: z.string().optional(),
|
|
18
|
+
seo: z.object({
|
|
19
|
+
title: z.string().optional(),
|
|
20
|
+
description: z.string().optional(),
|
|
21
|
+
}).optional(),
|
|
22
|
+
}).passthrough()
|
|
23
|
+
|
|
24
|
+
type ContentPageData = {
|
|
25
|
+
page: CmsPageDetailsDto
|
|
26
|
+
content: z.infer<typeof ContentPageSettingsSchema>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default definePage<ContentPageData, { pageSlug: string }>({
|
|
30
|
+
loader: async ({ params }) => {
|
|
31
|
+
const page = await serverApi.getPageBySlug(params.pageSlug)
|
|
32
|
+
if (page.status !== 'published') throw new Error('Página no publicada')
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
page,
|
|
36
|
+
content: ContentPageSettingsSchema.parse(page.settings ?? {}),
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
meta: ({ data }) => ({
|
|
41
|
+
title: data.content.seo?.title ?? data.content.title ?? data.page.name,
|
|
42
|
+
description: data.content.seo?.description ?? (
|
|
43
|
+
data.content.html
|
|
44
|
+
? stripHtml(data.content.html).slice(0, 155)
|
|
45
|
+
: undefined
|
|
46
|
+
),
|
|
47
|
+
}),
|
|
48
|
+
|
|
49
|
+
error: () => (
|
|
50
|
+
<StorefrontError
|
|
51
|
+
title="Página no disponible"
|
|
52
|
+
description="El contenido no existe o todavía no está publicado."
|
|
53
|
+
/>
|
|
54
|
+
),
|
|
55
|
+
|
|
56
|
+
component: ({ data }) => (
|
|
57
|
+
<article className="mx-auto w-full max-w-4xl px-4 py-10 sm:py-14">
|
|
58
|
+
<header className="mb-8 border-b pb-8">
|
|
59
|
+
<h1 className="text-balance text-3xl font-semibold tracking-[-0.02em] md:text-4xl">
|
|
60
|
+
{data.content.title ?? data.page.name}
|
|
61
|
+
</h1>
|
|
62
|
+
</header>
|
|
63
|
+
|
|
64
|
+
{data.content.html ? (
|
|
65
|
+
<div
|
|
66
|
+
className="prose prose-neutral max-w-none text-foreground prose-headings:font-semibold prose-a:text-primary"
|
|
67
|
+
dangerouslySetInnerHTML={{ __html: sanitizeHtml(data.content.html) }}
|
|
68
|
+
/>
|
|
69
|
+
) : (
|
|
70
|
+
<p className="text-muted-foreground">Contenido no disponible.</p>
|
|
71
|
+
)}
|
|
72
|
+
</article>
|
|
73
|
+
),
|
|
74
|
+
})
|
package/src/server.ts
CHANGED
|
@@ -86,6 +86,10 @@ const getCollectionBySlugFn = createServerFn({ method: 'GET' })
|
|
|
86
86
|
.validator(z.object({ slug: z.string().min(1) }))
|
|
87
87
|
.handler(({ data }) => getStorefrontClient().getCollectionBySlug(data.slug))
|
|
88
88
|
|
|
89
|
+
const getPageBySlugFn = createServerFn({ method: 'GET' })
|
|
90
|
+
.validator(z.object({ slug: z.string().min(1) }))
|
|
91
|
+
.handler(({ data }) => getStorefrontClient().getPageBySlug(data.slug))
|
|
92
|
+
|
|
89
93
|
const getProductsFn = createServerFn({ method: 'GET' })
|
|
90
94
|
.validator(ProductsParamsSchema)
|
|
91
95
|
.handler(({ data }) => getStorefrontClient().getProducts(data))
|
|
@@ -219,6 +223,7 @@ export const serverApi = {
|
|
|
219
223
|
getStoreId: () => getStoreIdFn(),
|
|
220
224
|
getCollections: () => getCollectionsFn(),
|
|
221
225
|
getCollectionBySlug: (slug: string) => getCollectionBySlugFn({ data: { slug } }),
|
|
226
|
+
getPageBySlug: (slug: string) => getPageBySlugFn({ data: { slug } }),
|
|
222
227
|
getProducts: (
|
|
223
228
|
params: ProductsParamsInput = {},
|
|
224
229
|
options: { signal?: AbortSignal } = {},
|