cms-renderer 0.0.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/.turbo/turbo-check-types.log +2 -0
- package/README.md +362 -0
- package/lib/__tests__/enrich-block-images.test.ts +394 -0
- package/lib/block-renderer.tsx +60 -0
- package/lib/cms-api.ts +86 -0
- package/lib/data-utils.ts +572 -0
- package/lib/image/lazy-load.ts +209 -0
- package/lib/markdown-utils.ts +368 -0
- package/lib/renderer.tsx +189 -0
- package/lib/result.ts +450 -0
- package/lib/schema.ts +74 -0
- package/lib/trpc.ts +28 -0
- package/lib/types.ts +201 -0
- package/next.config.ts +39 -0
- package/package.json +61 -0
- package/postcss.config.mjs +5 -0
- package/tsconfig.json +12 -0
- package/tsconfig.tsbuildinfo +1 -0
package/lib/trpc.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tRPC Client Setup
|
|
3
|
+
*
|
|
4
|
+
* Creates the tRPC React hooks for client components.
|
|
5
|
+
* Uses tRPC v11 patterns with TanStack Query v5.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AppRouter } from '@repo/cms-schema/trpc';
|
|
9
|
+
import { type CreateTRPCReact, createTRPCReact } from '@trpc/react-query';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* tRPC React client.
|
|
13
|
+
*
|
|
14
|
+
* Use this in client components to call tRPC procedures.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* 'use client';
|
|
19
|
+
* import { trpc } from '@/lib/trpc';
|
|
20
|
+
*
|
|
21
|
+
* function NavigationClient() {
|
|
22
|
+
* const { data, isLoading } = trpc.blocks.getNavigation.useQuery();
|
|
23
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
24
|
+
* return <nav>{data?.links.map(link => ...)}</nav>;
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const trpc: CreateTRPCReact<AppRouter, unknown> = createTRPCReact<AppRouter>();
|
package/lib/types.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block Rendering Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the ComponentMap pattern.
|
|
5
|
+
* Block types map to React components via the blockComponents registry.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ComponentType } from 'react';
|
|
9
|
+
|
|
10
|
+
// -----------------------------------------------------------------------------
|
|
11
|
+
// Block Type Discriminant
|
|
12
|
+
// -----------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Valid block type strings.
|
|
16
|
+
* Must match the schema_name field in block data from the tRPC API.
|
|
17
|
+
*/
|
|
18
|
+
export type BlockType =
|
|
19
|
+
| 'navigation'
|
|
20
|
+
| 'header'
|
|
21
|
+
| 'article'
|
|
22
|
+
| 'hero-block'
|
|
23
|
+
| 'features-block'
|
|
24
|
+
| 'cta-block'
|
|
25
|
+
| 'logo-trust-block';
|
|
26
|
+
|
|
27
|
+
// -----------------------------------------------------------------------------
|
|
28
|
+
// Content Types (inferred from seed data)
|
|
29
|
+
// -----------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Navigation link button structure.
|
|
33
|
+
*/
|
|
34
|
+
export interface NavigationButton {
|
|
35
|
+
label: string;
|
|
36
|
+
href: string;
|
|
37
|
+
ariaLabel: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Navigation link with type and button.
|
|
42
|
+
*/
|
|
43
|
+
export interface NavigationLink {
|
|
44
|
+
button: NavigationButton;
|
|
45
|
+
type: 'Default' | 'Flyout';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Level 3 navigation item (leaf node).
|
|
50
|
+
*/
|
|
51
|
+
export interface Level3Link {
|
|
52
|
+
link: NavigationLink;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Level 2 navigation item with optional Level 3 children.
|
|
57
|
+
*/
|
|
58
|
+
export interface Level2Link {
|
|
59
|
+
link: NavigationLink;
|
|
60
|
+
children?: Level3Link[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Level 1 navigation item with optional Level 2 children.
|
|
65
|
+
*/
|
|
66
|
+
export interface Level1Link {
|
|
67
|
+
link: NavigationLink;
|
|
68
|
+
children?: Level2Link[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Navigation block content.
|
|
73
|
+
*/
|
|
74
|
+
export interface NavigationContent {
|
|
75
|
+
logo?: {
|
|
76
|
+
url: string;
|
|
77
|
+
alt: string;
|
|
78
|
+
};
|
|
79
|
+
ariaLabel: string;
|
|
80
|
+
links: Level1Link[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Header block content.
|
|
85
|
+
*/
|
|
86
|
+
export interface HeaderContent {
|
|
87
|
+
headline: string;
|
|
88
|
+
subheadline?: string;
|
|
89
|
+
backgroundImage?: {
|
|
90
|
+
url: string;
|
|
91
|
+
alt: string;
|
|
92
|
+
};
|
|
93
|
+
ctaButton?: {
|
|
94
|
+
label: string;
|
|
95
|
+
href: string;
|
|
96
|
+
};
|
|
97
|
+
alignment: 'left' | 'center' | 'right';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Article block content.
|
|
102
|
+
*/
|
|
103
|
+
export interface ArticleContent {
|
|
104
|
+
headline: string;
|
|
105
|
+
author?: string;
|
|
106
|
+
publishedAt?: string;
|
|
107
|
+
body: string;
|
|
108
|
+
tags?: readonly string[] | string[];
|
|
109
|
+
status?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Hero block content (from CMS schema).
|
|
114
|
+
*/
|
|
115
|
+
export type HeroBlockContent = import('@repo/cms-schema/blocks').HeroBlockContent;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Features block content (from CMS schema).
|
|
119
|
+
*/
|
|
120
|
+
export type FeaturesBlockContent = import('@repo/cms-schema/blocks').FeaturesBlockContent;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* CTA block content (from CMS schema).
|
|
124
|
+
*/
|
|
125
|
+
export type CTABlockContent = import('@repo/cms-schema/blocks').CTABlockContent;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Logo Trust block content (from CMS schema).
|
|
129
|
+
*/
|
|
130
|
+
export type LogoTrustBlockContent = import('@repo/cms-schema/blocks').LogoTrustBlockContent;
|
|
131
|
+
|
|
132
|
+
// -----------------------------------------------------------------------------
|
|
133
|
+
// Block Data Union
|
|
134
|
+
// -----------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Discriminated union of all block types.
|
|
138
|
+
* Use the `type` field to narrow to specific content types.
|
|
139
|
+
*
|
|
140
|
+
* Each block also carries its stable CMS `id`, which should be used as the
|
|
141
|
+
* React `key` when rendering lists of blocks.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```tsx
|
|
145
|
+
* function renderBlock(block: BlockData) {
|
|
146
|
+
* if (block.type === 'header') {
|
|
147
|
+
* // TypeScript knows block.content is HeaderContent
|
|
148
|
+
* return <h1>{block.content.headline}</h1>;
|
|
149
|
+
* }
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export type BlockData =
|
|
154
|
+
| { id: string; type: 'navigation'; content: NavigationContent }
|
|
155
|
+
| { id: string; type: 'header'; content: HeaderContent }
|
|
156
|
+
| { id: string; type: 'article'; content: ArticleContent }
|
|
157
|
+
| { id: string; type: 'hero-block'; content: HeroBlockContent }
|
|
158
|
+
| { id: string; type: 'features-block'; content: FeaturesBlockContent }
|
|
159
|
+
| { id: string; type: 'cta-block'; content: CTABlockContent }
|
|
160
|
+
| { id: string; type: 'logo-trust-block'; content: LogoTrustBlockContent };
|
|
161
|
+
|
|
162
|
+
// -----------------------------------------------------------------------------
|
|
163
|
+
// Component Types
|
|
164
|
+
// -----------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Props for a block component.
|
|
168
|
+
* Each block component receives its typed content.
|
|
169
|
+
*/
|
|
170
|
+
export interface BlockComponentProps<T> {
|
|
171
|
+
content: T;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* A React component that renders a specific block type.
|
|
176
|
+
*/
|
|
177
|
+
export type BlockComponent<T> = ComponentType<BlockComponentProps<T>>;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Registry of block type to component mappings.
|
|
181
|
+
* This is the core of the ComponentMap pattern.
|
|
182
|
+
*/
|
|
183
|
+
export type BlockComponentRegistry = {
|
|
184
|
+
[K in BlockType]: BlockComponent<
|
|
185
|
+
K extends 'navigation'
|
|
186
|
+
? NavigationContent
|
|
187
|
+
: K extends 'header'
|
|
188
|
+
? HeaderContent
|
|
189
|
+
: K extends 'article'
|
|
190
|
+
? ArticleContent
|
|
191
|
+
: K extends 'hero-block'
|
|
192
|
+
? HeroBlockContent
|
|
193
|
+
: K extends 'features-block'
|
|
194
|
+
? FeaturesBlockContent
|
|
195
|
+
: K extends 'cta-block'
|
|
196
|
+
? CTABlockContent
|
|
197
|
+
: K extends 'logo-trust-block'
|
|
198
|
+
? LogoTrustBlockContent
|
|
199
|
+
: never
|
|
200
|
+
>;
|
|
201
|
+
};
|
package/next.config.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import type { NextConfig } from 'next';
|
|
3
|
+
|
|
4
|
+
const nextConfig: NextConfig = {
|
|
5
|
+
turbopack: {
|
|
6
|
+
root: resolve(import.meta.dirname, '../..'),
|
|
7
|
+
},
|
|
8
|
+
transpilePackages: ['@repo/cms-schema', '@repo/markdown-wasm'],
|
|
9
|
+
serverExternalPackages: ['md4w'],
|
|
10
|
+
images: {
|
|
11
|
+
remotePatterns: [
|
|
12
|
+
{
|
|
13
|
+
protocol: 'https',
|
|
14
|
+
hostname: '**',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
protocol: 'http',
|
|
18
|
+
hostname: 'localhost',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
// Prevent browser caching of dynamic pages
|
|
23
|
+
async headers() {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
// Apply to all pages
|
|
27
|
+
source: '/:path*',
|
|
28
|
+
headers: [
|
|
29
|
+
{
|
|
30
|
+
key: 'Cache-Control',
|
|
31
|
+
value: 'no-store, must-revalidate',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default nextConfig;
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cms-renderer",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./components/*": "./components/*",
|
|
8
|
+
"./components/blocks/*": "./components/blocks/*",
|
|
9
|
+
"./lib/*": "./lib/*",
|
|
10
|
+
"./server/*": "./server/*",
|
|
11
|
+
"./styles/*": "./styles/*"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"lint": "biome check .",
|
|
18
|
+
"check-types": "tsc --noEmit",
|
|
19
|
+
"clean": "rm -rf .next .turbo node_modules"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@auteur/trpc-utils": "0.0.0",
|
|
23
|
+
"@repo/supabase-utils": "0.0.0",
|
|
24
|
+
"@hookform/resolvers": "^5.1.0",
|
|
25
|
+
"@lexical/code": "0.39.0",
|
|
26
|
+
"@lexical/link": "0.39.0",
|
|
27
|
+
"@lexical/list": "0.39.0",
|
|
28
|
+
"@lexical/markdown": "0.39.0",
|
|
29
|
+
"@lexical/react": "0.39.0",
|
|
30
|
+
"@lexical/rich-text": "0.39.0",
|
|
31
|
+
"@lexical/selection": "0.39.0",
|
|
32
|
+
"@lexical/utils": "0.39.0",
|
|
33
|
+
"@repo/cms-schema": "0.0.0",
|
|
34
|
+
"@repo/markdown-wasm": "0.0.0",
|
|
35
|
+
"@supabase/ssr": "0.8.0",
|
|
36
|
+
"@supabase/supabase-js": "2.90.1",
|
|
37
|
+
"@tanstack/react-query": "5.90.16",
|
|
38
|
+
"@testing-library/react": "16.3.1",
|
|
39
|
+
"@trpc/client": "11.8.1",
|
|
40
|
+
"@trpc/react-query": "11.8.1",
|
|
41
|
+
"@trpc/server": "11.8.1",
|
|
42
|
+
"next": "^16.1.1",
|
|
43
|
+
"react": "^19.1.0",
|
|
44
|
+
"react-dom": "^19.1.0",
|
|
45
|
+
"superjson": "2.2.6",
|
|
46
|
+
"zod": "4.3.5"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@happy-dom/global-registrator": "20.1.0",
|
|
50
|
+
"@repo/typescript-config": "0.0.0",
|
|
51
|
+
"@tailwindcss/postcss": "4",
|
|
52
|
+
"@testing-library/jest-dom": "6.9.1",
|
|
53
|
+
"@types/node": "^25",
|
|
54
|
+
"@types/react": "^19",
|
|
55
|
+
"@types/react-dom": "^19",
|
|
56
|
+
"chalk": "5.6.2",
|
|
57
|
+
"jest-diff": "30.2.0",
|
|
58
|
+
"tailwindcss": "4",
|
|
59
|
+
"typescript": "^5.9.3"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@repo/typescript-config/nextjs.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"strict": true,
|
|
5
|
+
"baseUrl": ".",
|
|
6
|
+
"paths": {
|
|
7
|
+
"@/*": ["./*"]
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
|
11
|
+
"exclude": ["node_modules", "tests", "**/__tests__/**", "**/*.test.ts", "**/*.test.tsx", "e2e"]
|
|
12
|
+
}
|