@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.
- package/.claude/settings.json +7 -0
- package/.claude/settings.local.json +24 -0
- package/FEATURE_ROADMAP.md +343 -0
- package/README.md +205 -0
- package/TESTING.md +131 -0
- package/bin/create-agility-app.js +48 -0
- package/dist/agility/api-keys/generateApiKeys.d.ts +9 -0
- package/dist/agility/api-keys/generateApiKeys.d.ts.map +1 -0
- package/dist/agility/api-keys/generateApiKeys.js +99 -0
- package/dist/agility/api-keys/generateApiKeys.js.map +1 -0
- package/dist/agility/api-keys/getApiKeys.d.ts +9 -0
- package/dist/agility/api-keys/getApiKeys.d.ts.map +1 -0
- package/dist/agility/api-keys/getApiKeys.js +14 -0
- package/dist/agility/api-keys/getApiKeys.js.map +1 -0
- package/dist/agility/index.d.ts +3 -0
- package/dist/agility/index.d.ts.map +1 -0
- package/dist/agility/index.js +8 -0
- package/dist/agility/index.js.map +1 -0
- package/dist/agility/instance/createNewInstance.d.ts +8 -0
- package/dist/agility/instance/createNewInstance.d.ts.map +1 -0
- package/dist/agility/instance/createNewInstance.js +65 -0
- package/dist/agility/instance/createNewInstance.js.map +1 -0
- package/dist/agility/instance/getAvailableInstances.d.ts +8 -0
- package/dist/agility/instance/getAvailableInstances.d.ts.map +1 -0
- package/dist/agility/instance/getAvailableInstances.js +43 -0
- package/dist/agility/instance/getAvailableInstances.js.map +1 -0
- package/dist/agility/instance/manageInstance.d.ts +9 -0
- package/dist/agility/instance/manageInstance.d.ts.map +1 -0
- package/dist/agility/instance/manageInstance.js +82 -0
- package/dist/agility/instance/manageInstance.js.map +1 -0
- package/dist/agility/utils/getMgmtAPIUrl.d.ts +20 -0
- package/dist/agility/utils/getMgmtAPIUrl.d.ts.map +1 -0
- package/dist/agility/utils/getMgmtAPIUrl.js +61 -0
- package/dist/agility/utils/getMgmtAPIUrl.js.map +1 -0
- package/dist/auth/api-key/authenticateWithApiKey.d.ts +6 -0
- package/dist/auth/api-key/authenticateWithApiKey.d.ts.map +1 -0
- package/dist/auth/api-key/authenticateWithApiKey.js +28 -0
- package/dist/auth/api-key/authenticateWithApiKey.js.map +1 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth/authenticate.d.ts +6 -0
- package/dist/auth/oauth/authenticate.d.ts.map +1 -0
- package/dist/auth/oauth/authenticate.js +162 -0
- package/dist/auth/oauth/authenticate.js.map +1 -0
- package/dist/auth/oauth/constants.d.ts +5 -0
- package/dist/auth/oauth/constants.d.ts.map +1 -0
- package/dist/auth/oauth/constants.js +9 -0
- package/dist/auth/oauth/constants.js.map +1 -0
- package/dist/auth/oauth/exchangeCodeForToken.d.ts +7 -0
- package/dist/auth/oauth/exchangeCodeForToken.d.ts.map +1 -0
- package/dist/auth/oauth/exchangeCodeForToken.js +39 -0
- package/dist/auth/oauth/exchangeCodeForToken.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +290 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/promptForMissingOptions.d.ts +8 -0
- package/dist/cli/promptForMissingOptions.d.ts.map +1 -0
- package/dist/cli/promptForMissingOptions.js +92 -0
- package/dist/cli/promptForMissingOptions.js.map +1 -0
- package/dist/config/env/createEnvFile.d.ts +6 -0
- package/dist/config/env/createEnvFile.d.ts.map +1 -0
- package/dist/config/env/createEnvFile.js +31 -0
- package/dist/config/env/createEnvFile.js.map +1 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/mcp/createMcpConfig.d.ts +5 -0
- package/dist/config/mcp/createMcpConfig.d.ts.map +1 -0
- package/dist/config/mcp/createMcpConfig.js +32 -0
- package/dist/config/mcp/createMcpConfig.js.map +1 -0
- package/dist/config/packages/installAgilityPackages.d.ts +6 -0
- package/dist/config/packages/installAgilityPackages.d.ts.map +1 -0
- package/dist/config/packages/installAgilityPackages.js +61 -0
- package/dist/config/packages/installAgilityPackages.js.map +1 -0
- package/dist/config/setupProject.d.ts +8 -0
- package/dist/config/setupProject.d.ts.map +1 -0
- package/dist/config/setupProject.js +32 -0
- package/dist/config/setupProject.js.map +1 -0
- package/dist/create-next-app/createNextApp.d.ts +9 -0
- package/dist/create-next-app/createNextApp.d.ts.map +1 -0
- package/dist/create-next-app/createNextApp.js +83 -0
- package/dist/create-next-app/createNextApp.js.map +1 -0
- package/dist/create-next-app/index.d.ts +3 -0
- package/dist/create-next-app/index.d.ts.map +1 -0
- package/dist/create-next-app/index.js +8 -0
- package/dist/create-next-app/index.js.map +1 -0
- package/dist/scaffold/components/createPageComponents.d.ts +6 -0
- package/dist/scaffold/components/createPageComponents.d.ts.map +1 -0
- package/dist/scaffold/components/createPageComponents.js +62 -0
- package/dist/scaffold/components/createPageComponents.js.map +1 -0
- package/dist/scaffold/containers/createContainers.d.ts +6 -0
- package/dist/scaffold/containers/createContainers.d.ts.map +1 -0
- package/dist/scaffold/containers/createContainers.js +48 -0
- package/dist/scaffold/containers/createContainers.js.map +1 -0
- package/dist/scaffold/index.d.ts +2 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +6 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/instance/createBlankInstance.d.ts +8 -0
- package/dist/scaffold/instance/createBlankInstance.d.ts.map +1 -0
- package/dist/scaffold/instance/createBlankInstance.js +51 -0
- package/dist/scaffold/instance/createBlankInstance.js.map +1 -0
- package/dist/scaffold/models/createContentModels.d.ts +6 -0
- package/dist/scaffold/models/createContentModels.d.ts.map +1 -0
- package/dist/scaffold/models/createContentModels.js +70 -0
- package/dist/scaffold/models/createContentModels.js.map +1 -0
- package/dist/templates/copyDirectory.d.ts +5 -0
- package/dist/templates/copyDirectory.d.ts.map +1 -0
- package/dist/templates/copyDirectory.js +28 -0
- package/dist/templates/copyDirectory.js.map +1 -0
- package/dist/templates/copyTemplates.d.ts +8 -0
- package/dist/templates/copyTemplates.d.ts.map +1 -0
- package/dist/templates/copyTemplates.js +58 -0
- package/dist/templates/copyTemplates.js.map +1 -0
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +6 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/types/index.d.ts +50 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/git.d.ts +9 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +71 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/validation.d.ts +45 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +180 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +45 -0
- package/src/agility/api-keys/generateApiKeys.ts +100 -0
- package/src/agility/api-keys/getApiKeys.ts +13 -0
- package/src/agility/index.ts +3 -0
- package/src/agility/instance/createNewInstance.ts +67 -0
- package/src/agility/instance/getAvailableInstances.ts +49 -0
- package/src/agility/instance/manageInstance.ts +90 -0
- package/src/agility/utils/getMgmtAPIUrl.ts +68 -0
- package/src/auth/api-key/authenticateWithApiKey.ts +24 -0
- package/src/auth/index.ts +3 -0
- package/src/auth/oauth/authenticate.ts +165 -0
- package/src/auth/oauth/constants.ts +6 -0
- package/src/auth/oauth/exchangeCodeForToken.ts +43 -0
- package/src/cli/index.ts +281 -0
- package/src/cli/promptForMissingOptions.ts +104 -0
- package/src/config/env/createEnvFile.ts +30 -0
- package/src/config/index.ts +2 -0
- package/src/config/mcp/createMcpConfig.ts +30 -0
- package/src/config/packages/installAgilityPackages.ts +63 -0
- package/src/config/setupProject.ts +31 -0
- package/src/create-next-app/createNextApp.ts +75 -0
- package/src/create-next-app/index.ts +3 -0
- package/src/scaffold/components/createPageComponents.ts +74 -0
- package/src/scaffold/containers/createContainers.ts +55 -0
- package/src/scaffold/index.ts +2 -0
- package/src/scaffold/instance/createBlankInstance.ts +55 -0
- package/src/scaffold/models/createContentModels.ts +83 -0
- package/src/templates/copyDirectory.ts +24 -0
- package/src/templates/copyTemplates.ts +57 -0
- package/src/templates/index.ts +2 -0
- package/src/types/index.ts +55 -0
- package/src/utils/git.ts +74 -0
- package/src/utils/validation.ts +184 -0
- package/templates/.claude/QUICK-START.md +230 -0
- package/templates/.claude/README.md +32 -0
- package/templates/.claude/settings.json +8 -0
- package/templates/BLANK-INSTANCE-SETUP.md +375 -0
- package/templates/DEVELOPMENT.md +160 -0
- package/templates/EXAMPLE-PROMPTS.md +643 -0
- package/templates/PROMPTS.md +410 -0
- package/templates/README.md +281 -0
- package/templates/agents.md +429 -0
- package/templates/app/[locale]/[...slug]/error.tsx +17 -0
- package/templates/app/[locale]/[...slug]/not-found.tsx +9 -0
- package/templates/app/[locale]/[...slug]/page.tsx +102 -0
- package/templates/app/[locale]/layout.tsx +22 -0
- package/templates/app/[locale]/page.tsx +12 -0
- package/templates/app/api/dynamic-redirect/route.ts +24 -0
- package/templates/app/api/preview/exit/route.ts +34 -0
- package/templates/app/api/preview/route.ts +63 -0
- package/templates/app/api/revalidate/route.ts +118 -0
- package/templates/components/agility-components/RichTextArea.tsx +66 -0
- package/templates/components/agility-components/index.ts +30 -0
- package/templates/components/agility-pages/MainTemplate.tsx +36 -0
- package/templates/components/agility-pages/index.ts +11 -0
- package/templates/docs/01-agility-cms-overview.md +139 -0
- package/templates/docs/02-page-routing.md +251 -0
- package/templates/docs/03-creating-components.md +462 -0
- package/templates/docs/04-data-fetching.md +484 -0
- package/templates/docs/05-containers-and-lists.md +596 -0
- package/templates/docs/06-localization.md +561 -0
- package/templates/docs/07-caching-strategies.md +410 -0
- package/templates/docs/08-common-components.md +756 -0
- package/templates/docs/09-whats-included.md +279 -0
- package/templates/docs/10-mcp-server-setup.md +153 -0
- package/templates/docs/11-linked-nested-content.md +611 -0
- package/templates/docs/README.md +164 -0
- package/templates/lib/cms/getAgilityContext.ts +28 -0
- package/templates/lib/cms/getAgilityPage.ts +51 -0
- package/templates/lib/cms/getAgilitySDK.ts +22 -0
- package/templates/lib/cms/getContentItem.ts +20 -0
- package/templates/lib/cms/getContentList.ts +19 -0
- package/templates/lib/cms/getRedirections.ts +85 -0
- package/templates/lib/cms/getSitemapFlat.ts +19 -0
- package/templates/lib/cms/getSitemapNested.ts +19 -0
- package/templates/lib/env.ts +99 -0
- package/templates/lib/i18n/config.ts +28 -0
- package/templates/proxy.ts +101 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
# Linked and Nested Content in Agility CMS
|
|
2
|
+
|
|
3
|
+
This document explains the different types of linked and nested content in Agility CMS and how to properly fetch and work with each type.
|
|
4
|
+
|
|
5
|
+
## Understanding Content Relationships
|
|
6
|
+
|
|
7
|
+
Agility CMS supports several types of content relationships. Understanding which type you're working with is **CRITICAL** for correct data fetching.
|
|
8
|
+
|
|
9
|
+
### The Two Main Types
|
|
10
|
+
|
|
11
|
+
1. **Auto-Populated Linked Content** (Single Item Links)
|
|
12
|
+
2. **Reference-Based Nested Content** (Content Lists/Grids)
|
|
13
|
+
|
|
14
|
+
## Type 1: Auto-Populated Linked Content
|
|
15
|
+
|
|
16
|
+
### What is it?
|
|
17
|
+
|
|
18
|
+
When you use these Agility field types, the linked content is **automatically populated** by the SDK:
|
|
19
|
+
- **Content Link** (Single item selector)
|
|
20
|
+
- **Dropdown** (Single item from list)
|
|
21
|
+
- **Checkbox** (Multiple items from list)
|
|
22
|
+
- **Search List Box** (Searchable single/multiple item selector)
|
|
23
|
+
|
|
24
|
+
### Key Characteristic
|
|
25
|
+
|
|
26
|
+
The field contains the **complete content item** with all fields, not just a reference.
|
|
27
|
+
|
|
28
|
+
### How to Use
|
|
29
|
+
|
|
30
|
+
**NO separate fetch needed!** The data is already there.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import type { ContentItem } from "@agility/nextjs";
|
|
34
|
+
|
|
35
|
+
interface Author {
|
|
36
|
+
name: string;
|
|
37
|
+
bio: string;
|
|
38
|
+
avatar: ImageField;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface BlogPost {
|
|
42
|
+
title: string;
|
|
43
|
+
content: string;
|
|
44
|
+
author: ContentItem<Author>; // ✅ Complete author object
|
|
45
|
+
category: ContentItem<Category>; // ✅ Complete category object
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default async function BlogPost({ module, locale }: any) {
|
|
49
|
+
const { fields: { title, content, author, category } } = module;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<article>
|
|
53
|
+
<h1>{title}</h1>
|
|
54
|
+
|
|
55
|
+
{/* ✅ CORRECT: Access linked content directly */}
|
|
56
|
+
{author && (
|
|
57
|
+
<div className="author">
|
|
58
|
+
<img src={author.fields.avatar.url} alt={author.fields.name} />
|
|
59
|
+
<span>{author.fields.name}</span>
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
{/* ✅ CORRECT: No fetch needed */}
|
|
64
|
+
{category && (
|
|
65
|
+
<span className="badge">{category.fields.name}</span>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
<div dangerouslySetInnerHTML={{ __html: content }} />
|
|
69
|
+
</article>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Common Mistake
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
// ❌ WRONG: Don't fetch content that's already populated
|
|
78
|
+
const author = await getContentItem({
|
|
79
|
+
contentID: post.fields.author.contentID, // Unnecessary!
|
|
80
|
+
locale,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ✅ CORRECT: Use it directly
|
|
84
|
+
const authorName = post.fields.author.fields.name;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Type 2: Reference-Based Nested Content
|
|
88
|
+
|
|
89
|
+
### What is it?
|
|
90
|
+
|
|
91
|
+
When you use these Agility field types, you get a **reference** that you must fetch separately:
|
|
92
|
+
- **Linked Content** (Grid/list of items)
|
|
93
|
+
- **Content Link (Multi)** (Multiple content items)
|
|
94
|
+
|
|
95
|
+
### Key Characteristic
|
|
96
|
+
|
|
97
|
+
The field contains a **referencename** property that points to a content list, NOT the actual content.
|
|
98
|
+
|
|
99
|
+
### Critical Pattern
|
|
100
|
+
|
|
101
|
+
**You MUST fetch the content separately using the `referencename`.**
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { getContentItem } from "@/lib/cms/getContentItem";
|
|
105
|
+
import { getContentList } from "@/lib/cms/getContentList";
|
|
106
|
+
|
|
107
|
+
interface BentoSection {
|
|
108
|
+
heading: string;
|
|
109
|
+
cards: {
|
|
110
|
+
referencename: string; // ⚠️ Just a reference, not the actual content!
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default async function BentoSection({ module, locale }: any) {
|
|
115
|
+
// Step 1: Get the module fields
|
|
116
|
+
const { fields: { heading, cards } } = await getContentItem<BentoSection>({
|
|
117
|
+
contentID: module.contentid,
|
|
118
|
+
languageCode: locale,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Step 2: Fetch the nested content using the reference name
|
|
122
|
+
const cardItems = await getContentList({
|
|
123
|
+
referenceName: cards.referencename, // ✅ CRITICAL: Use referencename
|
|
124
|
+
languageCode: locale,
|
|
125
|
+
take: 20,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<section>
|
|
130
|
+
<h2>{heading}</h2>
|
|
131
|
+
<div className="grid grid-cols-3 gap-4">
|
|
132
|
+
{cardItems.map((card) => (
|
|
133
|
+
<div key={card.contentID}>
|
|
134
|
+
<h3>{card.fields.title}</h3>
|
|
135
|
+
<p>{card.fields.description}</p>
|
|
136
|
+
</div>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
</section>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Common Mistakes
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
// ❌ WRONG: Trying to access fields directly
|
|
148
|
+
<div>{cards.fields.title}</div> // This won't work!
|
|
149
|
+
|
|
150
|
+
// ❌ WRONG: Using contentID instead of referencename
|
|
151
|
+
const items = await getContentList({
|
|
152
|
+
referenceName: cards.contentID, // Wrong property!
|
|
153
|
+
locale,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// ✅ CORRECT: Fetch using referencename
|
|
157
|
+
const items = await getContentList({
|
|
158
|
+
referenceName: cards.referencename, // Correct!
|
|
159
|
+
locale,
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## How to Tell Which Type You Have
|
|
164
|
+
|
|
165
|
+
### Method 1: Check the Field Type in Agility CMS
|
|
166
|
+
|
|
167
|
+
| Field Type | Populated? | How to Access |
|
|
168
|
+
|------------|-----------|---------------|
|
|
169
|
+
| Content Link (Single) | ✅ Yes | `field.fields.xxx` |
|
|
170
|
+
| Dropdown | ✅ Yes | `field.fields.xxx` |
|
|
171
|
+
| Checkbox | ✅ Yes | `field.fields.xxx` |
|
|
172
|
+
| Search List Box | ✅ Yes | `field.fields.xxx` |
|
|
173
|
+
| **Linked Content (Grid)** | ❌ No | `await getContentList({ referenceName: field.referencename })` |
|
|
174
|
+
| **Content Link (Multi)** | ❌ No | `await getContentList({ referenceName: field.referencename })` |
|
|
175
|
+
|
|
176
|
+
### Method 2: Inspect the Data Structure
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
// Auto-populated linked content has 'fields'
|
|
180
|
+
if (module.fields.author?.fields) {
|
|
181
|
+
// ✅ Auto-populated - use directly
|
|
182
|
+
console.log(module.fields.author.fields.name);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Reference-based content has 'referencename'
|
|
186
|
+
if (module.fields.cards?.referencename) {
|
|
187
|
+
// ⚠️ Reference-based - must fetch separately
|
|
188
|
+
const items = await getContentList({
|
|
189
|
+
referenceName: module.fields.cards.referencename,
|
|
190
|
+
locale,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Real-World Examples
|
|
196
|
+
|
|
197
|
+
### Example 1: Blog Post with Auto-Populated Links
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { AgilityPic } from "@agility/nextjs";
|
|
201
|
+
import type { ContentItem, ImageField } from "@agility/nextjs";
|
|
202
|
+
|
|
203
|
+
interface Category {
|
|
204
|
+
name: string;
|
|
205
|
+
slug: string;
|
|
206
|
+
color: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
interface Author {
|
|
210
|
+
name: string;
|
|
211
|
+
bio: string;
|
|
212
|
+
avatar: ImageField;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
interface Tag {
|
|
216
|
+
name: string;
|
|
217
|
+
slug: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
interface BlogPost {
|
|
221
|
+
title: string;
|
|
222
|
+
content: string;
|
|
223
|
+
image: ImageField;
|
|
224
|
+
author: ContentItem<Author>; // Auto-populated
|
|
225
|
+
category: ContentItem<Category>; // Auto-populated
|
|
226
|
+
tags: ContentItem<Tag>[]; // Auto-populated array
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export default async function BlogPostDetail({ module, locale }: any) {
|
|
230
|
+
const { fields } = module as { fields: BlogPost };
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<article>
|
|
234
|
+
<header>
|
|
235
|
+
<h1>{fields.title}</h1>
|
|
236
|
+
|
|
237
|
+
{/* ✅ Category is auto-populated */}
|
|
238
|
+
{fields.category && (
|
|
239
|
+
<span
|
|
240
|
+
className="badge"
|
|
241
|
+
style={{ backgroundColor: fields.category.fields.color }}
|
|
242
|
+
>
|
|
243
|
+
{fields.category.fields.name}
|
|
244
|
+
</span>
|
|
245
|
+
)}
|
|
246
|
+
|
|
247
|
+
{/* ✅ Author is auto-populated */}
|
|
248
|
+
{fields.author && (
|
|
249
|
+
<div className="author-info">
|
|
250
|
+
<AgilityPic
|
|
251
|
+
image={fields.author.fields.avatar}
|
|
252
|
+
fallbackWidth={48}
|
|
253
|
+
className="rounded-full"
|
|
254
|
+
/>
|
|
255
|
+
<div>
|
|
256
|
+
<p className="font-bold">{fields.author.fields.name}</p>
|
|
257
|
+
<p className="text-sm">{fields.author.fields.bio}</p>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
</header>
|
|
262
|
+
|
|
263
|
+
<AgilityPic
|
|
264
|
+
image={fields.image}
|
|
265
|
+
fallbackWidth={1200}
|
|
266
|
+
className="w-full"
|
|
267
|
+
/>
|
|
268
|
+
|
|
269
|
+
<div dangerouslySetInnerHTML={{ __html: fields.content }} />
|
|
270
|
+
|
|
271
|
+
{/* ✅ Tags are auto-populated array */}
|
|
272
|
+
{fields.tags && fields.tags.length > 0 && (
|
|
273
|
+
<footer className="flex gap-2">
|
|
274
|
+
{fields.tags.map((tag) => (
|
|
275
|
+
<a
|
|
276
|
+
key={tag.contentID}
|
|
277
|
+
href={`/tags/${tag.fields.slug}`}
|
|
278
|
+
className="tag"
|
|
279
|
+
>
|
|
280
|
+
{tag.fields.name}
|
|
281
|
+
</a>
|
|
282
|
+
))}
|
|
283
|
+
</footer>
|
|
284
|
+
)}
|
|
285
|
+
</article>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Example 2: Testimonials Grid with Reference-Based Content
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
import { getContentItem } from "@/lib/cms/getContentItem";
|
|
294
|
+
import { getContentList } from "@/lib/cms/getContentList";
|
|
295
|
+
import { AgilityPic } from "@agility/nextjs";
|
|
296
|
+
import type { ImageField } from "@agility/nextjs";
|
|
297
|
+
|
|
298
|
+
interface TestimonialsModule {
|
|
299
|
+
heading: string;
|
|
300
|
+
subheading: string;
|
|
301
|
+
testimonials: {
|
|
302
|
+
referencename: string; // Reference, not actual content
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
interface Testimonial {
|
|
307
|
+
quote: string;
|
|
308
|
+
name: string;
|
|
309
|
+
company: string;
|
|
310
|
+
avatar: ImageField;
|
|
311
|
+
rating: number;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export default async function TestimonialsGrid({ module, locale }: any) {
|
|
315
|
+
// Step 1: Get module configuration
|
|
316
|
+
const { fields } = await getContentItem<TestimonialsModule>({
|
|
317
|
+
contentID: module.contentid,
|
|
318
|
+
languageCode: locale,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Step 2: Fetch the actual testimonials using the reference
|
|
322
|
+
const testimonials = await getContentList<Testimonial>({
|
|
323
|
+
referenceName: fields.testimonials.referencename,
|
|
324
|
+
languageCode: locale,
|
|
325
|
+
take: 12,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return (
|
|
329
|
+
<section>
|
|
330
|
+
<div className="text-center mb-12">
|
|
331
|
+
<h2>{fields.heading}</h2>
|
|
332
|
+
<p>{fields.subheading}</p>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
<div className="grid md:grid-cols-3 gap-6">
|
|
336
|
+
{testimonials.map((testimonial) => (
|
|
337
|
+
<div key={testimonial.contentID} className="testimonial-card">
|
|
338
|
+
<div className="flex items-center gap-3 mb-4">
|
|
339
|
+
<AgilityPic
|
|
340
|
+
image={testimonial.fields.avatar}
|
|
341
|
+
fallbackWidth={64}
|
|
342
|
+
className="rounded-full"
|
|
343
|
+
/>
|
|
344
|
+
<div>
|
|
345
|
+
<p className="font-bold">{testimonial.fields.name}</p>
|
|
346
|
+
<p className="text-sm">{testimonial.fields.company}</p>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
<p className="italic">"{testimonial.fields.quote}"</p>
|
|
350
|
+
<div className="stars">
|
|
351
|
+
{"★".repeat(testimonial.fields.rating)}
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
))}
|
|
355
|
+
</div>
|
|
356
|
+
</section>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Example 3: Mixed - Both Types in One Component
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
import { getContentItem } from "@/lib/cms/getContentItem";
|
|
365
|
+
import { getContentList } from "@/lib/cms/getContentList";
|
|
366
|
+
import type { ContentItem, ImageField } from "@agility/nextjs";
|
|
367
|
+
|
|
368
|
+
interface FeaturedSection {
|
|
369
|
+
title: string;
|
|
370
|
+
featuredPost: ContentItem<BlogPost>; // Auto-populated single item
|
|
371
|
+
relatedPosts: {
|
|
372
|
+
referencename: string; // Reference to list
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
interface BlogPost {
|
|
377
|
+
title: string;
|
|
378
|
+
excerpt: string;
|
|
379
|
+
image: ImageField;
|
|
380
|
+
slug: string;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export default async function FeaturedSection({ module, locale }: any) {
|
|
384
|
+
// Get module data
|
|
385
|
+
const { fields } = await getContentItem<FeaturedSection>({
|
|
386
|
+
contentID: module.contentid,
|
|
387
|
+
languageCode: locale,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Fetch the referenced list
|
|
391
|
+
const relatedPosts = await getContentList<BlogPost>({
|
|
392
|
+
referenceName: fields.relatedPosts.referencename,
|
|
393
|
+
languageCode: locale,
|
|
394
|
+
take: 3,
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<section>
|
|
399
|
+
<h2>{fields.title}</h2>
|
|
400
|
+
|
|
401
|
+
{/* ✅ Featured post is auto-populated - use directly */}
|
|
402
|
+
{fields.featuredPost && (
|
|
403
|
+
<div className="featured-post">
|
|
404
|
+
<h3>{fields.featuredPost.fields.title}</h3>
|
|
405
|
+
<p>{fields.featuredPost.fields.excerpt}</p>
|
|
406
|
+
<a href={`/blog/${fields.featuredPost.fields.slug}`}>Read more</a>
|
|
407
|
+
</div>
|
|
408
|
+
)}
|
|
409
|
+
|
|
410
|
+
{/* ⚠️ Related posts were fetched separately */}
|
|
411
|
+
<div className="grid grid-cols-3 gap-4">
|
|
412
|
+
{relatedPosts.map((post) => (
|
|
413
|
+
<article key={post.contentID}>
|
|
414
|
+
<h4>{post.fields.title}</h4>
|
|
415
|
+
<p>{post.fields.excerpt}</p>
|
|
416
|
+
</article>
|
|
417
|
+
))}
|
|
418
|
+
</div>
|
|
419
|
+
</section>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## TypeScript Typing Best Practices
|
|
425
|
+
|
|
426
|
+
### For Auto-Populated Content
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
import type { ContentItem } from "@agility/nextjs";
|
|
430
|
+
|
|
431
|
+
interface Author {
|
|
432
|
+
name: string;
|
|
433
|
+
email: string;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
interface MyModule {
|
|
437
|
+
title: string;
|
|
438
|
+
author: ContentItem<Author>; // Type the linked content
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### For Reference-Based Content
|
|
443
|
+
|
|
444
|
+
```tsx
|
|
445
|
+
interface MyModule {
|
|
446
|
+
title: string;
|
|
447
|
+
items: {
|
|
448
|
+
referencename: string; // This is just a string reference
|
|
449
|
+
sortids?: string;
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## Performance Considerations
|
|
455
|
+
|
|
456
|
+
### Auto-Populated Content
|
|
457
|
+
|
|
458
|
+
**Pros:**
|
|
459
|
+
- Single API call - already included
|
|
460
|
+
- No additional fetch latency
|
|
461
|
+
- Simpler code
|
|
462
|
+
|
|
463
|
+
**Cons:**
|
|
464
|
+
- Included even if you don't need it
|
|
465
|
+
- Can increase payload size
|
|
466
|
+
|
|
467
|
+
### Reference-Based Content
|
|
468
|
+
|
|
469
|
+
**Pros:**
|
|
470
|
+
- Flexible - fetch only when needed
|
|
471
|
+
- Can control what data is fetched
|
|
472
|
+
- Can add filters, sorts, pagination
|
|
473
|
+
|
|
474
|
+
**Cons:**
|
|
475
|
+
- Additional API call required
|
|
476
|
+
- Slightly more complex code
|
|
477
|
+
- Must remember to fetch
|
|
478
|
+
|
|
479
|
+
### Parallel Fetching for Multiple References
|
|
480
|
+
|
|
481
|
+
```tsx
|
|
482
|
+
export default async function Dashboard({ module, locale }: any) {
|
|
483
|
+
const { fields } = await getContentItem({
|
|
484
|
+
contentID: module.contentid,
|
|
485
|
+
languageCode: locale,
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Fetch multiple referenced lists in parallel
|
|
489
|
+
const [testimonials, teamMembers, stats] = await Promise.all([
|
|
490
|
+
getContentList({
|
|
491
|
+
referenceName: fields.testimonials.referencename,
|
|
492
|
+
languageCode: locale,
|
|
493
|
+
}),
|
|
494
|
+
getContentList({
|
|
495
|
+
referenceName: fields.team.referencename,
|
|
496
|
+
languageCode: locale,
|
|
497
|
+
}),
|
|
498
|
+
getContentList({
|
|
499
|
+
referenceName: fields.stats.referencename,
|
|
500
|
+
languageCode: locale,
|
|
501
|
+
}),
|
|
502
|
+
]);
|
|
503
|
+
|
|
504
|
+
return (
|
|
505
|
+
<div>
|
|
506
|
+
{/* Use the fetched data */}
|
|
507
|
+
</div>
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
## Decision Tree: Which Approach?
|
|
513
|
+
|
|
514
|
+
```
|
|
515
|
+
Is the field a Linked Content (Grid) or Content Link (Multi)?
|
|
516
|
+
│
|
|
517
|
+
├─ YES → It's reference-based
|
|
518
|
+
│ │
|
|
519
|
+
│ └─ Must fetch using:
|
|
520
|
+
│ await getContentList({
|
|
521
|
+
│ referenceName: field.referencename,
|
|
522
|
+
│ languageCode: locale
|
|
523
|
+
│ })
|
|
524
|
+
│
|
|
525
|
+
└─ NO → It's auto-populated
|
|
526
|
+
│
|
|
527
|
+
└─ Access directly:
|
|
528
|
+
field.fields.propertyName
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Common Patterns Summary
|
|
532
|
+
|
|
533
|
+
### Pattern 1: Direct Access (Auto-Populated)
|
|
534
|
+
|
|
535
|
+
```tsx
|
|
536
|
+
// Single linked item
|
|
537
|
+
const authorName = post.fields.author.fields.name;
|
|
538
|
+
|
|
539
|
+
// Multiple linked items (array)
|
|
540
|
+
post.fields.tags.map((tag) => tag.fields.name);
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Pattern 2: Fetch by Reference
|
|
544
|
+
|
|
545
|
+
```tsx
|
|
546
|
+
// Get reference name first
|
|
547
|
+
const { fields } = await getContentItem({ contentID, languageCode });
|
|
548
|
+
|
|
549
|
+
// Then fetch the list
|
|
550
|
+
const items = await getContentList({
|
|
551
|
+
referenceName: fields.myList.referencename,
|
|
552
|
+
languageCode,
|
|
553
|
+
});
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Pattern 3: Conditional Fetching
|
|
557
|
+
|
|
558
|
+
```tsx
|
|
559
|
+
// Only fetch if reference exists
|
|
560
|
+
const items = fields.optionalList?.referencename
|
|
561
|
+
? await getContentList({
|
|
562
|
+
referenceName: fields.optionalList.referencename,
|
|
563
|
+
languageCode: locale,
|
|
564
|
+
})
|
|
565
|
+
: [];
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## Debugging Tips
|
|
569
|
+
|
|
570
|
+
### Check What You Have
|
|
571
|
+
|
|
572
|
+
```tsx
|
|
573
|
+
console.log("Field keys:", Object.keys(module.fields.myField));
|
|
574
|
+
|
|
575
|
+
// Auto-populated will show: ['contentID', 'fields', ...]
|
|
576
|
+
// Reference-based will show: ['referencename', 'sortids', ...]
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Verify Data Structure
|
|
580
|
+
|
|
581
|
+
```tsx
|
|
582
|
+
// Auto-populated
|
|
583
|
+
if (module.fields.author?.fields) {
|
|
584
|
+
console.log("✅ Auto-populated linked content");
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Reference-based
|
|
588
|
+
if (module.fields.items?.referencename) {
|
|
589
|
+
console.log("⚠️ Reference-based - need to fetch");
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
## Summary
|
|
594
|
+
|
|
595
|
+
| Aspect | Auto-Populated | Reference-Based |
|
|
596
|
+
|--------|---------------|-----------------|
|
|
597
|
+
| **Field Types** | Content Link, Dropdown, Checkbox, Search List Box | Linked Content (Grid), Content Link (Multi) |
|
|
598
|
+
| **Data Structure** | `{ contentID, fields: {...} }` | `{ referencename: "..." }` |
|
|
599
|
+
| **Access Pattern** | Direct: `field.fields.name` | Fetch: `getContentList({ referenceName })` |
|
|
600
|
+
| **API Calls** | 0 (included) | 1 (separate fetch required) |
|
|
601
|
+
| **When to Use** | Single related items | Lists/grids of items |
|
|
602
|
+
|
|
603
|
+
## Key Takeaways
|
|
604
|
+
|
|
605
|
+
1. ✅ **Auto-populated links** (dropdowns, checkboxes, etc.) - use directly with `.fields`
|
|
606
|
+
2. ⚠️ **Reference-based links** (grids, linked content) - fetch using `.referencename`
|
|
607
|
+
3. 🔍 **Check the field structure** to determine which type you have
|
|
608
|
+
4. 📦 **Use TypeScript** to document which type each field is
|
|
609
|
+
5. ⚡ **Use Promise.all()** when fetching multiple referenced lists
|
|
610
|
+
|
|
611
|
+
Following these patterns ensures correct data access and optimal performance!
|