@agility/create-next-app 1.0.0-beta.2

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 (213) hide show
  1. package/.claude/settings.json +7 -0
  2. package/.claude/settings.local.json +24 -0
  3. package/FEATURE_ROADMAP.md +343 -0
  4. package/README.md +205 -0
  5. package/TESTING.md +131 -0
  6. package/bin/create-agility-app.js +48 -0
  7. package/dist/agility/api-keys/generateApiKeys.d.ts +9 -0
  8. package/dist/agility/api-keys/generateApiKeys.d.ts.map +1 -0
  9. package/dist/agility/api-keys/generateApiKeys.js +99 -0
  10. package/dist/agility/api-keys/generateApiKeys.js.map +1 -0
  11. package/dist/agility/api-keys/getApiKeys.d.ts +9 -0
  12. package/dist/agility/api-keys/getApiKeys.d.ts.map +1 -0
  13. package/dist/agility/api-keys/getApiKeys.js +14 -0
  14. package/dist/agility/api-keys/getApiKeys.js.map +1 -0
  15. package/dist/agility/index.d.ts +3 -0
  16. package/dist/agility/index.d.ts.map +1 -0
  17. package/dist/agility/index.js +8 -0
  18. package/dist/agility/index.js.map +1 -0
  19. package/dist/agility/instance/createNewInstance.d.ts +8 -0
  20. package/dist/agility/instance/createNewInstance.d.ts.map +1 -0
  21. package/dist/agility/instance/createNewInstance.js +65 -0
  22. package/dist/agility/instance/createNewInstance.js.map +1 -0
  23. package/dist/agility/instance/getAvailableInstances.d.ts +8 -0
  24. package/dist/agility/instance/getAvailableInstances.d.ts.map +1 -0
  25. package/dist/agility/instance/getAvailableInstances.js +43 -0
  26. package/dist/agility/instance/getAvailableInstances.js.map +1 -0
  27. package/dist/agility/instance/manageInstance.d.ts +9 -0
  28. package/dist/agility/instance/manageInstance.d.ts.map +1 -0
  29. package/dist/agility/instance/manageInstance.js +82 -0
  30. package/dist/agility/instance/manageInstance.js.map +1 -0
  31. package/dist/agility/utils/getMgmtAPIUrl.d.ts +20 -0
  32. package/dist/agility/utils/getMgmtAPIUrl.d.ts.map +1 -0
  33. package/dist/agility/utils/getMgmtAPIUrl.js +61 -0
  34. package/dist/agility/utils/getMgmtAPIUrl.js.map +1 -0
  35. package/dist/auth/api-key/authenticateWithApiKey.d.ts +6 -0
  36. package/dist/auth/api-key/authenticateWithApiKey.d.ts.map +1 -0
  37. package/dist/auth/api-key/authenticateWithApiKey.js +28 -0
  38. package/dist/auth/api-key/authenticateWithApiKey.js.map +1 -0
  39. package/dist/auth/index.d.ts +3 -0
  40. package/dist/auth/index.d.ts.map +1 -0
  41. package/dist/auth/index.js +8 -0
  42. package/dist/auth/index.js.map +1 -0
  43. package/dist/auth/oauth/authenticate.d.ts +6 -0
  44. package/dist/auth/oauth/authenticate.d.ts.map +1 -0
  45. package/dist/auth/oauth/authenticate.js +162 -0
  46. package/dist/auth/oauth/authenticate.js.map +1 -0
  47. package/dist/auth/oauth/constants.d.ts +5 -0
  48. package/dist/auth/oauth/constants.d.ts.map +1 -0
  49. package/dist/auth/oauth/constants.js +9 -0
  50. package/dist/auth/oauth/constants.js.map +1 -0
  51. package/dist/auth/oauth/exchangeCodeForToken.d.ts +7 -0
  52. package/dist/auth/oauth/exchangeCodeForToken.d.ts.map +1 -0
  53. package/dist/auth/oauth/exchangeCodeForToken.js +39 -0
  54. package/dist/auth/oauth/exchangeCodeForToken.js.map +1 -0
  55. package/dist/cli/index.d.ts +3 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +290 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/cli/promptForMissingOptions.d.ts +8 -0
  60. package/dist/cli/promptForMissingOptions.d.ts.map +1 -0
  61. package/dist/cli/promptForMissingOptions.js +92 -0
  62. package/dist/cli/promptForMissingOptions.js.map +1 -0
  63. package/dist/config/env/createEnvFile.d.ts +6 -0
  64. package/dist/config/env/createEnvFile.d.ts.map +1 -0
  65. package/dist/config/env/createEnvFile.js +31 -0
  66. package/dist/config/env/createEnvFile.js.map +1 -0
  67. package/dist/config/index.d.ts +2 -0
  68. package/dist/config/index.d.ts.map +1 -0
  69. package/dist/config/index.js +6 -0
  70. package/dist/config/index.js.map +1 -0
  71. package/dist/config/mcp/createMcpConfig.d.ts +5 -0
  72. package/dist/config/mcp/createMcpConfig.d.ts.map +1 -0
  73. package/dist/config/mcp/createMcpConfig.js +32 -0
  74. package/dist/config/mcp/createMcpConfig.js.map +1 -0
  75. package/dist/config/packages/installAgilityPackages.d.ts +6 -0
  76. package/dist/config/packages/installAgilityPackages.d.ts.map +1 -0
  77. package/dist/config/packages/installAgilityPackages.js +61 -0
  78. package/dist/config/packages/installAgilityPackages.js.map +1 -0
  79. package/dist/config/setupProject.d.ts +8 -0
  80. package/dist/config/setupProject.d.ts.map +1 -0
  81. package/dist/config/setupProject.js +32 -0
  82. package/dist/config/setupProject.js.map +1 -0
  83. package/dist/create-next-app/createNextApp.d.ts +9 -0
  84. package/dist/create-next-app/createNextApp.d.ts.map +1 -0
  85. package/dist/create-next-app/createNextApp.js +83 -0
  86. package/dist/create-next-app/createNextApp.js.map +1 -0
  87. package/dist/create-next-app/index.d.ts +3 -0
  88. package/dist/create-next-app/index.d.ts.map +1 -0
  89. package/dist/create-next-app/index.js +8 -0
  90. package/dist/create-next-app/index.js.map +1 -0
  91. package/dist/scaffold/components/createPageComponents.d.ts +6 -0
  92. package/dist/scaffold/components/createPageComponents.d.ts.map +1 -0
  93. package/dist/scaffold/components/createPageComponents.js +62 -0
  94. package/dist/scaffold/components/createPageComponents.js.map +1 -0
  95. package/dist/scaffold/containers/createContainers.d.ts +6 -0
  96. package/dist/scaffold/containers/createContainers.d.ts.map +1 -0
  97. package/dist/scaffold/containers/createContainers.js +48 -0
  98. package/dist/scaffold/containers/createContainers.js.map +1 -0
  99. package/dist/scaffold/index.d.ts +2 -0
  100. package/dist/scaffold/index.d.ts.map +1 -0
  101. package/dist/scaffold/index.js +6 -0
  102. package/dist/scaffold/index.js.map +1 -0
  103. package/dist/scaffold/instance/createBlankInstance.d.ts +8 -0
  104. package/dist/scaffold/instance/createBlankInstance.d.ts.map +1 -0
  105. package/dist/scaffold/instance/createBlankInstance.js +51 -0
  106. package/dist/scaffold/instance/createBlankInstance.js.map +1 -0
  107. package/dist/scaffold/models/createContentModels.d.ts +6 -0
  108. package/dist/scaffold/models/createContentModels.d.ts.map +1 -0
  109. package/dist/scaffold/models/createContentModels.js +70 -0
  110. package/dist/scaffold/models/createContentModels.js.map +1 -0
  111. package/dist/templates/copyDirectory.d.ts +5 -0
  112. package/dist/templates/copyDirectory.d.ts.map +1 -0
  113. package/dist/templates/copyDirectory.js +28 -0
  114. package/dist/templates/copyDirectory.js.map +1 -0
  115. package/dist/templates/copyTemplates.d.ts +8 -0
  116. package/dist/templates/copyTemplates.d.ts.map +1 -0
  117. package/dist/templates/copyTemplates.js +58 -0
  118. package/dist/templates/copyTemplates.js.map +1 -0
  119. package/dist/templates/index.d.ts +2 -0
  120. package/dist/templates/index.d.ts.map +1 -0
  121. package/dist/templates/index.js +6 -0
  122. package/dist/templates/index.js.map +1 -0
  123. package/dist/types/index.d.ts +50 -0
  124. package/dist/types/index.d.ts.map +1 -0
  125. package/dist/types/index.js +3 -0
  126. package/dist/types/index.js.map +1 -0
  127. package/dist/utils/git.d.ts +9 -0
  128. package/dist/utils/git.d.ts.map +1 -0
  129. package/dist/utils/git.js +71 -0
  130. package/dist/utils/git.js.map +1 -0
  131. package/dist/utils/validation.d.ts +45 -0
  132. package/dist/utils/validation.d.ts.map +1 -0
  133. package/dist/utils/validation.js +180 -0
  134. package/dist/utils/validation.js.map +1 -0
  135. package/package.json +45 -0
  136. package/src/agility/api-keys/generateApiKeys.ts +100 -0
  137. package/src/agility/api-keys/getApiKeys.ts +13 -0
  138. package/src/agility/index.ts +3 -0
  139. package/src/agility/instance/createNewInstance.ts +67 -0
  140. package/src/agility/instance/getAvailableInstances.ts +49 -0
  141. package/src/agility/instance/manageInstance.ts +90 -0
  142. package/src/agility/utils/getMgmtAPIUrl.ts +68 -0
  143. package/src/auth/api-key/authenticateWithApiKey.ts +24 -0
  144. package/src/auth/index.ts +3 -0
  145. package/src/auth/oauth/authenticate.ts +165 -0
  146. package/src/auth/oauth/constants.ts +6 -0
  147. package/src/auth/oauth/exchangeCodeForToken.ts +43 -0
  148. package/src/cli/index.ts +281 -0
  149. package/src/cli/promptForMissingOptions.ts +104 -0
  150. package/src/config/env/createEnvFile.ts +30 -0
  151. package/src/config/index.ts +2 -0
  152. package/src/config/mcp/createMcpConfig.ts +30 -0
  153. package/src/config/packages/installAgilityPackages.ts +63 -0
  154. package/src/config/setupProject.ts +31 -0
  155. package/src/create-next-app/createNextApp.ts +75 -0
  156. package/src/create-next-app/index.ts +3 -0
  157. package/src/scaffold/components/createPageComponents.ts +74 -0
  158. package/src/scaffold/containers/createContainers.ts +55 -0
  159. package/src/scaffold/index.ts +2 -0
  160. package/src/scaffold/instance/createBlankInstance.ts +55 -0
  161. package/src/scaffold/models/createContentModels.ts +83 -0
  162. package/src/templates/copyDirectory.ts +24 -0
  163. package/src/templates/copyTemplates.ts +57 -0
  164. package/src/templates/index.ts +2 -0
  165. package/src/types/index.ts +55 -0
  166. package/src/utils/git.ts +74 -0
  167. package/src/utils/validation.ts +184 -0
  168. package/templates/.claude/QUICK-START.md +230 -0
  169. package/templates/.claude/README.md +32 -0
  170. package/templates/.claude/settings.json +8 -0
  171. package/templates/BLANK-INSTANCE-SETUP.md +375 -0
  172. package/templates/DEVELOPMENT.md +160 -0
  173. package/templates/EXAMPLE-PROMPTS.md +643 -0
  174. package/templates/PROMPTS.md +410 -0
  175. package/templates/README.md +281 -0
  176. package/templates/agents.md +429 -0
  177. package/templates/app/[locale]/[...slug]/error.tsx +17 -0
  178. package/templates/app/[locale]/[...slug]/not-found.tsx +9 -0
  179. package/templates/app/[locale]/[...slug]/page.tsx +102 -0
  180. package/templates/app/[locale]/layout.tsx +22 -0
  181. package/templates/app/[locale]/page.tsx +12 -0
  182. package/templates/app/api/dynamic-redirect/route.ts +24 -0
  183. package/templates/app/api/preview/exit/route.ts +34 -0
  184. package/templates/app/api/preview/route.ts +63 -0
  185. package/templates/app/api/revalidate/route.ts +118 -0
  186. package/templates/components/agility-components/RichTextArea.tsx +66 -0
  187. package/templates/components/agility-components/index.ts +30 -0
  188. package/templates/components/agility-pages/MainTemplate.tsx +36 -0
  189. package/templates/components/agility-pages/index.ts +11 -0
  190. package/templates/docs/01-agility-cms-overview.md +139 -0
  191. package/templates/docs/02-page-routing.md +251 -0
  192. package/templates/docs/03-creating-components.md +462 -0
  193. package/templates/docs/04-data-fetching.md +484 -0
  194. package/templates/docs/05-containers-and-lists.md +596 -0
  195. package/templates/docs/06-localization.md +561 -0
  196. package/templates/docs/07-caching-strategies.md +410 -0
  197. package/templates/docs/08-common-components.md +756 -0
  198. package/templates/docs/09-whats-included.md +279 -0
  199. package/templates/docs/10-mcp-server-setup.md +153 -0
  200. package/templates/docs/11-linked-nested-content.md +611 -0
  201. package/templates/docs/README.md +164 -0
  202. package/templates/lib/cms/getAgilityContext.ts +28 -0
  203. package/templates/lib/cms/getAgilityPage.ts +51 -0
  204. package/templates/lib/cms/getAgilitySDK.ts +22 -0
  205. package/templates/lib/cms/getContentItem.ts +20 -0
  206. package/templates/lib/cms/getContentList.ts +19 -0
  207. package/templates/lib/cms/getRedirections.ts +85 -0
  208. package/templates/lib/cms/getSitemapFlat.ts +19 -0
  209. package/templates/lib/cms/getSitemapNested.ts +19 -0
  210. package/templates/lib/env.ts +99 -0
  211. package/templates/lib/i18n/config.ts +28 -0
  212. package/templates/proxy.ts +101 -0
  213. package/tsconfig.json +21 -0
