decoupled-cli 2.3.2 → 2.4.1
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/dist/commands/auth-interactive.d.ts +3 -0
- package/dist/commands/auth-interactive.d.ts.map +1 -0
- package/dist/commands/auth-interactive.js +236 -0
- package/dist/commands/auth-interactive.js.map +1 -0
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +3 -26
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +384 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +18 -6
- package/examples/.cursorrules +0 -189
- package/examples/CLAUDE.md +0 -1184
- package/examples/GEMINI.md +0 -1160
package/examples/GEMINI.md
DELETED
|
@@ -1,1160 +0,0 @@
|
|
|
1
|
-
# GEMINI.md - End-to-End Content Type Development Guide
|
|
2
|
-
|
|
3
|
-
This document provides Gemini with comprehensive instructions for building complete content type implementations from frontend to backend in this Drupal-Next.js project.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
**Architecture**: Headless Drupal backend with Next.js frontend
|
|
8
|
-
**Backend**: Drupal 11 with GraphQL Compose and DC Import API
|
|
9
|
-
**Frontend**: Next.js 15 with TypeScript, Tailwind CSS, and Apollo GraphQL
|
|
10
|
-
**Environment**: DDEV local development
|
|
11
|
-
|
|
12
|
-
## Environment Configuration
|
|
13
|
-
|
|
14
|
-
**Environment Variables (`.env.local`):**
|
|
15
|
-
- `NEXT_PUBLIC_DRUPAL_BASE_URL` - Drupal backend URL
|
|
16
|
-
- `DRUPAL_CLIENT_ID` - OAuth client ID for API authentication
|
|
17
|
-
- `DRUPAL_CLIENT_SECRET` - OAuth client secret for API authentication
|
|
18
|
-
- `DRUPAL_REVALIDATE_SECRET` - Secret for on-demand revalidation
|
|
19
|
-
- `NODE_TLS_REJECT_UNAUTHORIZED=0` - Allow self-signed certificates (development)
|
|
20
|
-
|
|
21
|
-
## DC CLI Setup
|
|
22
|
-
|
|
23
|
-
**First-time CLI setup:**
|
|
24
|
-
```bash
|
|
25
|
-
# 1. Install decoupled-cli locally (if not already present in package.json)
|
|
26
|
-
npm install --save-dev decoupled-cli
|
|
27
|
-
|
|
28
|
-
# 2. Configure authentication - you have two options:
|
|
29
|
-
|
|
30
|
-
# Option A: Use OAuth with your existing Drupal credentials (recommended for local development)
|
|
31
|
-
npx decoupled-cli auth oauth
|
|
32
|
-
|
|
33
|
-
# Option B: Use personal access token (for production/remote deployments)
|
|
34
|
-
npx decoupled-cli auth login
|
|
35
|
-
|
|
36
|
-
# 3. Set your default space for content imports (optional but recommended)
|
|
37
|
-
npx decoupled-cli spaces use <space_id>
|
|
38
|
-
|
|
39
|
-
# 4. Verify authentication and space setup
|
|
40
|
-
npx decoupled-cli spaces current
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Space Management
|
|
44
|
-
|
|
45
|
-
**Creating a new space:**
|
|
46
|
-
```bash
|
|
47
|
-
# Create a space with default type (starter)
|
|
48
|
-
npx decoupled-cli spaces create "My New Space"
|
|
49
|
-
|
|
50
|
-
# Create a space with specific type
|
|
51
|
-
npx decoupled-cli spaces create "Production Site" --type pro
|
|
52
|
-
npx decoupled-cli spaces create "Enterprise App" --type premium
|
|
53
|
-
|
|
54
|
-
# Available types: starter, pro, premium
|
|
55
|
-
|
|
56
|
-
# Create with description
|
|
57
|
-
npx decoupled-cli spaces create "E-commerce Platform" \
|
|
58
|
-
--type premium \
|
|
59
|
-
--description "Main production e-commerce site"
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
**Quick Start with AI (One-Shot Workflow) ⚡:**
|
|
63
|
-
|
|
64
|
-
The fastest way to get started! Create a space with AI-generated content types and sample data from a single prompt:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
# One command to create space + content
|
|
68
|
-
npx decoupled-cli spaces quick-start "blog about coffee"
|
|
69
|
-
|
|
70
|
-
# Preview AI-generated content first
|
|
71
|
-
npx decoupled-cli spaces quick-start "e-commerce store" --preview
|
|
72
|
-
|
|
73
|
-
# Custom configuration
|
|
74
|
-
npx decoupled-cli spaces quick-start "portfolio site" \
|
|
75
|
-
--name "my-portfolio" \
|
|
76
|
-
--type premium
|
|
77
|
-
|
|
78
|
-
# Add to existing space
|
|
79
|
-
npx decoupled-cli spaces quick-start "add products" --space-id 123
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
**What it does:**
|
|
83
|
-
1. 🤖 AI generates content type schema + sample data from your prompt
|
|
84
|
-
2. 🚀 Creates space (or uses `--space-id`)
|
|
85
|
-
3. ⏳ Waits for space to be ready
|
|
86
|
-
4. 📥 Imports generated content automatically
|
|
87
|
-
5. 🎉 Displays GraphQL endpoint and next steps
|
|
88
|
-
|
|
89
|
-
**Benefits:**
|
|
90
|
-
- ✅ No JSON writing - just describe what you want
|
|
91
|
-
- ✅ Instant prototypes for demos and POCs
|
|
92
|
-
- ✅ Learns from examples/content-import-sample.json patterns
|
|
93
|
-
- ✅ Uses centrally-managed AI (no API keys needed)
|
|
94
|
-
- ✅ Works with PAT authentication only
|
|
95
|
-
|
|
96
|
-
**Example use cases:**
|
|
97
|
-
- `"blog with posts about travel"` → Creates post content type with sample travel articles
|
|
98
|
-
- `"e-commerce with products"` → Creates product content type with pricing, images, inventory
|
|
99
|
-
- `"events calendar"` → Creates event content type with dates, locations, speakers
|
|
100
|
-
- `"team directory"` → Creates team_member content type with bios, photos, roles
|
|
101
|
-
|
|
102
|
-
**Listing and managing spaces:**
|
|
103
|
-
```bash
|
|
104
|
-
# List all spaces
|
|
105
|
-
npx decoupled-cli spaces list
|
|
106
|
-
|
|
107
|
-
# List with detailed information
|
|
108
|
-
npx decoupled-cli spaces list --detailed
|
|
109
|
-
|
|
110
|
-
# Get space details by ID or name
|
|
111
|
-
npx decoupled-cli spaces get 123
|
|
112
|
-
npx decoupled-cli spaces get my-space-name
|
|
113
|
-
|
|
114
|
-
# Check space status
|
|
115
|
-
npx decoupled-cli spaces status 123
|
|
116
|
-
|
|
117
|
-
# Set default space for operations
|
|
118
|
-
npx decoupled-cli spaces use 123
|
|
119
|
-
npx decoupled-cli spaces current
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
**Other space operations:**
|
|
123
|
-
```bash
|
|
124
|
-
# Clone an existing space
|
|
125
|
-
npx decoupled-cli spaces clone 123 --name "Development Copy"
|
|
126
|
-
|
|
127
|
-
# Update space name or type
|
|
128
|
-
npx decoupled-cli spaces update 123 --name "Updated Name"
|
|
129
|
-
npx decoupled-cli spaces update 123 --type premium
|
|
130
|
-
|
|
131
|
-
# Get Drupal admin login link
|
|
132
|
-
npx decoupled-cli spaces login 123
|
|
133
|
-
|
|
134
|
-
# Archive/unarchive spaces
|
|
135
|
-
npx decoupled-cli spaces archive 123
|
|
136
|
-
npx decoupled-cli spaces unarchive 123
|
|
137
|
-
|
|
138
|
-
# Delete space (with confirmation)
|
|
139
|
-
npx decoupled-cli spaces delete 123
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
**OAuth Prerequisites (for `decoupled-cli auth oauth`):**
|
|
143
|
-
Your `.env.local` must contain:
|
|
144
|
-
- `NEXT_PUBLIC_DRUPAL_BASE_URL=https://your-space.decoupled.io`
|
|
145
|
-
- `DRUPAL_CLIENT_ID=your_oauth_client_id`
|
|
146
|
-
- `DRUPAL_CLIENT_SECRET=your_oauth_client_secret`
|
|
147
|
-
|
|
148
|
-
**Authentication Method Differences:**
|
|
149
|
-
- **Personal Access Token (PAT)** → Works with Decoupled Drupal platform API (spaces, users, organizations, **content import via platform proxy**)
|
|
150
|
-
- **OAuth** → Direct Drupal site API access (alternative method for content import, local development)
|
|
151
|
-
|
|
152
|
-
**If CLI is not available locally:**
|
|
153
|
-
- For projects with decoupled-cli in package.json: Run `npm install` then use `npx decoupled-cli`
|
|
154
|
-
- For development: `cd cli && npm install && npm run build && npm link` then use `decoupled-cli`
|
|
155
|
-
- Always prefer using `npx decoupled-cli` for consistency and local package management
|
|
156
|
-
|
|
157
|
-
## End-to-End Development Workflow
|
|
158
|
-
|
|
159
|
-
When asked to create a new content type (e.g., "create a product page"), follow these steps:
|
|
160
|
-
|
|
161
|
-
**Recommended Workflow (PAT + Platform Proxy - Contentful-like simplicity):**
|
|
162
|
-
Use a single PAT token for all operations including content import via the platform proxy.
|
|
163
|
-
|
|
164
|
-
### 1. Content Analysis & Planning
|
|
165
|
-
|
|
166
|
-
**Analyze the request and determine:**
|
|
167
|
-
- Content type name and machine name
|
|
168
|
-
- Required fields and their types
|
|
169
|
-
- Frontend components needed
|
|
170
|
-
- URL structure and routing
|
|
171
|
-
- Display requirements (listing, detail pages, etc.)
|
|
172
|
-
|
|
173
|
-
**Create a todo list for tracking progress:**
|
|
174
|
-
```markdown
|
|
175
|
-
1. Verify DC CLI authentication (npx decoupled-cli auth status)
|
|
176
|
-
2. Create DC Import JSON for [content_type]
|
|
177
|
-
3. Import content type and sample content via platform (npx decoupled-cli spaces content-import <space_id> --file import.json)
|
|
178
|
-
4. **CRITICAL**: Run schema generation (`npm run generate-schema`) to update GraphQL schema
|
|
179
|
-
5. Create TypeScript types and GraphQL queries
|
|
180
|
-
6. Build frontend components ([ContentCard], [ContentRenderer])
|
|
181
|
-
7. Create listing and detail pages
|
|
182
|
-
8. Test build process and fix errors
|
|
183
|
-
9. Validate end-to-end functionality
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
**Alternative Workflow (OAuth + Direct Drupal API):**
|
|
187
|
-
```markdown
|
|
188
|
-
1. Set up OAuth authentication (npx decoupled-cli auth oauth)
|
|
189
|
-
2. Create DC Import JSON for [content_type]
|
|
190
|
-
3. Import content type and sample content to Drupal directly (npx decoupled-cli content import --file import.json)
|
|
191
|
-
4. **CRITICAL**: Run schema generation (`npm run generate-schema`) to update GraphQL schema
|
|
192
|
-
5. Create TypeScript types and GraphQL queries
|
|
193
|
-
6. Build frontend components ([ContentCard], [ContentRenderer])
|
|
194
|
-
7. Create listing and detail pages
|
|
195
|
-
8. Test build process and fix errors
|
|
196
|
-
9. Validate end-to-end functionality
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### 0. CLI Authentication Check
|
|
200
|
-
|
|
201
|
-
**ALWAYS start by verifying CLI setup:**
|
|
202
|
-
```bash
|
|
203
|
-
# Check if CLI is authenticated
|
|
204
|
-
npx decoupled-cli auth status
|
|
205
|
-
|
|
206
|
-
# If not authenticated, choose authentication method:
|
|
207
|
-
# For local development with Drupal OAuth:
|
|
208
|
-
npx decoupled-cli auth oauth
|
|
209
|
-
|
|
210
|
-
# For production with personal access token:
|
|
211
|
-
npx decoupled-cli auth login
|
|
212
|
-
|
|
213
|
-
# List available spaces and set default
|
|
214
|
-
npx decoupled-cli spaces list
|
|
215
|
-
npx decoupled-cli spaces use <space_id>
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
**If CLI authentication fails:**
|
|
219
|
-
- For OAuth: Verify `.env.local` contains `NEXT_PUBLIC_DRUPAL_BASE_URL`, `DRUPAL_CLIENT_ID`, `DRUPAL_CLIENT_SECRET`
|
|
220
|
-
- For personal tokens: Check that DC platform is accessible
|
|
221
|
-
- Ensure user has proper permissions for the operations you're trying to perform
|
|
222
|
-
|
|
223
|
-
**Important: Command Availability by Authentication Method**
|
|
224
|
-
- **OAuth commands**: `content import`, `content status`, `auth status`
|
|
225
|
-
- **PAT-only commands**: `spaces list`, `spaces use`, `usage`, `org info`
|
|
226
|
-
- **Universal commands**: `auth login`, `auth oauth`, `auth test`
|
|
227
|
-
|
|
228
|
-
### 2. DC Import JSON Creation
|
|
229
|
-
|
|
230
|
-
**Get example format with CLI:**
|
|
231
|
-
```bash
|
|
232
|
-
npx decoupled-cli spaces content-import --example > my-content-type.json
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
**Use this JSON structure:**
|
|
236
|
-
|
|
237
|
-
```json
|
|
238
|
-
{
|
|
239
|
-
"model": [
|
|
240
|
-
{
|
|
241
|
-
"bundle": "content_type_name",
|
|
242
|
-
"description": "Description of the content type",
|
|
243
|
-
"label": "Content Type Label",
|
|
244
|
-
"body": true,
|
|
245
|
-
"fields": [
|
|
246
|
-
{
|
|
247
|
-
"id": "field_name",
|
|
248
|
-
"label": "Field Label",
|
|
249
|
-
"type": "text|string|image|datetime|bool|text[]"
|
|
250
|
-
}
|
|
251
|
-
]
|
|
252
|
-
}
|
|
253
|
-
],
|
|
254
|
-
"content": [
|
|
255
|
-
{
|
|
256
|
-
"id": "item1",
|
|
257
|
-
"type": "node.content_type_name",
|
|
258
|
-
"path": "/content-type/item-slug",
|
|
259
|
-
"values": {
|
|
260
|
-
"title": "Item Title",
|
|
261
|
-
"body": "<p>Body content</p>",
|
|
262
|
-
"field_name": "field_value"
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
]
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
**Field Type Reference:**
|
|
270
|
-
- `text` - Long text with formatting
|
|
271
|
-
- `string` - Short plain text (max 255 chars)
|
|
272
|
-
- `image` - Image upload
|
|
273
|
-
- `datetime` - Date and time
|
|
274
|
-
- `bool` - Boolean true/false
|
|
275
|
-
- `text[]` - Multiple text values
|
|
276
|
-
- `string[]` - Multiple string values
|
|
277
|
-
|
|
278
|
-
**Important Notes:**
|
|
279
|
-
- Field IDs become `field_[id]` in Drupal (e.g., `"id": "price"` → `field_price`)
|
|
280
|
-
- **CRITICAL**: In content values, use the field ID directly without "field_" prefix (e.g., `"price": "$299.99"` NOT `"field_price": "$299.99"`)
|
|
281
|
-
- Use `"body": true` to include standard body field
|
|
282
|
-
- Always include sample content for testing
|
|
283
|
-
- Path aliases should follow `/content-type/slug` pattern
|
|
284
|
-
- **For image fields**: Use full URLs with the Drupal domain from `.env.local`, not relative paths:
|
|
285
|
-
```json
|
|
286
|
-
"featured_image": {
|
|
287
|
-
"uri": "${DRUPAL_BASE_URL}/modules/custom/dc_import/resources/placeholder.png",
|
|
288
|
-
"alt": "Description of the image",
|
|
289
|
-
"title": "Image title"
|
|
290
|
-
}
|
|
291
|
-
```
|
|
292
|
-
Always read the `NEXT_PUBLIC_DRUPAL_BASE_URL` from `.env.local` and use that as the base for image URIs to ensure images load correctly from the Drupal backend.
|
|
293
|
-
|
|
294
|
-
### 3. Import via DC CLI
|
|
295
|
-
|
|
296
|
-
**Import Content Type (Recommended - PAT Only):**
|
|
297
|
-
```bash
|
|
298
|
-
# Import via platform proxy using PAT token (Contentful-like simplicity)
|
|
299
|
-
# No OAuth credentials needed - just your PAT token!
|
|
300
|
-
npx decoupled-cli spaces content-import <space_id> --file content-type-import.json
|
|
301
|
-
|
|
302
|
-
# Or let the CLI use your default space
|
|
303
|
-
npx decoupled-cli spaces content-import --file content-type-import.json
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
**Alternative: Direct Drupal Import (OAuth Required):**
|
|
307
|
-
```bash
|
|
308
|
-
# Import directly to your Drupal site (requires OAuth authentication)
|
|
309
|
-
npx decoupled-cli content import --file content-type-import.json
|
|
310
|
-
|
|
311
|
-
# Or preview first to see what will be imported
|
|
312
|
-
npx decoupled-cli content import --file content-type-import.json --preview
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
**Generate example import file:**
|
|
316
|
-
```bash
|
|
317
|
-
# Get example import JSON structure (works with both methods)
|
|
318
|
-
npx decoupled-cli content import --example > content-type-import.json
|
|
319
|
-
# Then edit the generated file with your content type definition
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**Always check the response for success and note:**
|
|
323
|
-
- Content type machine name (may differ from input)
|
|
324
|
-
- Field machine names (auto-generated)
|
|
325
|
-
- Node IDs created
|
|
326
|
-
- GraphQL schema updates
|
|
327
|
-
- **Important**: GraphQL field names may differ from DC field IDs (check actual schema)
|
|
328
|
-
|
|
329
|
-
**CRITICAL: Immediately Update GraphQL Schema After Import:**
|
|
330
|
-
```bash
|
|
331
|
-
# Generate updated GraphQL schema to include new content type
|
|
332
|
-
npm run generate-schema
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
**Verify the schema includes your content type:**
|
|
336
|
-
```bash
|
|
337
|
-
# Check that your content type appears in the generated schema
|
|
338
|
-
grep -i [your_content_type] schema/schema.graphql
|
|
339
|
-
|
|
340
|
-
# Example for products:
|
|
341
|
-
grep -i product schema/schema.graphql
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
**Test that content was imported successfully:**
|
|
345
|
-
```bash
|
|
346
|
-
# Check that new content type appears in generated schema
|
|
347
|
-
npm run generate-schema
|
|
348
|
-
grep -i [your_content_type] schema/schema.graphql
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
### 4. Frontend Implementation
|
|
352
|
-
|
|
353
|
-
**File Structure:**
|
|
354
|
-
```
|
|
355
|
-
app/
|
|
356
|
-
├── components/
|
|
357
|
-
│ ├── [ContentType]Card.tsx # List view component
|
|
358
|
-
│ ├── [ContentType]Renderer.tsx # Detail view component
|
|
359
|
-
│ └── DynamicIcon.tsx # Icon component (if needed)
|
|
360
|
-
├── [content-type]/
|
|
361
|
-
│ └── page.tsx # Listing page
|
|
362
|
-
├── [...slug]/
|
|
363
|
-
│ └── page.tsx # Dynamic routing (update)
|
|
364
|
-
lib/
|
|
365
|
-
├── queries.ts # GraphQL queries (update)
|
|
366
|
-
└── types.ts # TypeScript types (update)
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
#### GraphQL Queries
|
|
370
|
-
|
|
371
|
-
**Add to `lib/queries.ts`:**
|
|
372
|
-
```typescript
|
|
373
|
-
export const GET_CONTENT_TYPES = gql`
|
|
374
|
-
query GetContentTypes($first: Int = 10) {
|
|
375
|
-
node[ContentType]s(first: $first, sortKey: CREATED_AT) {
|
|
376
|
-
nodes {
|
|
377
|
-
id
|
|
378
|
-
title
|
|
379
|
-
path
|
|
380
|
-
created { timestamp }
|
|
381
|
-
changed { timestamp }
|
|
382
|
-
... on Node[ContentType] {
|
|
383
|
-
body { processed }
|
|
384
|
-
field[FieldName] { processed }
|
|
385
|
-
// Add all fields
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
`
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
**Update `GET_NODE_BY_PATH` to include new content type:**
|
|
394
|
-
```typescript
|
|
395
|
-
... on Node[ContentType] {
|
|
396
|
-
id
|
|
397
|
-
title
|
|
398
|
-
body { processed }
|
|
399
|
-
field[FieldName] { processed }
|
|
400
|
-
// Add all fields
|
|
401
|
-
}
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
#### TypeScript Types
|
|
405
|
-
|
|
406
|
-
**Add to `lib/types.ts`:**
|
|
407
|
-
```typescript
|
|
408
|
-
export interface Drupal[ContentType] extends DrupalNode {
|
|
409
|
-
body?: { processed: string }
|
|
410
|
-
field[FieldName]?: { processed: string }
|
|
411
|
-
// Add all fields with proper types
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
export interface [ContentType]Data {
|
|
415
|
-
node[ContentType]s: {
|
|
416
|
-
nodes: Drupal[ContentType][]
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
#### Component Templates
|
|
422
|
-
|
|
423
|
-
**Card Component (`[ContentType]Card.tsx`):**
|
|
424
|
-
```typescript
|
|
425
|
-
import Link from 'next/link'
|
|
426
|
-
import { Drupal[ContentType] } from '@/lib/types'
|
|
427
|
-
|
|
428
|
-
interface [ContentType]CardProps {
|
|
429
|
-
[contentType]: Drupal[ContentType]
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
export default function [ContentType]Card({ [contentType] }: [ContentType]CardProps) {
|
|
433
|
-
return (
|
|
434
|
-
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden hover:shadow-md transition-shadow duration-200">
|
|
435
|
-
<div className="p-6">
|
|
436
|
-
<h3 className="text-xl font-semibold text-gray-900 mb-2">
|
|
437
|
-
{[contentType].title}
|
|
438
|
-
</h3>
|
|
439
|
-
|
|
440
|
-
{/* Add field displays */}
|
|
441
|
-
{/* For text[] fields with HTML content, use dangerouslySetInnerHTML */}
|
|
442
|
-
{/* Example: <span dangerouslySetInnerHTML={{ __html: field.processed }} /> */}
|
|
443
|
-
|
|
444
|
-
<Link
|
|
445
|
-
href={[contentType].path || `/[content-type]/${[contentType].id}`}
|
|
446
|
-
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 transition-colors duration-200"
|
|
447
|
-
>
|
|
448
|
-
Learn More
|
|
449
|
-
</Link>
|
|
450
|
-
</div>
|
|
451
|
-
</div>
|
|
452
|
-
)
|
|
453
|
-
}
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
**Renderer Component (`[ContentType]Renderer.tsx`):**
|
|
457
|
-
```typescript
|
|
458
|
-
import Header from './Header'
|
|
459
|
-
import { Drupal[ContentType] } from '@/lib/types'
|
|
460
|
-
|
|
461
|
-
interface [ContentType]RendererProps {
|
|
462
|
-
[contentType]: Drupal[ContentType]
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
export default function [ContentType]Renderer({ [contentType] }: [ContentType]RendererProps) {
|
|
466
|
-
return (
|
|
467
|
-
<div className="min-h-screen bg-gray-50">
|
|
468
|
-
<Header />
|
|
469
|
-
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
470
|
-
<article className="bg-white rounded-lg shadow-sm overflow-hidden">
|
|
471
|
-
<div className="p-8">
|
|
472
|
-
<h1 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
|
|
473
|
-
{[contentType].title}
|
|
474
|
-
</h1>
|
|
475
|
-
|
|
476
|
-
{/* Add field displays */}
|
|
477
|
-
{/* For text[] fields with HTML content, use dangerouslySetInnerHTML */}
|
|
478
|
-
{/* Example: <span dangerouslySetInnerHTML={{ __html: field.processed }} /> */}
|
|
479
|
-
|
|
480
|
-
{[contentType].body?.processed && (
|
|
481
|
-
<div
|
|
482
|
-
className="prose prose-lg max-w-none mt-8"
|
|
483
|
-
dangerouslySetInnerHTML={{ __html: [contentType].body.processed }}
|
|
484
|
-
/>
|
|
485
|
-
)}
|
|
486
|
-
</div>
|
|
487
|
-
</article>
|
|
488
|
-
</main>
|
|
489
|
-
</div>
|
|
490
|
-
)
|
|
491
|
-
}
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
#### Listing Page
|
|
495
|
-
|
|
496
|
-
**Create `app/[content-type]/page.tsx`:**
|
|
497
|
-
```typescript
|
|
498
|
-
import Header from '../components/Header'
|
|
499
|
-
import [ContentType]Card from '../components/[ContentType]Card'
|
|
500
|
-
import { GET_CONTENT_TYPES } from '@/lib/queries'
|
|
501
|
-
import { getServerApolloClient } from '@/lib/apollo-client'
|
|
502
|
-
import { [ContentType]Data } from '@/lib/types'
|
|
503
|
-
|
|
504
|
-
export const revalidate = 300
|
|
505
|
-
|
|
506
|
-
export default async function [ContentType]sPage() {
|
|
507
|
-
const apollo = getServerApolloClient(await headers())
|
|
508
|
-
|
|
509
|
-
try {
|
|
510
|
-
const { data } = await apollo.query<[ContentType]Data>({
|
|
511
|
-
query: GET_CONTENT_TYPES,
|
|
512
|
-
variables: { first: 20 },
|
|
513
|
-
fetchPolicy: 'no-cache'
|
|
514
|
-
})
|
|
515
|
-
|
|
516
|
-
const [contentType]s = data?.node[ContentType]s?.nodes || []
|
|
517
|
-
|
|
518
|
-
return (
|
|
519
|
-
<div className="min-h-screen bg-gray-50">
|
|
520
|
-
<Header />
|
|
521
|
-
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
522
|
-
<div className="text-center mb-12">
|
|
523
|
-
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
|
|
524
|
-
[Content Types]
|
|
525
|
-
</h1>
|
|
526
|
-
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
|
527
|
-
Description of content type listing
|
|
528
|
-
</p>
|
|
529
|
-
</div>
|
|
530
|
-
|
|
531
|
-
{[contentType]s.length > 0 ? (
|
|
532
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
533
|
-
{[contentType]s.map(([contentType]) => (
|
|
534
|
-
<[ContentType]Card key={[contentType].id} [contentType]={[contentType]} />
|
|
535
|
-
))}
|
|
536
|
-
</div>
|
|
537
|
-
) : (
|
|
538
|
-
<div className="text-center py-16">
|
|
539
|
-
<p className="text-gray-600">No [content types] available.</p>
|
|
540
|
-
</div>
|
|
541
|
-
)}
|
|
542
|
-
</main>
|
|
543
|
-
</div>
|
|
544
|
-
)
|
|
545
|
-
} catch (error) {
|
|
546
|
-
console.error('Error loading [content types]:', error)
|
|
547
|
-
// Return error state
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
#### Update Dynamic Routing
|
|
553
|
-
|
|
554
|
-
**Add to `app/[...slug]/page.tsx`:**
|
|
555
|
-
```typescript
|
|
556
|
-
// In generateMetadata and page functions, add:
|
|
557
|
-
... on Node[ContentType] {
|
|
558
|
-
id
|
|
559
|
-
title
|
|
560
|
-
// Add fields needed for display
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// In the page component return:
|
|
564
|
-
if (node.__typename === 'Node[ContentType]') {
|
|
565
|
-
return <[ContentType]Renderer [contentType]={node as Drupal[ContentType]} />
|
|
566
|
-
}
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
#### Update Navigation
|
|
570
|
-
|
|
571
|
-
**Add to `app/components/Header.tsx`:**
|
|
572
|
-
```typescript
|
|
573
|
-
// Add navigation link
|
|
574
|
-
<Link
|
|
575
|
-
href="/[content-type]"
|
|
576
|
-
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
577
|
-
pathname === '/[content-type]' ? 'bg-blue-100 text-blue-700' : 'text-gray-700 hover:text-blue-600'
|
|
578
|
-
}`}
|
|
579
|
-
>
|
|
580
|
-
[Content Types]
|
|
581
|
-
</Link>
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
### 5. Build and Test Process
|
|
585
|
-
|
|
586
|
-
**Always run these commands in sequence:**
|
|
587
|
-
|
|
588
|
-
1. **Build the application:**
|
|
589
|
-
```bash
|
|
590
|
-
npm run build
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
2. **Fix any TypeScript/build errors** that appear
|
|
594
|
-
|
|
595
|
-
3. **Start development server:**
|
|
596
|
-
```bash
|
|
597
|
-
npm run dev
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
4. **Test endpoints:**
|
|
601
|
-
- Listing page: `http://localhost:3001/[content-type]`
|
|
602
|
-
- Detail pages: `http://localhost:3001/[content-type]/[slug]`
|
|
603
|
-
|
|
604
|
-
### 6. Testing Checklist
|
|
605
|
-
|
|
606
|
-
**Verify each step:**
|
|
607
|
-
- [ ] DC import successful with no errors
|
|
608
|
-
- [ ] GraphQL schema includes new content type
|
|
609
|
-
- [ ] TypeScript types are properly defined
|
|
610
|
-
- [ ] Build process completes without errors
|
|
611
|
-
- [ ] Listing page displays content
|
|
612
|
-
- [ ] Detail pages render correctly
|
|
613
|
-
- [ ] Navigation links work
|
|
614
|
-
- [ ] Responsive design functions
|
|
615
|
-
- [ ] Error states handled gracefully
|
|
616
|
-
|
|
617
|
-
## Common Content Type Patterns
|
|
618
|
-
|
|
619
|
-
### E-commerce Product
|
|
620
|
-
```json
|
|
621
|
-
{
|
|
622
|
-
"id": "price",
|
|
623
|
-
"label": "Price",
|
|
624
|
-
"type": "string"
|
|
625
|
-
},
|
|
626
|
-
{
|
|
627
|
-
"id": "product_images",
|
|
628
|
-
"label": "Product Images",
|
|
629
|
-
"type": "image[]"
|
|
630
|
-
},
|
|
631
|
-
{
|
|
632
|
-
"id": "in_stock",
|
|
633
|
-
"label": "In Stock",
|
|
634
|
-
"type": "bool"
|
|
635
|
-
},
|
|
636
|
-
{
|
|
637
|
-
"id": "features",
|
|
638
|
-
"label": "Key Features",
|
|
639
|
-
"type": "string[]"
|
|
640
|
-
}
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
**Sample content with image URI:**
|
|
644
|
-
```json
|
|
645
|
-
"product_images": [
|
|
646
|
-
{
|
|
647
|
-
"uri": "${DRUPAL_BASE_URL}/modules/custom/dc_import/resources/placeholder.png",
|
|
648
|
-
"alt": "Product showcase image",
|
|
649
|
-
"title": "Product Image"
|
|
650
|
-
}
|
|
651
|
-
]
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
### Event/Conference
|
|
655
|
-
```json
|
|
656
|
-
{
|
|
657
|
-
"id": "event_date",
|
|
658
|
-
"label": "Event Date",
|
|
659
|
-
"type": "datetime"
|
|
660
|
-
},
|
|
661
|
-
{
|
|
662
|
-
"id": "location",
|
|
663
|
-
"label": "Location",
|
|
664
|
-
"type": "string"
|
|
665
|
-
},
|
|
666
|
-
{
|
|
667
|
-
"id": "speakers",
|
|
668
|
-
"label": "Speakers",
|
|
669
|
-
"type": "string[]"
|
|
670
|
-
}
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
### Team Member
|
|
674
|
-
```json
|
|
675
|
-
{
|
|
676
|
-
"id": "position",
|
|
677
|
-
"label": "Position",
|
|
678
|
-
"type": "string"
|
|
679
|
-
},
|
|
680
|
-
{
|
|
681
|
-
"id": "profile_image",
|
|
682
|
-
"label": "Profile Image",
|
|
683
|
-
"type": "image"
|
|
684
|
-
},
|
|
685
|
-
{
|
|
686
|
-
"id": "bio",
|
|
687
|
-
"label": "Biography",
|
|
688
|
-
"type": "text"
|
|
689
|
-
}
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
**Sample content with image URI:**
|
|
693
|
-
```json
|
|
694
|
-
"profile_image": {
|
|
695
|
-
"uri": "${DRUPAL_BASE_URL}/modules/custom/dc_import/resources/placeholder.png",
|
|
696
|
-
"alt": "Team member headshot",
|
|
697
|
-
"title": "Profile Photo"
|
|
698
|
-
}
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
### Portfolio/Case Study
|
|
702
|
-
```json
|
|
703
|
-
{
|
|
704
|
-
"id": "project_url",
|
|
705
|
-
"label": "Project URL",
|
|
706
|
-
"type": "string"
|
|
707
|
-
},
|
|
708
|
-
{
|
|
709
|
-
"id": "technologies",
|
|
710
|
-
"label": "Technologies Used",
|
|
711
|
-
"type": "string[]"
|
|
712
|
-
},
|
|
713
|
-
{
|
|
714
|
-
"id": "project_images",
|
|
715
|
-
"label": "Project Images",
|
|
716
|
-
"type": "image[]"
|
|
717
|
-
}
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
**Sample content with image URIs:**
|
|
721
|
-
```json
|
|
722
|
-
"project_images": [
|
|
723
|
-
{
|
|
724
|
-
"uri": "${DRUPAL_BASE_URL}/modules/custom/dc_import/resources/placeholder.png",
|
|
725
|
-
"alt": "Project screenshot showing main interface",
|
|
726
|
-
"title": "Project Interface"
|
|
727
|
-
}
|
|
728
|
-
]
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
## Troubleshooting
|
|
732
|
-
|
|
733
|
-
### Common Issues
|
|
734
|
-
|
|
735
|
-
**1. DC Import Fails**
|
|
736
|
-
- Check OAuth token expiration
|
|
737
|
-
- Verify JSON structure matches `schema/sample.json` format
|
|
738
|
-
- Ensure field IDs don't start with `field_`
|
|
739
|
-
- **Critical**: In content values, use field ID without "field_" prefix (e.g., `"price": "$299.99"` not `"field_price": "$299.99"`)
|
|
740
|
-
|
|
741
|
-
**2. GraphQL Errors**
|
|
742
|
-
- Check if content type was created successfully in Drupal
|
|
743
|
-
- Verify GraphQL Compose configuration
|
|
744
|
-
- Clear GraphQL cache in Drupal admin
|
|
745
|
-
|
|
746
|
-
**3. Build Errors**
|
|
747
|
-
- Check TypeScript type definitions
|
|
748
|
-
- Ensure all imports are correct
|
|
749
|
-
- Verify GraphQL query syntax
|
|
750
|
-
|
|
751
|
-
**4. Content Not Displaying**
|
|
752
|
-
- Check GraphQL query field names match Drupal fields (may not match DC field IDs)
|
|
753
|
-
- Verify content was created and published
|
|
754
|
-
- Check query variables and pagination
|
|
755
|
-
- For HTML content showing raw tags, use `dangerouslySetInnerHTML={{ __html: field.processed }}`
|
|
756
|
-
|
|
757
|
-
**5. GraphQL Schema Not Updated After DC Import**
|
|
758
|
-
- **Critical Issue**: DC imports create content types successfully but GraphQL schema may not update immediately
|
|
759
|
-
- Content exists in Drupal but `nodeProducts` query returns "field not found" errors
|
|
760
|
-
- This is the most common issue with DC imports
|
|
761
|
-
|
|
762
|
-
**Solution Process**:
|
|
763
|
-
1. Clear Drupal caches (if you have admin access)
|
|
764
|
-
2. **Always run schema generation script**: `npm run generate-schema`
|
|
765
|
-
3. This regenerates the GraphQL schema files and validates the schema is updated
|
|
766
|
-
4. Test queries again after schema regeneration
|
|
767
|
-
|
|
768
|
-
### Essential Schema Generation Workflow
|
|
769
|
-
|
|
770
|
-
**CRITICAL**: Always run schema generation after DC imports to ensure GraphQL integration works properly.
|
|
771
|
-
|
|
772
|
-
```bash
|
|
773
|
-
# Generate updated GraphQL schema after content type imports
|
|
774
|
-
npm run generate-schema
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
This command:
|
|
778
|
-
- Authenticates with Drupal using OAuth
|
|
779
|
-
- Performs GraphQL introspection to get the current schema
|
|
780
|
-
- Generates updated schema files in `/schema/` directory
|
|
781
|
-
- Validates that new content types are available in GraphQL
|
|
782
|
-
|
|
783
|
-
**Add this to your workflow**:
|
|
784
|
-
1. Import content type via DC API
|
|
785
|
-
2. **Immediately run**: `npm run generate-schema`
|
|
786
|
-
3. Check generated schema includes your new content type
|
|
787
|
-
4. Test GraphQL queries
|
|
788
|
-
5. Proceed with frontend implementation
|
|
789
|
-
|
|
790
|
-
### Debug Commands
|
|
791
|
-
|
|
792
|
-
```bash
|
|
793
|
-
# Check CLI authentication status
|
|
794
|
-
npx decoupled-cli auth status
|
|
795
|
-
|
|
796
|
-
# OAUTH/DRUPAL COMMANDS:
|
|
797
|
-
# Check content import API status
|
|
798
|
-
npx decoupled-cli content status
|
|
799
|
-
|
|
800
|
-
# Preview content import without applying changes
|
|
801
|
-
npx decoupled-cli content import --file your-import.json --preview
|
|
802
|
-
|
|
803
|
-
# Import content to Drupal
|
|
804
|
-
npx decoupled-cli content import --file your-import.json
|
|
805
|
-
|
|
806
|
-
# Generate fresh schema (most important after any import)
|
|
807
|
-
npm run generate-schema
|
|
808
|
-
|
|
809
|
-
# Check schema includes your new content type
|
|
810
|
-
npm run generate-schema
|
|
811
|
-
grep -i [your_content_type] schema/schema.graphql
|
|
812
|
-
|
|
813
|
-
# PAT/PLATFORM COMMANDS (require npx decoupled-cli auth login):
|
|
814
|
-
# List available spaces
|
|
815
|
-
npx decoupled-cli spaces list
|
|
816
|
-
|
|
817
|
-
# Check current default space
|
|
818
|
-
npx decoupled-cli spaces current
|
|
819
|
-
|
|
820
|
-
# Get health status of DC platform
|
|
821
|
-
npx decoupled-cli health check
|
|
822
|
-
|
|
823
|
-
# Check organization info
|
|
824
|
-
npx decoupled-cli org info
|
|
825
|
-
|
|
826
|
-
# Legacy platform content import
|
|
827
|
-
npx decoupled-cli spaces content-import --file your-import.json --preview
|
|
828
|
-
npx decoupled-cli spaces content-import --file your-import.json --json
|
|
829
|
-
```
|
|
830
|
-
|
|
831
|
-
## Content Import Command Reference
|
|
832
|
-
|
|
833
|
-
### OAuth + Drupal API Commands
|
|
834
|
-
|
|
835
|
-
**Check content import API status:**
|
|
836
|
-
```bash
|
|
837
|
-
npx decoupled-cli content status
|
|
838
|
-
```
|
|
839
|
-
|
|
840
|
-
**Generate example import JSON:**
|
|
841
|
-
```bash
|
|
842
|
-
npx decoupled-cli content import --example > my-content-type.json
|
|
843
|
-
```
|
|
844
|
-
|
|
845
|
-
**Preview import (dry run):**
|
|
846
|
-
```bash
|
|
847
|
-
npx decoupled-cli content import --file my-content-type.json --preview
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
**Import content to Drupal:**
|
|
851
|
-
```bash
|
|
852
|
-
npx decoupled-cli content import --file my-content-type.json
|
|
853
|
-
```
|
|
854
|
-
|
|
855
|
-
### Authentication Setup for Content Import
|
|
856
|
-
|
|
857
|
-
**Quick OAuth Setup (recommended for local development):**
|
|
858
|
-
```bash
|
|
859
|
-
# 1. Ensure .env.local has OAuth credentials
|
|
860
|
-
# 2. Set up OAuth profile
|
|
861
|
-
npx decoupled-cli auth oauth
|
|
862
|
-
|
|
863
|
-
# 3. Verify it's working
|
|
864
|
-
npx decoupled-cli content status
|
|
865
|
-
|
|
866
|
-
# 4. Import content
|
|
867
|
-
npx decoupled-cli content import --file your-content.json
|
|
868
|
-
```
|
|
869
|
-
|
|
870
|
-
**When to use PAT vs OAuth:**
|
|
871
|
-
- **PAT (Recommended)**: All operations including content import, space management, production deployments - **just like Contentful's CMA token**
|
|
872
|
-
- **OAuth**: Alternative method for direct Drupal API access, local development with existing Drupal credentials
|
|
873
|
-
|
|
874
|
-
## Best Practices
|
|
875
|
-
|
|
876
|
-
1. **Always create sample content** during import for immediate testing
|
|
877
|
-
2. **Use descriptive field names** that are clear and consistent
|
|
878
|
-
3. **Follow existing naming conventions** in the codebase
|
|
879
|
-
4. **Test responsive design** on different screen sizes
|
|
880
|
-
5. **Handle empty/missing data gracefully** in components
|
|
881
|
-
6. **Use semantic HTML** for accessibility
|
|
882
|
-
7. **Include proper TypeScript types** for all data structures
|
|
883
|
-
8. **Test the full user journey** from listing to detail pages
|
|
884
|
-
9. **Use `dangerouslySetInnerHTML`** for processed HTML content from Drupal to avoid raw tag display
|
|
885
|
-
10. **Verify GraphQL field names** match actual schema, not DC field IDs
|
|
886
|
-
|
|
887
|
-
## Success Criteria
|
|
888
|
-
|
|
889
|
-
A successful end-to-end implementation should:
|
|
890
|
-
- [ ] Build without errors
|
|
891
|
-
- [ ] Display content correctly on listing and detail pages
|
|
892
|
-
- [ ] Handle navigation between pages
|
|
893
|
-
- [ ] Render responsively on all device sizes
|
|
894
|
-
- [ ] Show appropriate fallbacks for missing data
|
|
895
|
-
- [ ] Follow the established design patterns
|
|
896
|
-
- [ ] Include proper metadata and SEO elements
|
|
897
|
-
- [ ] Work with the existing authentication and routing system
|
|
898
|
-
|
|
899
|
-
## Key Learnings and Common Mistakes
|
|
900
|
-
|
|
901
|
-
Based on successful product catalog implementation, here are critical learnings to avoid common pitfalls:
|
|
902
|
-
|
|
903
|
-
### DC Import Format Issues
|
|
904
|
-
|
|
905
|
-
**Problem**: Field values incorrectly formatted with "field_" prefix
|
|
906
|
-
```json
|
|
907
|
-
// WRONG - This causes import failures
|
|
908
|
-
"values": {
|
|
909
|
-
"field_price": "$299.99",
|
|
910
|
-
"field_in_stock": true
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// CORRECT - Use field ID directly
|
|
914
|
-
"values": {
|
|
915
|
-
"price": "$299.99",
|
|
916
|
-
"in_stock": true
|
|
917
|
-
}
|
|
918
|
-
```
|
|
919
|
-
|
|
920
|
-
**Solution**: Always use the field `id` value directly in content values, never add "field_" prefix.
|
|
921
|
-
|
|
922
|
-
### GraphQL Field Name Mapping
|
|
923
|
-
|
|
924
|
-
**Problem**: Assuming GraphQL field names match DC field IDs
|
|
925
|
-
```typescript
|
|
926
|
-
// WRONG - Field names may be transformed
|
|
927
|
-
price: string // DC field ID
|
|
928
|
-
fieldPrice: string // What you might expect in GraphQL
|
|
929
|
-
|
|
930
|
-
// CORRECT - Check actual schema
|
|
931
|
-
price: string // Actual GraphQL field name (can match DC ID)
|
|
932
|
-
inStock: boolean // camelCase transformation
|
|
933
|
-
productImages: object // snake_case to camelCase
|
|
934
|
-
```
|
|
935
|
-
|
|
936
|
-
**Solution**: Always verify GraphQL field names by checking the actual schema or API response before writing queries.
|
|
937
|
-
|
|
938
|
-
### GraphQL Field Value Structure Discovery
|
|
939
|
-
|
|
940
|
-
**Critical Learning**: GraphQL field values from DC imports return as simple types, not objects:
|
|
941
|
-
|
|
942
|
-
```typescript
|
|
943
|
-
// WRONG - Assuming field values are objects with .processed
|
|
944
|
-
fieldPrice?: {
|
|
945
|
-
processed: string
|
|
946
|
-
}
|
|
947
|
-
fieldFeatures?: Array<{
|
|
948
|
-
processed: string
|
|
949
|
-
}>
|
|
950
|
-
|
|
951
|
-
// CORRECT - DC imports create simple value fields
|
|
952
|
-
price?: string
|
|
953
|
-
features?: string[]
|
|
954
|
-
```
|
|
955
|
-
|
|
956
|
-
**Discovery Process**: Check GraphQL schema to understand actual field structure:
|
|
957
|
-
```bash
|
|
958
|
-
# Generate fresh schema and check field structure
|
|
959
|
-
npm run generate-schema
|
|
960
|
-
grep -i nodeProducts schema/schema.graphql
|
|
961
|
-
# Check the actual schema files for field names and types
|
|
962
|
-
```
|
|
963
|
-
|
|
964
|
-
**Solution**: Always test a simple GraphQL query first to understand the actual field structure before writing TypeScript types and components.
|
|
965
|
-
|
|
966
|
-
### HTML Content Rendering
|
|
967
|
-
|
|
968
|
-
**Problem**: Drupal processed HTML showing as raw tags in frontend
|
|
969
|
-
```jsx
|
|
970
|
-
// WRONG - Shows: "Key Features <p>Fast Wireless Charging</p> <p>Qi-Compatible</p>"
|
|
971
|
-
<span>{feature.processed}</span>
|
|
972
|
-
|
|
973
|
-
// CORRECT - Renders HTML properly
|
|
974
|
-
<span dangerouslySetInnerHTML={{ __html: feature.processed }} />
|
|
975
|
-
```
|
|
976
|
-
|
|
977
|
-
**Better Solution**: Use `string[]` instead of `text[]` for simple lists to avoid HTML altogether:
|
|
978
|
-
```jsx
|
|
979
|
-
// BEST - Clean, simple, no HTML rendering needed
|
|
980
|
-
<span>{feature}</span> // Just plain text: "Fast Wireless Charging"
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
**Solution**: Use `dangerouslySetInnerHTML` only when necessary for rich `text[]` fields. Prefer `string[]` for simple bullet lists.
|
|
984
|
-
|
|
985
|
-
### Field Type Selection
|
|
986
|
-
|
|
987
|
-
**Best Practice**: Choose field types based on content structure:
|
|
988
|
-
- `string[]` for bullet points, features, specifications, tags, categories (clean plain text, no HTML rendering issues)
|
|
989
|
-
- `text[]` only when you need rich formatting within each item (rare - avoid unless necessary)
|
|
990
|
-
- `text` for rich content descriptions that need HTML formatting
|
|
991
|
-
- `string` for simple values like price, SKU, names (plain text, max 255 chars)
|
|
992
|
-
|
|
993
|
-
**Recommendation**: For repeated items like product features, use `string[]` instead of `text[]` to avoid HTML rendering complexity and security concerns.
|
|
994
|
-
|
|
995
|
-
### Navigation Menu Integration Pattern
|
|
996
|
-
|
|
997
|
-
**Essential Step**: Always update the main navigation when creating new content types:
|
|
998
|
-
|
|
999
|
-
```typescript
|
|
1000
|
-
// Update navigationItems array in app/components/Header.tsx
|
|
1001
|
-
const navigationItems = [
|
|
1002
|
-
{ name: 'Home', href: '/' },
|
|
1003
|
-
{ name: 'Products', href: '/products' }, // Add new content type
|
|
1004
|
-
{ name: 'Articles', href: '/articles' },
|
|
1005
|
-
{ name: 'About', href: '/about' }
|
|
1006
|
-
]
|
|
1007
|
-
|
|
1008
|
-
// Update active tab detection to include content type routes
|
|
1009
|
-
const getActiveTab = () => {
|
|
1010
|
-
if (pathname === '/') return 'Home'
|
|
1011
|
-
if (pathname === '/products' || pathname.startsWith('/products/')) return 'Products'
|
|
1012
|
-
if (pathname === '/articles' || pathname.startsWith('/articles/')) return 'Articles'
|
|
1013
|
-
if (pathname === '/about') return 'About'
|
|
1014
|
-
return null
|
|
1015
|
-
}
|
|
1016
|
-
```
|
|
1017
|
-
|
|
1018
|
-
**Pattern**: Use `.startsWith()` for active state detection to highlight navigation for both listing and detail pages.
|
|
1019
|
-
|
|
1020
|
-
### Component Architecture Best Practices
|
|
1021
|
-
|
|
1022
|
-
**Proven Pattern**: Create two components per content type:
|
|
1023
|
-
1. **`[ContentType]Card.tsx`** - For listing views with preview information
|
|
1024
|
-
2. **`[ContentType]Renderer.tsx`** - For detail pages with complete information
|
|
1025
|
-
|
|
1026
|
-
**Component Features**:
|
|
1027
|
-
- **Cards**: Preview data, stock/status indicators, truncated feature lists, call-to-action links
|
|
1028
|
-
- **Renderers**: Full data display, sticky sidebars, responsive grids, structured information hierarchy
|
|
1029
|
-
|
|
1030
|
-
**File Organization**:
|
|
1031
|
-
```
|
|
1032
|
-
app/
|
|
1033
|
-
├── components/
|
|
1034
|
-
│ ├── ProductCard.tsx # Reusable card for listings
|
|
1035
|
-
│ ├── ProductRenderer.tsx # Full page renderer for details
|
|
1036
|
-
│ └── Header.tsx # Updated with new navigation
|
|
1037
|
-
├── products/
|
|
1038
|
-
│ └── page.tsx # Listing page using ProductCard
|
|
1039
|
-
└── [...slug]/
|
|
1040
|
-
└── page.tsx # Updated to handle new content type routing
|
|
1041
|
-
```
|
|
1042
|
-
|
|
1043
|
-
### Build Process Validation
|
|
1044
|
-
|
|
1045
|
-
**Critical**: Always run build process after major changes to catch TypeScript and import errors:
|
|
1046
|
-
|
|
1047
|
-
```bash
|
|
1048
|
-
npm run build # Must complete without errors
|
|
1049
|
-
npm run dev # Test in development mode
|
|
1050
|
-
```
|
|
1051
|
-
|
|
1052
|
-
**Testing Checklist**:
|
|
1053
|
-
- [ ] Build completes successfully
|
|
1054
|
-
- [ ] Listing page loads and displays content
|
|
1055
|
-
- [ ] Detail pages render via dynamic routing
|
|
1056
|
-
- [ ] Navigation highlights correctly
|
|
1057
|
-
- [ ] Mobile responsive design works
|
|
1058
|
-
- [ ] Error states display appropriately
|
|
1059
|
-
|
|
1060
|
-
### GraphQL Schema Generation Workflow (CRITICAL LEARNING)
|
|
1061
|
-
|
|
1062
|
-
The most important learning from the product catalog implementation is the **mandatory schema generation step**:
|
|
1063
|
-
|
|
1064
|
-
**Problem**: DC imports successfully create content types and content, but GraphQL schema doesn't update automatically.
|
|
1065
|
-
**Symptoms**:
|
|
1066
|
-
- `nodeProducts` query returns "field not found" errors
|
|
1067
|
-
- Content exists in Drupal but not accessible via GraphQL
|
|
1068
|
-
- Route queries work but content type queries fail
|
|
1069
|
-
|
|
1070
|
-
**Solution**: **ALWAYS run schema generation immediately after DC imports**:
|
|
1071
|
-
|
|
1072
|
-
```bash
|
|
1073
|
-
# After any DC import, immediately run:
|
|
1074
|
-
npm run generate-schema
|
|
1075
|
-
|
|
1076
|
-
# This step is MANDATORY for GraphQL integration to work
|
|
1077
|
-
```
|
|
1078
|
-
|
|
1079
|
-
**Why this is critical**:
|
|
1080
|
-
- DC API creates Drupal content types but doesn't trigger GraphQL schema rebuilds
|
|
1081
|
-
- The `generate-schema` script performs fresh introspection and updates local schema files
|
|
1082
|
-
- Without this step, frontend development will fail with "type not found" errors
|
|
1083
|
-
- This step bridges the gap between Drupal content type creation and Next.js GraphQL integration
|
|
1084
|
-
|
|
1085
|
-
**Workflow Integration**:
|
|
1086
|
-
1. Import via DC API ✅
|
|
1087
|
-
2. **Run `npm run generate-schema`** ✅ ← CRITICAL STEP
|
|
1088
|
-
3. Verify schema includes new content type ✅
|
|
1089
|
-
4. Proceed with frontend development ✅
|
|
1090
|
-
|
|
1091
|
-
This learning transforms the development workflow from "sometimes works" to "reliably works every time."
|
|
1092
|
-
|
|
1093
|
-
### Additional Key Learnings
|
|
1094
|
-
|
|
1095
|
-
#### GraphQL Field Name Discovery (CRITICAL)
|
|
1096
|
-
**Always verify actual GraphQL field names after DC import** - they may differ from field IDs used in import JSON.
|
|
1097
|
-
|
|
1098
|
-
**Field Name Transformations**:
|
|
1099
|
-
- `in_stock` becomes `inStock` (camelCase)
|
|
1100
|
-
- `product_images` becomes `productImages` (camelCase)
|
|
1101
|
-
- Simple field types work reliably: `string`, `boolean`, `string[]`
|
|
1102
|
-
|
|
1103
|
-
**Discovery Method**: Check generated schema to understand actual field structure:
|
|
1104
|
-
```bash
|
|
1105
|
-
# Check generated schema to understand actual field structure
|
|
1106
|
-
npm run generate-schema
|
|
1107
|
-
grep -A 10 -B 5 nodeProducts schema/schema.graphql
|
|
1108
|
-
# This shows the actual field names and types available
|
|
1109
|
-
```
|
|
1110
|
-
|
|
1111
|
-
#### Component Architecture Best Practices
|
|
1112
|
-
**Proven Two-Component Pattern**:
|
|
1113
|
-
1. **`[ContentType]Card.tsx`** - Listing view with preview info, truncated content, CTAs
|
|
1114
|
-
2. **`[ContentType]Renderer.tsx`** - Detail page with complete data, image galleries, full specs
|
|
1115
|
-
|
|
1116
|
-
**Navigation Integration Pattern**:
|
|
1117
|
-
```typescript
|
|
1118
|
-
// Update navigationItems array
|
|
1119
|
-
const navigationItems = [
|
|
1120
|
-
{ name: 'Home', href: '/' },
|
|
1121
|
-
{ name: 'Products', href: '/products' }, // Add new content type
|
|
1122
|
-
// ...
|
|
1123
|
-
]
|
|
1124
|
-
|
|
1125
|
-
// Use .startsWith() for active state detection
|
|
1126
|
-
const getActiveTab = () => {
|
|
1127
|
-
if (pathname === '/products' || pathname.startsWith('/products/')) return 'Products'
|
|
1128
|
-
// ...
|
|
1129
|
-
}
|
|
1130
|
-
```
|
|
1131
|
-
|
|
1132
|
-
#### Field Type Best Practices (Revised)
|
|
1133
|
-
**Reliable Field Types**:
|
|
1134
|
-
- `string` - Simple text values (price, SKU, category)
|
|
1135
|
-
- `string[]` - Multiple simple values (features, specifications) - **recommended for lists**
|
|
1136
|
-
- `bool` - Boolean flags (in_stock, featured)
|
|
1137
|
-
- `text` - Rich HTML content (body, descriptions)
|
|
1138
|
-
- `image` - Single image upload
|
|
1139
|
-
- `image[]` - Multiple image uploads
|
|
1140
|
-
|
|
1141
|
-
**Recommendation**: Use `string[]` for lists (features, specifications) instead of `text[]` to avoid HTML rendering complexity.
|
|
1142
|
-
|
|
1143
|
-
#### Build Process Integration
|
|
1144
|
-
**Essential Commands Sequence**:
|
|
1145
|
-
```bash
|
|
1146
|
-
# After DC import, always run:
|
|
1147
|
-
npm run generate-schema # Updates GraphQL schema
|
|
1148
|
-
npm run build # Validates TypeScript and builds
|
|
1149
|
-
npm run dev # Test in development
|
|
1150
|
-
```
|
|
1151
|
-
|
|
1152
|
-
**Success Criteria**:
|
|
1153
|
-
- ✅ Build completes without TypeScript errors
|
|
1154
|
-
- ✅ Listing page loads with HTTP 200
|
|
1155
|
-
- ✅ Detail pages load with proper titles
|
|
1156
|
-
- ✅ Navigation highlighting works correctly
|
|
1157
|
-
|
|
1158
|
-
## Summary
|
|
1159
|
-
|
|
1160
|
-
This comprehensive guide enables "one-shot" prompts like "create a product catalog" to result in complete, working implementations from backend to frontend, with field name discovery and component architecture patterns proven in production.
|