@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,251 @@
1
+ # Page Routing
2
+
3
+ This document explains how page routing works with Agility CMS in this Next.js project.
4
+
5
+ ## Dynamic Page Route
6
+
7
+ All Agility CMS pages are handled by a single dynamic route:
8
+
9
+ ```
10
+ app/[locale]/[...slug]/page.tsx
11
+ ```
12
+
13
+ This route:
14
+ - **`[locale]`**: Handles locale/language (e.g., `en-us`, `fr`, `es`)
15
+ - **`[...slug]`**: Captures the entire path (e.g., `about`, `blog/post-1`, `products/category/item`)
16
+
17
+ ## How It Works
18
+
19
+ ### 1. Generate Static Params
20
+
21
+ At build time, Next.js generates all pages from the Agility sitemap:
22
+
23
+ ```typescript
24
+ export async function generateStaticParams() {
25
+ const allPaths: { locale: string; slug: string[] }[] = [];
26
+
27
+ for (const locale of locales) {
28
+ const sitemap = await agilityClient.getSitemapFlat({
29
+ channelName: "website",
30
+ languageCode: locale,
31
+ });
32
+
33
+ const localePaths = Object.values(sitemap)
34
+ .filter((node) => !node.redirect && !node.isFolder)
35
+ .map((node) => ({
36
+ locale,
37
+ slug: node.path.split("/").slice(1),
38
+ }));
39
+
40
+ allPaths.push(...localePaths);
41
+ }
42
+
43
+ return allPaths;
44
+ }
45
+ ```
46
+
47
+ **Result**: All pages are pre-rendered at build time for optimal performance.
48
+
49
+ ### 2. Generate Metadata
50
+
51
+ Each page generates SEO metadata:
52
+
53
+ ```typescript
54
+ export async function generateMetadata(props: PageProps): Promise<Metadata> {
55
+ const agilityData = await getAgilityPage({ params });
56
+
57
+ return {
58
+ title: agilityData.page?.seo?.metaTitle || 'Page',
59
+ description: agilityData.page?.seo?.metaDescription || '',
60
+ };
61
+ }
62
+ ```
63
+
64
+ ### 3. Render Page
65
+
66
+ The page component fetches data and renders:
67
+
68
+ ```typescript
69
+ export default async function Page({ params }: PageProps) {
70
+ const agilityData = await getAgilityPage({ params });
71
+ if (!agilityData.page) notFound();
72
+
73
+ const AgilityPageTemplate = getPageTemplate(
74
+ agilityData.pageTemplateName || "MainTemplate"
75
+ );
76
+
77
+ return (
78
+ <div data-agility-page={agilityData.page?.pageID}>
79
+ <AgilityPageTemplate {...agilityData} />
80
+ </div>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ## Proxy Flow
86
+
87
+ The `proxy.ts` file handles special routing cases:
88
+
89
+ ### 1. Preview Mode
90
+ ```
91
+ URL: /about?agilitypreviewkey=xxx
92
+ → Redirects to: /api/preview?slug=/about&agilitypreviewkey=xxx
93
+ ```
94
+
95
+ ### 2. Exit Preview
96
+ ```
97
+ URL: /about?AgilityPreview=0
98
+ → Redirects to: /api/preview/exit?slug=/about
99
+ ```
100
+
101
+ ### 3. Content ID Routing
102
+ ```
103
+ URL: /?ContentID=123
104
+ → Rewrite to: /api/dynamic-redirect?ContentID=123
105
+ ```
106
+
107
+ ### 4. Locale Query Param
108
+ ```
109
+ URL: /about?lang=fr
110
+ → Redirects to: /fr/about
111
+ ```
112
+
113
+ ### 5. Search Params Encoding
114
+ ```
115
+ URL: /products?category=shirts&size=large
116
+ → Rewrite to: /products/~~~category=shirts&size=large~~~
117
+ ```
118
+
119
+ This encoding allows Next.js to cache pages with different query parameters.
120
+
121
+ ### 6. Default Locale Handling
122
+ ```
123
+ URL: /about (no locale prefix)
124
+ → Rewrite to: /en-us/about (internal)
125
+ ```
126
+
127
+ The default locale doesn't appear in URLs for cleaner paths.
128
+
129
+ ## Locale Configuration
130
+
131
+ Locales are configured in `src/lib/i18n/config.ts`:
132
+
133
+ ```typescript
134
+ export const defaultLocale = "en-us"
135
+ export const locales = ["en-us", "fr", "es"] as const
136
+ ```
137
+
138
+ ### URL Structure
139
+
140
+ - **Default locale**: `/about` (no prefix)
141
+ - **Other locales**: `/fr/about`, `/es/about`
142
+
143
+ ### Adding a New Locale
144
+
145
+ 1. Update environment variable:
146
+ ```env
147
+ AGILITY_LOCALES=en-us,fr,es,de
148
+ ```
149
+
150
+ 2. Update config file:
151
+ ```typescript
152
+ export const locales = ["en-us", "fr", "es", "de"] as const
153
+ ```
154
+
155
+ 3. Add content in Agility CMS for the new locale
156
+
157
+ 4. Rebuild the site
158
+
159
+ ## Static Generation with ISR
160
+
161
+ The project uses Incremental Static Regeneration (ISR):
162
+
163
+ ```typescript
164
+ export const revalidate = 60 // Revalidate every 60 seconds
165
+ ```
166
+
167
+ **How it works**:
168
+ 1. First request: Page is generated statically at build time
169
+ 2. Subsequent requests: Serve cached page
170
+ 3. After 60 seconds: Next request triggers regeneration in background
171
+ 4. New version: Cached for next 60 seconds
172
+
173
+ ## Dynamic Routing Scenarios
174
+
175
+ ### Scenario 1: Simple Page
176
+ ```
177
+ Path: /about
178
+ Locale: en-us
179
+ Template: MainTemplate
180
+ Zones: [main-content-zone]
181
+ ```
182
+
183
+ ### Scenario 2: Blog Post
184
+ ```
185
+ Path: /blog/my-first-post
186
+ Locale: en-us
187
+ Template: BlogPostTemplate
188
+ Zones: [main-content-zone]
189
+ Dynamic Content: BlogPost item
190
+ ```
191
+
192
+ ### Scenario 3: Multi-locale Page
193
+ ```
194
+ Path: /about (en-us)
195
+ Path: /fr/about (fr)
196
+ Path: /es/acerca-de (es)
197
+ ```
198
+
199
+ ## Search Parameters
200
+
201
+ Search parameters are supported and passed to page templates:
202
+
203
+ ```typescript
204
+ // URL: /products?category=shirts&size=large
205
+ const globalSearchParams = agilityData.globalData?.["searchParams"] || {};
206
+
207
+ // In your module component:
208
+ function ProductListing({ searchParams }) {
209
+ const category = searchParams.category; // "shirts"
210
+ const size = searchParams.size; // "large"
211
+ // ... filter products
212
+ }
213
+ ```
214
+
215
+ ## Important Notes
216
+
217
+ 1. **Case Sensitivity**: Page paths in Agility CMS are case-insensitive
218
+ 2. **Trailing Slashes**: Automatically handled by proxy
219
+ 3. **404 Pages**: Non-existent pages return 404 with `not-found.tsx`
220
+ 4. **Error Handling**: Errors are caught by `error.tsx`
221
+ 5. **Folders**: Folder pages in Agility CMS are filtered out (not rendered)
222
+ 6. **Redirects**: Redirect pages are handled by proxy
223
+
224
+ ## Common Routing Patterns
225
+
226
+ ### Pattern 1: Homepage
227
+ ```
228
+ Path: /
229
+ Slug: []
230
+ Template: HomeTemplate
231
+ ```
232
+
233
+ ### Pattern 2: Single Level
234
+ ```
235
+ Path: /about
236
+ Slug: ["about"]
237
+ Template: MainTemplate
238
+ ```
239
+
240
+ ### Pattern 3: Multi Level
241
+ ```
242
+ Path: /blog/category/post-title
243
+ Slug: ["blog", "category", "post-title"]
244
+ Template: BlogPostTemplate
245
+ ```
246
+
247
+ ## Next Steps
248
+
249
+ - Read [03-creating-components.md](./03-creating-components.md) to add components to pages
250
+ - Read [04-data-fetching.md](./04-data-fetching.md) to fetch dynamic content
251
+ - Read [06-localization.md](./06-localization.md) for multi-language support
@@ -0,0 +1,462 @@
1
+ # Creating Agility Components
2
+
3
+ This document explains how to create new components (also called modules in the CMS) for Agility CMS.
4
+
5
+ ## What is an Agility Component?
6
+
7
+ An **Agility Component** (also referred to as a "module" in the CMS interface) is a reusable component that editors can add to pages in Agility CMS. Common examples:
8
+ - Hero sections
9
+ - Rich text content
10
+ - Image galleries
11
+ - Blog post listings
12
+ - Contact forms
13
+ - Testimonials
14
+ - Call-to-action buttons
15
+
16
+ **Note**: While these are called "modules" in the Agility CMS interface, we refer to them as "components" in code to align with React terminology.
17
+
18
+ ## Component Architecture
19
+
20
+ ### Server vs Client Components
21
+
22
+ **Server Components** (default):
23
+ - Fetch data from Agility CMS or other APIs
24
+ - No client-side JavaScript
25
+ - Better performance
26
+ - Use `.server.tsx` extension (optional but recommended)
27
+
28
+ **Client Components**:
29
+ - Interactive features (forms, carousels, tabs)
30
+ - Use React hooks (useState, useEffect, etc.)
31
+ - Must have `"use client"` directive
32
+ - Use `.client.tsx` extension (optional but recommended)
33
+
34
+ **Hybrid Pattern** (recommended for complex components):
35
+ - Server component fetches data
36
+ - Passes data to client component for interactivity
37
+
38
+ ## Creating a New Component
39
+
40
+ ### Step 1: Define the Component Model in Agility CMS
41
+
42
+ In Agility CMS:
43
+ 1. Go to **Settings > Content Definitions**
44
+ 2. Click **New Module** (Components are called "modules" in the CMS interface)
45
+ 3. Add a **Reference Name** (e.g., "Hero")
46
+ 4. Add **Fields** (e.g., title, subtitle, image, ctaButton)
47
+
48
+ ### Step 2: Create the Component File
49
+
50
+ Create a new file in `src/components/agility-components/`:
51
+
52
+ #### Example 1: Simple Server Component
53
+
54
+ ```tsx
55
+ // src/components/agility-components/Hero.tsx
56
+
57
+ interface HeroProps {
58
+ module: { // Note: prop is called "module" for historical reasons
59
+ fields: {
60
+ title: string;
61
+ subtitle: string;
62
+ backgroundImage: {
63
+ url: string;
64
+ label: string;
65
+ };
66
+ };
67
+ };
68
+ locale: string;
69
+ }
70
+
71
+ export default function Hero({ module, locale }: HeroProps) {
72
+ const { title, subtitle, backgroundImage } = module.fields;
73
+
74
+ return (
75
+ <div className="relative h-screen">
76
+ <img
77
+ src={backgroundImage.url}
78
+ alt={backgroundImage.label}
79
+ className="absolute inset-0 w-full h-full object-cover"
80
+ />
81
+ <div className="relative z-10 flex flex-col items-center justify-center h-full text-white">
82
+ <h1 className="text-6xl font-bold">{title}</h1>
83
+ <p className="text-2xl mt-4">{subtitle}</p>
84
+ </div>
85
+ </div>
86
+ );
87
+ }
88
+ ```
89
+
90
+ #### Example 2: Client Component with Interactivity
91
+
92
+ ```tsx
93
+ // src/components/agility-components/Carousel.client.tsx
94
+ "use client";
95
+
96
+ import { useState } from "react";
97
+
98
+ interface CarouselProps {
99
+ module: {
100
+ fields: {
101
+ slides: Array<{
102
+ image: { url: string; label: string };
103
+ caption: string;
104
+ }>;
105
+ };
106
+ };
107
+ }
108
+
109
+ export default function Carousel({ module }: CarouselProps) {
110
+ const { slides } = module.fields;
111
+ const [currentIndex, setCurrentIndex] = useState(0);
112
+
113
+ const next = () => setCurrentIndex((i) => (i + 1) % slides.length);
114
+ const prev = () => setCurrentIndex((i) => (i - 1 + slides.length) % slides.length);
115
+
116
+ return (
117
+ <div className="relative">
118
+ <img
119
+ src={slides[currentIndex].image.url}
120
+ alt={slides[currentIndex].image.label}
121
+ className="w-full h-96 object-cover"
122
+ />
123
+ <p className="text-center mt-2">{slides[currentIndex].caption}</p>
124
+ <button onClick={prev} className="absolute left-4 top-1/2">←</button>
125
+ <button onClick={next} className="absolute right-4 top-1/2">→</button>
126
+ </div>
127
+ );
128
+ }
129
+ ```
130
+
131
+ #### Example 3: Hybrid Pattern (Server + Client)
132
+
133
+ ```tsx
134
+ // src/components/agility-components/PostListing.server.tsx
135
+
136
+ import { getContentList } from "@/lib/cms/getContentList";
137
+ import PostListingClient from "./PostListing.client";
138
+
139
+ interface PostListingProps {
140
+ module: {
141
+ fields: {
142
+ title: string;
143
+ postsToShow: number;
144
+ };
145
+ };
146
+ locale: string;
147
+ }
148
+
149
+ export default async function PostListing({ module, locale }: PostListingProps) {
150
+ const { title, postsToShow } = module.fields;
151
+
152
+ // Fetch posts from Agility CMS
153
+ const posts = await getContentList({
154
+ referenceName: "posts",
155
+ locale,
156
+ take: postsToShow,
157
+ });
158
+
159
+ return (
160
+ <div>
161
+ <h2>{title}</h2>
162
+ <PostListingClient posts={posts} />
163
+ </div>
164
+ );
165
+ }
166
+ ```
167
+
168
+ ```tsx
169
+ // src/components/agility-components/PostListing.client.tsx
170
+ "use client";
171
+
172
+ import { useState } from "react";
173
+
174
+ interface Post {
175
+ contentID: number;
176
+ fields: {
177
+ title: string;
178
+ excerpt: string;
179
+ date: string;
180
+ };
181
+ }
182
+
183
+ interface PostListingClientProps {
184
+ posts: Post[];
185
+ }
186
+
187
+ export default function PostListingClient({ posts }: PostListingClientProps) {
188
+ const [filter, setFilter] = useState("");
189
+
190
+ const filteredPosts = posts.filter((post) =>
191
+ post.fields.title.toLowerCase().includes(filter.toLowerCase())
192
+ );
193
+
194
+ return (
195
+ <div>
196
+ <input
197
+ type="text"
198
+ placeholder="Search posts..."
199
+ value={filter}
200
+ onChange={(e) => setFilter(e.target.value)}
201
+ className="border p-2 mb-4"
202
+ />
203
+ <div className="grid gap-4">
204
+ {filteredPosts.map((post) => (
205
+ <article key={post.contentID}>
206
+ <h3>{post.fields.title}</h3>
207
+ <p>{post.fields.excerpt}</p>
208
+ <time>{post.fields.date}</time>
209
+ </article>
210
+ ))}
211
+ </div>
212
+ </div>
213
+ );
214
+ }
215
+ ```
216
+
217
+ ### Step 3: Register the Component
218
+
219
+ Add the component to `src/components/agility-components/index.ts`:
220
+
221
+ ```typescript
222
+ import RichTextArea from "./RichTextArea";
223
+ import Hero from "./Hero";
224
+ import Carousel from "./Carousel.client";
225
+ import PostListing from "./PostListing.server";
226
+
227
+ export const getModule = (moduleName: string) => {
228
+ switch (moduleName) {
229
+ case "RichTextArea":
230
+ return RichTextArea;
231
+ case "Hero":
232
+ return Hero;
233
+ case "Carousel":
234
+ return Carousel;
235
+ case "PostListing":
236
+ return PostListing;
237
+ default:
238
+ return null;
239
+ }
240
+ };
241
+ ```
242
+
243
+ **Important**: The `moduleName` must match the **Reference Name** you defined in Agility CMS.
244
+
245
+ ### Step 4: Add Component to Page in Agility CMS
246
+
247
+ In Agility CMS:
248
+ 1. Edit a page
249
+ 2. Click **Add Module** in a content zone (this adds a component instance)
250
+ 3. Select your new component
251
+ 4. Fill in the fields
252
+ 5. Save and publish
253
+
254
+ ## Component Props
255
+
256
+ Every Agility component receives these props:
257
+
258
+ ```typescript
259
+ interface ComponentProps {
260
+ module: { // Note: prop is called "module" for historical reasons
261
+ contentID: number; // Unique ID
262
+ properties: {
263
+ referenceName: string; // Component reference name
264
+ };
265
+ fields: { // Your custom fields
266
+ [key: string]: any;
267
+ };
268
+ };
269
+ locale: string; // Current locale (e.g., "en-us")
270
+ sitemap: Array<any>; // Full sitemap
271
+ page: { // Current page data
272
+ pageID: number;
273
+ title: string;
274
+ seo: {
275
+ metaTitle: string;
276
+ metaDescription: string;
277
+ };
278
+ };
279
+ pageTemplateName: string; // Template name
280
+ dynamicPageItem?: any; // For dynamic pages
281
+ searchParams?: Record<string, string>; // Query parameters
282
+ }
283
+ ```
284
+
285
+ ## Common Field Types
286
+
287
+ ### Text Fields
288
+ ```typescript
289
+ fields: {
290
+ title: string;
291
+ description: string;
292
+ }
293
+ ```
294
+
295
+ ### Rich Text
296
+ ```typescript
297
+ fields: {
298
+ content: string; // HTML string
299
+ }
300
+ ```
301
+
302
+ ### Images
303
+ ```typescript
304
+ fields: {
305
+ image: {
306
+ url: string;
307
+ label: string;
308
+ width: number;
309
+ height: number;
310
+ };
311
+ }
312
+ ```
313
+
314
+ ### Content References
315
+ ```typescript
316
+ fields: {
317
+ featuredPost: {
318
+ contentID: number;
319
+ fields: {
320
+ title: string;
321
+ // ... other post fields
322
+ };
323
+ };
324
+ }
325
+ ```
326
+
327
+ ### Content Lists
328
+ ```typescript
329
+ fields: {
330
+ posts: {
331
+ referenceName: string; // "posts"
332
+ };
333
+ }
334
+ ```
335
+
336
+ ### Links
337
+ ```typescript
338
+ fields: {
339
+ ctaButton: {
340
+ href: string;
341
+ text: string;
342
+ target: string;
343
+ };
344
+ }
345
+ ```
346
+
347
+ ## Fetching Nested Content
348
+
349
+ If a component references content or content lists, fetch it in the component:
350
+
351
+ ### Example: Featured Post Component
352
+
353
+ ```tsx
354
+ import { getContentItem } from "@/lib/cms/getContentItem";
355
+
356
+ interface FeaturedPostProps {
357
+ module: {
358
+ fields: {
359
+ title: string;
360
+ post: {
361
+ contentID: number;
362
+ };
363
+ };
364
+ };
365
+ locale: string;
366
+ }
367
+
368
+ export default async function FeaturedPost({ module, locale }: FeaturedPostProps) {
369
+ const { title, post } = module.fields;
370
+
371
+ // Fetch the full post data
372
+ const fullPost = await getContentItem({
373
+ contentID: post.contentID,
374
+ locale,
375
+ });
376
+
377
+ return (
378
+ <div>
379
+ <h2>{title}</h2>
380
+ <article>
381
+ <h3>{fullPost.fields.title}</h3>
382
+ <p>{fullPost.fields.excerpt}</p>
383
+ <a href={`/blog/${fullPost.fields.slug}`}>Read More</a>
384
+ </article>
385
+ </div>
386
+ );
387
+ }
388
+ ```
389
+
390
+ ### Example: Post Listing with Categories
391
+
392
+ ```tsx
393
+ import { getContentList } from "@/lib/cms/getContentList";
394
+
395
+ export default async function PostListing({ module, locale }: any) {
396
+ const posts = await getContentList({
397
+ referenceName: "posts",
398
+ locale,
399
+ sort: "fields.date desc",
400
+ take: 10,
401
+ });
402
+
403
+ // Fetch category for each post
404
+ const postsWithCategories = await Promise.all(
405
+ posts.map(async (post) => {
406
+ if (post.fields.category?.contentID) {
407
+ const category = await getContentItem({
408
+ contentID: post.fields.category.contentID,
409
+ locale,
410
+ });
411
+ return { ...post, category };
412
+ }
413
+ return post;
414
+ })
415
+ );
416
+
417
+ return (
418
+ <div className="grid gap-4">
419
+ {postsWithCategories.map((post) => (
420
+ <article key={post.contentID}>
421
+ <h3>{post.fields.title}</h3>
422
+ {post.category && (
423
+ <span className="badge">{post.category.fields.name}</span>
424
+ )}
425
+ <p>{post.fields.excerpt}</p>
426
+ </article>
427
+ ))}
428
+ </div>
429
+ );
430
+ }
431
+ ```
432
+
433
+ ## Best Practices
434
+
435
+ 1. **Type Safety**: Define TypeScript interfaces for component props
436
+ 2. **Error Handling**: Check for missing fields and provide defaults
437
+ 3. **Loading States**: Use Suspense for data fetching
438
+ 4. **Caching**: Data is automatically cached by Next.js
439
+ 5. **Responsive Design**: Use Tailwind classes for responsive layouts
440
+ 6. **Accessibility**: Include proper ARIA labels and semantic HTML
441
+ 7. **Performance**: Optimize images with next/image when possible
442
+
443
+ ## Component Examples from Demo Site
444
+
445
+ Here are real-world component examples from the demo site:
446
+
447
+ 1. **BackgroundHero**: Hero component with image/gradient backgrounds
448
+ 2. **BentoSection**: Grid layout component with nested card collection
449
+ 3. **LogoStrip**: Logo gallery component with CTA
450
+ 4. **PostListing**: Blog listing component with pagination
451
+ 5. **Testimonials**: Testimonial carousel component
452
+ 6. **TeamListing**: Team member grid component
453
+ 7. **CompanyStats**: Animated statistics component
454
+ 8. **ContactUs**: Contact form component (hybrid pattern)
455
+ 9. **FrequentlyAskedQuestions**: FAQ accordion component
456
+ 10. **PricingCards**: Pricing display component
457
+
458
+ ## Next Steps
459
+
460
+ - Read [04-data-fetching.md](./04-data-fetching.md) for data fetching patterns
461
+ - Read [05-containers-and-lists.md](./05-containers-and-lists.md) for content lists
462
+ - Read [08-common-components.md](./08-common-components.md) for more examples