@@ -0,0 +1,596 @@
1
+ # Containers and Content Lists
2
+
3
+ This document explains how to work with content lists (containers) in Agility CMS, including common patterns like blog posts, testimonials, team members, and shared data.
4
+
5
+ ## What are Content Lists?
6
+
7
+ Content lists (also called containers) are collections of content items of the same type. Common examples:
8
+ - **Blog Posts**: Articles, news, updates
9
+ - **Testimonials**: Customer reviews, quotes
10
+ - **Team Members**: Staff, leadership team
11
+ - **Products**: Catalog items
12
+ - **FAQs**: Question and answer pairs
13
+ - **Categories**: Taxonomy for organizing content
14
+
15
+ ## Creating a Content List
16
+
17
+ ### Step 1: Define Content Definition in Agility CMS
18
+
19
+ 1. Go to **Settings > Content Definitions**
20
+ 2. Click **New Content Definition**
21
+ 3. Choose **Content List**
22
+ 4. Add a **Reference Name** (e.g., "BlogPost")
23
+ 5. Add fields (e.g., title, content, date, author, category)
24
+
25
+ ### Step 2: Create Container in Agility CMS
26
+
27
+ 1. Go to **Shared Content**
28
+ 2. Click **New Container**
29
+ 3. Select your content definition (e.g., "BlogPost")
30
+ 4. Give it a **Reference Name** (e.g., "posts")
31
+ 5. Add content items
32
+
33
+ ## Displaying Content Lists
34
+
35
+ ### Pattern 1: Simple List Module
36
+
37
+ Create a module that displays items from a content list:
38
+
39
+ ```tsx
40
+ // src/components/agility-components/PostListing.tsx
41
+
42
+ import { getContentList } from "@/lib/cms/getContentList";
43
+
44
+ interface PostListingProps {
45
+ module: {
46
+ fields: {
47
+ title: string;
48
+ numberOfPosts: number;
49
+ };
50
+ };
51
+ locale: string;
52
+ }
53
+
54
+ export default async function PostListing({ module, locale }: PostListingProps) {
55
+ const { title, numberOfPosts } = module.fields;
56
+
57
+ const posts = await getContentList({
58
+ referenceName: "posts",
59
+ locale,
60
+ take: numberOfPosts || 10,
61
+ sort: "fields.date desc",
62
+ });
63
+
64
+ return (
65
+ <section className="py-16">
66
+ <h2 className="text-4xl font-bold mb-8">{title}</h2>
67
+ <div className="grid md:grid-cols-3 gap-6">
68
+ {posts.map((post) => (
69
+ <article key={post.contentID} className="border rounded-lg p-6">
70
+ {post.fields.image && (
71
+ <img
72
+ src={post.fields.image.url}
73
+ alt={post.fields.image.label}
74
+ className="w-full h-48 object-cover rounded mb-4"
75
+ />
76
+ )}
77
+ <h3 className="text-xl font-semibold mb-2">{post.fields.title}</h3>
78
+ <p className="text-gray-600 mb-4">{post.fields.excerpt}</p>
79
+ <a
80
+ href={`/blog/${post.fields.slug}`}
81
+ className="text-blue-600 hover:underline"
82
+ >
83
+ Read More →
84
+ </a>
85
+ </article>
86
+ ))}
87
+ </div>
88
+ </section>
89
+ );
90
+ }
91
+ ```
92
+
93
+ ### Pattern 2: Nested Content List Reference
94
+
95
+ When a module references a content list (not hardcoded):
96
+
97
+ ```tsx
98
+ // src/components/agility-components/CardGrid.tsx
99
+
100
+ import { getContentList } from "@/lib/cms/getContentList";
101
+
102
+ interface CardGridProps {
103
+ module: {
104
+ fields: {
105
+ title: string;
106
+ cards: {
107
+ referenceName: string; // The list reference name
108
+ };
109
+ };
110
+ };
111
+ locale: string;
112
+ }
113
+
114
+ export default async function CardGrid({ module, locale }: CardGridProps) {
115
+ const { title, cards } = module.fields;
116
+
117
+ // Fetch items from the referenced list
118
+ const cardItems = await getContentList({
119
+ referenceName: cards.referenceName, // e.g., "testimonials", "team", etc.
120
+ locale,
121
+ });
122
+
123
+ return (
124
+ <section className="py-16">
125
+ <h2 className="text-4xl font-bold mb-8 text-center">{title}</h2>
126
+ <div className="grid md:grid-cols-4 gap-6">
127
+ {cardItems.map((card) => (
128
+ <div key={card.contentID} className="text-center">
129
+ {card.fields.image && (
130
+ <img
131
+ src={card.fields.image.url}
132
+ alt={card.fields.image.label}
133
+ className="w-24 h-24 rounded-full mx-auto mb-4"
134
+ />
135
+ )}
136
+ <h3 className="font-semibold">{card.fields.title}</h3>
137
+ <p className="text-sm text-gray-600">{card.fields.description}</p>
138
+ </div>
139
+ ))}
140
+ </div>
141
+ </section>
142
+ );
143
+ }
144
+ ```
145
+
146
+ **Why this pattern?** Editors can choose different content lists without code changes.
147
+
148
+ ### Pattern 3: Testimonials Carousel
149
+
150
+ ```tsx
151
+ // src/components/agility-components/Testimonials.server.tsx
152
+
153
+ import { getContentList } from "@/lib/cms/getContentList";
154
+ import TestimonialsClient from "./Testimonials.client";
155
+
156
+ export default async function Testimonials({ module, locale }: any) {
157
+ const { title } = module.fields;
158
+
159
+ const testimonials = await getContentList({
160
+ referenceName: "testimonials",
161
+ locale,
162
+ take: 10,
163
+ });
164
+
165
+ return (
166
+ <section className="py-16 bg-gray-50">
167
+ <h2 className="text-4xl font-bold mb-8 text-center">{title}</h2>
168
+ <TestimonialsClient testimonials={testimonials} />
169
+ </section>
170
+ );
171
+ }
172
+ ```
173
+
174
+ ```tsx
175
+ // src/components/agility-components/Testimonials.client.tsx
176
+ "use client";
177
+
178
+ import { useState } from "react";
179
+
180
+ export default function TestimonialsClient({ testimonials }: any) {
181
+ const [current, setCurrent] = useState(0);
182
+
183
+ const next = () => setCurrent((i) => (i + 1) % testimonials.length);
184
+ const prev = () =>
185
+ setCurrent((i) => (i - 1 + testimonials.length) % testimonials.length);
186
+
187
+ const testimonial = testimonials[current];
188
+
189
+ return (
190
+ <div className="max-w-4xl mx-auto relative">
191
+ <div className="text-center">
192
+ <p className="text-xl italic mb-4">"{testimonial.fields.quote}"</p>
193
+ <p className="font-semibold">{testimonial.fields.name}</p>
194
+ <p className="text-gray-600">{testimonial.fields.company}</p>
195
+ </div>
196
+ <div className="flex justify-center gap-4 mt-8">
197
+ <button onClick={prev} className="px-4 py-2 bg-gray-200 rounded">
198
+
199
+ </button>
200
+ <button onClick={next} className="px-4 py-2 bg-gray-200 rounded">
201
+
202
+ </button>
203
+ </div>
204
+ </div>
205
+ );
206
+ }
207
+ ```
208
+
209
+ ### Pattern 4: Team Listing
210
+
211
+ ```tsx
212
+ // src/components/agility-components/TeamListing.tsx
213
+
214
+ import { getContentList } from "@/lib/cms/getContentList";
215
+
216
+ export default async function TeamListing({ module, locale }: any) {
217
+ const { title, subtitle } = module.fields;
218
+
219
+ const team = await getContentList({
220
+ referenceName: "team",
221
+ locale,
222
+ sort: "fields.order asc",
223
+ });
224
+
225
+ return (
226
+ <section className="py-16">
227
+ <div className="text-center mb-12">
228
+ <h2 className="text-4xl font-bold mb-4">{title}</h2>
229
+ <p className="text-xl text-gray-600">{subtitle}</p>
230
+ </div>
231
+ <div className="grid md:grid-cols-4 gap-8">
232
+ {team.map((member) => (
233
+ <div key={member.contentID} className="text-center">
234
+ <img
235
+ src={member.fields.photo.url}
236
+ alt={member.fields.name}
237
+ className="w-48 h-48 rounded-full mx-auto mb-4 object-cover"
238
+ />
239
+ <h3 className="text-xl font-semibold">{member.fields.name}</h3>
240
+ <p className="text-gray-600 mb-2">{member.fields.title}</p>
241
+ <p className="text-sm">{member.fields.bio}</p>
242
+ {member.fields.linkedin && (
243
+ <a
244
+ href={member.fields.linkedin.href}
245
+ className="text-blue-600 mt-2 inline-block"
246
+ >
247
+ LinkedIn →
248
+ </a>
249
+ )}
250
+ </div>
251
+ ))}
252
+ </div>
253
+ </section>
254
+ );
255
+ }
256
+ ```
257
+
258
+ ## Pagination
259
+
260
+ ### Pattern 1: Simple Pagination
261
+
262
+ ```tsx
263
+ // src/components/agility-components/BlogListing.tsx
264
+
265
+ import { getContentList } from "@/lib/cms/getContentList";
266
+
267
+ export default async function BlogListing({ searchParams, locale }: any) {
268
+ const page = parseInt(searchParams?.page || "1");
269
+ const perPage = 12;
270
+
271
+ const posts = await getContentList({
272
+ referenceName: "posts",
273
+ locale,
274
+ take: perPage,
275
+ skip: (page - 1) * perPage,
276
+ sort: "fields.date desc",
277
+ });
278
+
279
+ // Calculate total pages (you may need to fetch total count separately)
280
+ const hasMore = posts.length === perPage;
281
+
282
+ return (
283
+ <div>
284
+ <div className="grid md:grid-cols-3 gap-6">
285
+ {posts.map((post) => (
286
+ <article key={post.contentID}>{/* Post card */}</article>
287
+ ))}
288
+ </div>
289
+ <nav className="flex justify-center gap-4 mt-8">
290
+ {page > 1 && (
291
+ <a
292
+ href={`?page=${page - 1}`}
293
+ className="px-4 py-2 bg-blue-600 text-white rounded"
294
+ >
295
+ Previous
296
+ </a>
297
+ )}
298
+ <span className="px-4 py-2">Page {page}</span>
299
+ {hasMore && (
300
+ <a
301
+ href={`?page=${page + 1}`}
302
+ className="px-4 py-2 bg-blue-600 text-white rounded"
303
+ >
304
+ Next
305
+ </a>
306
+ )}
307
+ </nav>
308
+ </div>
309
+ );
310
+ }
311
+ ```
312
+
313
+ ### Pattern 2: Load More Button
314
+
315
+ ```tsx
316
+ // src/components/agility-components/PostListing.server.tsx
317
+
318
+ import { getContentList } from "@/lib/cms/getContentList";
319
+ import LoadMoreClient from "./LoadMore.client";
320
+
321
+ export default async function PostListing({ module, locale }: any) {
322
+ const initialPosts = await getContentList({
323
+ referenceName: "posts",
324
+ locale,
325
+ take: 6,
326
+ sort: "fields.date desc",
327
+ });
328
+
329
+ return (
330
+ <div>
331
+ <LoadMoreClient initialPosts={initialPosts} locale={locale} />
332
+ </div>
333
+ );
334
+ }
335
+ ```
336
+
337
+ ```tsx
338
+ // src/components/agility-components/LoadMore.client.tsx
339
+ "use client";
340
+
341
+ import { useState } from "react";
342
+
343
+ export default function LoadMoreClient({ initialPosts, locale }: any) {
344
+ const [posts, setPosts] = useState(initialPosts);
345
+ const [skip, setSkip] = useState(6);
346
+ const [loading, setLoading] = useState(false);
347
+
348
+ const loadMore = async () => {
349
+ setLoading(true);
350
+ // Call API route to fetch more posts
351
+ const res = await fetch(`/api/posts?skip=${skip}&take=6&locale=${locale}`);
352
+ const newPosts = await res.json();
353
+ setPosts([...posts, ...newPosts]);
354
+ setSkip(skip + 6);
355
+ setLoading(false);
356
+ };
357
+
358
+ return (
359
+ <div>
360
+ <div className="grid md:grid-cols-3 gap-6">
361
+ {posts.map((post) => (
362
+ <article key={post.contentID}>{/* Post card */}</article>
363
+ ))}
364
+ </div>
365
+ <div className="text-center mt-8">
366
+ <button
367
+ onClick={loadMore}
368
+ disabled={loading}
369
+ className="px-6 py-3 bg-blue-600 text-white rounded"
370
+ >
371
+ {loading ? "Loading..." : "Load More"}
372
+ </button>
373
+ </div>
374
+ </div>
375
+ );
376
+ }
377
+ ```
378
+
379
+ ## Filtering and Categories
380
+
381
+ ### Pattern 1: Category Filter
382
+
383
+ ```tsx
384
+ // src/components/agility-components/BlogWithCategories.tsx
385
+
386
+ import { getContentList } from "@/lib/cms/getContentList";
387
+
388
+ export default async function BlogWithCategories({ searchParams, locale }: any) {
389
+ const categoryId = searchParams?.category;
390
+
391
+ // Fetch categories
392
+ const categories = await getContentList({
393
+ referenceName: "categories",
394
+ locale,
395
+ sort: "fields.name asc",
396
+ });
397
+
398
+ // Fetch all posts
399
+ const allPosts = await getContentList({
400
+ referenceName: "posts",
401
+ locale,
402
+ sort: "fields.date desc",
403
+ });
404
+
405
+ // Filter by category if selected
406
+ const posts = categoryId
407
+ ? allPosts.filter((post) => post.fields.category?.contentID === parseInt(categoryId))
408
+ : allPosts;
409
+
410
+ return (
411
+ <div>
412
+ <nav className="flex gap-4 mb-8">
413
+ <a
414
+ href="?"
415
+ className={!categoryId ? "font-bold" : ""}
416
+ >
417
+ All
418
+ </a>
419
+ {categories.map((cat) => (
420
+ <a
421
+ key={cat.contentID}
422
+ href={`?category=${cat.contentID}`}
423
+ className={categoryId === String(cat.contentID) ? "font-bold" : ""}
424
+ >
425
+ {cat.fields.name}
426
+ </a>
427
+ ))}
428
+ </nav>
429
+ <div className="grid md:grid-cols-3 gap-6">
430
+ {posts.map((post) => (
431
+ <article key={post.contentID}>{/* Post card */}</article>
432
+ ))}
433
+ </div>
434
+ </div>
435
+ );
436
+ }
437
+ ```
438
+
439
+ ### Pattern 2: Search and Filter
440
+
441
+ ```tsx
442
+ // src/components/agility-components/PostSearch.tsx
443
+
444
+ import { getContentList } from "@/lib/cms/getContentList";
445
+
446
+ export default async function PostSearch({ searchParams, locale }: any) {
447
+ const query = searchParams?.q?.toLowerCase() || "";
448
+
449
+ const allPosts = await getContentList({
450
+ referenceName: "posts",
451
+ locale,
452
+ sort: "fields.date desc",
453
+ });
454
+
455
+ const filteredPosts = query
456
+ ? allPosts.filter(
457
+ (post) =>
458
+ post.fields.title.toLowerCase().includes(query) ||
459
+ post.fields.excerpt.toLowerCase().includes(query)
460
+ )
461
+ : allPosts;
462
+
463
+ return (
464
+ <div>
465
+ <form method="GET" className="mb-8">
466
+ <input
467
+ type="text"
468
+ name="q"
469
+ defaultValue={query}
470
+ placeholder="Search posts..."
471
+ className="w-full p-4 border rounded"
472
+ />
473
+ </form>
474
+ <p className="mb-4">
475
+ {filteredPosts.length} {filteredPosts.length === 1 ? "post" : "posts"} found
476
+ </p>
477
+ <div className="grid md:grid-cols-3 gap-6">
478
+ {filteredPosts.map((post) => (
479
+ <article key={post.contentID}>{/* Post card */}</article>
480
+ ))}
481
+ </div>
482
+ </div>
483
+ );
484
+ }
485
+ ```
486
+
487
+ ## Shared Data Pattern
488
+
489
+ Content lists can be used as shared data across the site:
490
+
491
+ ### Example: Site Settings
492
+
493
+ ```tsx
494
+ // src/lib/cms/getSiteSettings.ts
495
+
496
+ import { getContentList } from "./getContentList";
497
+
498
+ export async function getSiteSettings(locale: string) {
499
+ const settings = await getContentList({
500
+ referenceName: "siteSettings",
501
+ locale,
502
+ take: 1,
503
+ });
504
+
505
+ return settings[0]?.fields || {};
506
+ }
507
+ ```
508
+
509
+ ```tsx
510
+ // src/components/Header.tsx
511
+
512
+ import { getSiteSettings } from "@/lib/cms/getSiteSettings";
513
+
514
+ export default async function Header({ locale }: any) {
515
+ const settings = await getSiteSettings(locale);
516
+
517
+ return (
518
+ <header>
519
+ <img src={settings.logo.url} alt={settings.siteName} />
520
+ <nav>
521
+ {settings.navigationLinks.map((link: any) => (
522
+ <a key={link.href} href={link.href}>
523
+ {link.text}
524
+ </a>
525
+ ))}
526
+ </nav>
527
+ </header>
528
+ );
529
+ }
530
+ ```
531
+
532
+ ## Related Content
533
+
534
+ ### Example: Posts with Author
535
+
536
+ ```tsx
537
+ import { getContentList, getContentItem } from "@/lib/cms";
538
+
539
+ export default async function PostsWithAuthors({ locale }: any) {
540
+ const posts = await getContentList({
541
+ referenceName: "posts",
542
+ locale,
543
+ take: 10,
544
+ });
545
+
546
+ // Fetch author for each post
547
+ const postsWithAuthors = await Promise.all(
548
+ posts.map(async (post) => {
549
+ if (post.fields.author?.contentID) {
550
+ const author = await getContentItem({
551
+ contentID: post.fields.author.contentID,
552
+ locale,
553
+ });
554
+ return { ...post, author };
555
+ }
556
+ return post;
557
+ })
558
+ );
559
+
560
+ return (
561
+ <div>
562
+ {postsWithAuthors.map((post) => (
563
+ <article key={post.contentID}>
564
+ <h3>{post.fields.title}</h3>
565
+ {post.author && (
566
+ <div className="flex items-center gap-2 mt-2">
567
+ <img
568
+ src={post.author.fields.avatar.url}
569
+ alt={post.author.fields.name}
570
+ className="w-8 h-8 rounded-full"
571
+ />
572
+ <span>{post.author.fields.name}</span>
573
+ </div>
574
+ )}
575
+ </article>
576
+ ))}
577
+ </div>
578
+ );
579
+ }
580
+ ```
581
+
582
+ ## Best Practices
583
+
584
+ 1. **Use Reference Names**: Always use `referenceName`, not hardcoded strings
585
+ 2. **Pagination**: Implement pagination for large lists
586
+ 3. **Sorting**: Always specify a sort order for consistency
587
+ 4. **Caching**: Let Next.js handle caching automatically
588
+ 5. **Error Handling**: Check for empty lists and show appropriate messages
589
+ 6. **Type Safety**: Define TypeScript interfaces for content types
590
+ 7. **Performance**: Use `Promise.all()` for parallel fetching
591
+
592
+ ## Next Steps
593
+
594
+ - Read [04-data-fetching.md](./04-data-fetching.md) for more data patterns
595
+ - Read [06-localization.md](./06-localization.md) for multi-locale lists
596
+ - Read [09-example-components.md](./09-example-components.md) for real examples