shadcn-nextjs-page-generator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1900 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/cli/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ run: () => run
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+ var import_ora = __toESM(require("ora"), 1);
38
+
39
+ // src/cli/prompts.ts
40
+ var import_prompts = __toESM(require("prompts"), 1);
41
+
42
+ // src/utils/string-transforms.ts
43
+ function toPascalCase(str) {
44
+ return str.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase()).replace(/^[a-z]/, (c) => c.toUpperCase()).replace(/[^a-zA-Z0-9]/g, "");
45
+ }
46
+ function toKebabCase(str) {
47
+ return str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)+/g, "");
48
+ }
49
+
50
+ // src/cli/validators.ts
51
+ function validateNotEmpty(value) {
52
+ return value.length > 0 || "This field is required";
53
+ }
54
+ function validateRoutePath(value) {
55
+ if (!value || value.length === 0) {
56
+ return "Route path is required";
57
+ }
58
+ if (!/^[a-z0-9\-\/]+$/.test(value)) {
59
+ return "Route path can only contain lowercase letters, numbers, dashes, and slashes";
60
+ }
61
+ return true;
62
+ }
63
+ function validateColumnKey(value) {
64
+ if (!value || value.length === 0) {
65
+ return "Column key is required";
66
+ }
67
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value)) {
68
+ return "Column key must be a valid JavaScript identifier (camelCase recommended)";
69
+ }
70
+ return true;
71
+ }
72
+
73
+ // src/cli/prompts.ts
74
+ async function collectConfiguration() {
75
+ import_prompts.default.override({ onCancel: () => process.exit(0) });
76
+ console.log("\n\u{1F680} Welcome to shadcn-page-gen!\n");
77
+ const { pageName } = await (0, import_prompts.default)({
78
+ type: "text",
79
+ name: "pageName",
80
+ message: 'What is the name of the page? (e.g., "User Management")',
81
+ validate: validateNotEmpty
82
+ });
83
+ if (!pageName) return null;
84
+ const defaultRoute = toKebabCase(pageName);
85
+ const { routePath } = await (0, import_prompts.default)({
86
+ type: "text",
87
+ name: "routePath",
88
+ message: `What is the route path? (e.g., "admin/users")`,
89
+ initial: defaultRoute,
90
+ validate: validateRoutePath
91
+ });
92
+ if (!routePath) return null;
93
+ const cleanRoutePath = routePath.replace(/^\/+|\/+$/g, "");
94
+ const defaultModuleName = cleanRoutePath.replace(/\//g, "-");
95
+ const { moduleName } = await (0, import_prompts.default)({
96
+ type: "text",
97
+ name: "moduleName",
98
+ message: `Module name?`,
99
+ initial: defaultModuleName
100
+ });
101
+ if (!moduleName) return null;
102
+ const { architecture } = await (0, import_prompts.default)({
103
+ type: "select",
104
+ name: "architecture",
105
+ message: "Choose architecture pattern:",
106
+ choices: [
107
+ {
108
+ title: "DDD (Domain-Driven Design)",
109
+ value: "ddd",
110
+ description: "Full layers: Domain, Application, Infrastructure, Presentation"
111
+ },
112
+ {
113
+ title: "Simplified",
114
+ value: "simplified",
115
+ description: "Just components and pages - cleaner, faster"
116
+ }
117
+ ],
118
+ initial: 0
119
+ });
120
+ if (!architecture) return null;
121
+ const pascalName = toPascalCase(pageName);
122
+ let entityName = pascalName;
123
+ if (architecture === "ddd") {
124
+ const response = await (0, import_prompts.default)({
125
+ type: "text",
126
+ name: "entityName",
127
+ message: `Entity name? (e.g., Product, Ticket)`,
128
+ initial: pascalName
129
+ });
130
+ if (!response.entityName) return null;
131
+ entityName = response.entityName;
132
+ }
133
+ const columns = [];
134
+ let addColumn = true;
135
+ console.log("\n--- Table Columns (ID is automatic) ---");
136
+ const { useDefaultColumns } = await (0, import_prompts.default)({
137
+ type: "confirm",
138
+ name: "useDefaultColumns",
139
+ message: "Use default columns? (Name, Status, Created At)",
140
+ initial: true
141
+ });
142
+ if (useDefaultColumns) {
143
+ columns.push(
144
+ { label: "Name", key: "name", type: "string", sortable: true },
145
+ { label: "Status", key: "status", type: "string", sortable: true },
146
+ { label: "Created At", key: "createdAt", type: "date", sortable: true }
147
+ );
148
+ } else {
149
+ while (addColumn) {
150
+ const { continueAdding } = await (0, import_prompts.default)({
151
+ type: "confirm",
152
+ name: "continueAdding",
153
+ message: columns.length === 0 ? "Add a column?" : "Add another column?",
154
+ initial: true
155
+ });
156
+ if (!continueAdding) break;
157
+ const columnData = await (0, import_prompts.default)([
158
+ {
159
+ type: "text",
160
+ name: "label",
161
+ message: 'Column Label (e.g., "Email Address"):',
162
+ validate: validateNotEmpty
163
+ },
164
+ {
165
+ type: "text",
166
+ name: "key",
167
+ message: 'Column Key (e.g., "email"):',
168
+ validate: validateColumnKey
169
+ },
170
+ {
171
+ type: "select",
172
+ name: "type",
173
+ message: "Column Type:",
174
+ choices: [
175
+ { title: "String", value: "string" },
176
+ { title: "Number", value: "number" },
177
+ { title: "Boolean", value: "boolean" },
178
+ { title: "Date", value: "date" }
179
+ ],
180
+ initial: 0
181
+ }
182
+ ]);
183
+ if (columnData.label && columnData.key && columnData.type) {
184
+ columns.push({
185
+ ...columnData,
186
+ sortable: false
187
+ // Will be set later
188
+ });
189
+ }
190
+ }
191
+ if (columns.length === 0) {
192
+ columns.push(
193
+ { label: "Name", key: "name", type: "string", sortable: true },
194
+ { label: "Status", key: "status", type: "string", sortable: true },
195
+ { label: "Created At", key: "createdAt", type: "date", sortable: true }
196
+ );
197
+ }
198
+ }
199
+ const filters = [];
200
+ console.log("\n--- Filters ---");
201
+ const { addFilters } = await (0, import_prompts.default)({
202
+ type: "confirm",
203
+ name: "addFilters",
204
+ message: "Add filters?",
205
+ initial: true
206
+ });
207
+ if (addFilters) {
208
+ let addFilterLoop = true;
209
+ while (addFilterLoop) {
210
+ const { continueAddingFilter } = await (0, import_prompts.default)({
211
+ type: "confirm",
212
+ name: "continueAddingFilter",
213
+ message: filters.length === 0 ? "Add a filter?" : "Add another filter?",
214
+ initial: filters.length === 0
215
+ });
216
+ if (!continueAddingFilter) break;
217
+ const filterData = await (0, import_prompts.default)([
218
+ {
219
+ type: "select",
220
+ name: "type",
221
+ message: "Filter type:",
222
+ choices: [
223
+ { title: "Dropdown (Select)", value: "select" },
224
+ { title: "Date Picker", value: "date" },
225
+ { title: "Text Input", value: "input" }
226
+ ]
227
+ },
228
+ {
229
+ type: "text",
230
+ name: "label",
231
+ message: 'Filter Label (e.g., "Status"):',
232
+ validate: validateNotEmpty
233
+ },
234
+ {
235
+ type: "text",
236
+ name: "key",
237
+ message: 'Filter Key (URL param, e.g., "status"):',
238
+ validate: validateColumnKey
239
+ }
240
+ ]);
241
+ if (filterData.type && filterData.label && filterData.key) {
242
+ filters.push(filterData);
243
+ }
244
+ }
245
+ }
246
+ console.log("\n--- UI Options ---");
247
+ const uiOptions = await (0, import_prompts.default)([
248
+ {
249
+ type: "confirm",
250
+ name: "includeStats",
251
+ message: "Include Stats Cards at the top?",
252
+ initial: true
253
+ },
254
+ {
255
+ type: "confirm",
256
+ name: "includeRowSelection",
257
+ message: "Include Row Selection (Checkboxes)?",
258
+ initial: false
259
+ }
260
+ ]);
261
+ const { dataFetching } = await (0, import_prompts.default)({
262
+ type: "select",
263
+ name: "dataFetching",
264
+ message: "Data Fetching Strategy:",
265
+ choices: [
266
+ { title: "Mock Data (Repository Pattern)", value: "mock", description: "Default mock data with repository pattern" },
267
+ { title: "TanStack Query (React Query)", value: "tanstack", description: "Modern data fetching with caching" },
268
+ { title: "Standard (fetch/useEffect)", value: "fetch", description: "Basic fetch with useEffect" }
269
+ ],
270
+ initial: 0
271
+ });
272
+ const { enableSorting } = await (0, import_prompts.default)({
273
+ type: "confirm",
274
+ name: "enableSorting",
275
+ message: "Enable Column Sorting?",
276
+ initial: true
277
+ });
278
+ let sortableColumns = [];
279
+ if (enableSorting && columns.length > 0) {
280
+ const { selected } = await (0, import_prompts.default)({
281
+ type: "multiselect",
282
+ name: "selected",
283
+ message: "Select columns to enable sorting (Space to select, Enter to submit):",
284
+ choices: columns.map((c) => ({ title: c.label, value: c.key, selected: true })),
285
+ min: 0
286
+ });
287
+ sortableColumns = selected || [];
288
+ columns.forEach((col) => {
289
+ col.sortable = sortableColumns.includes(col.key);
290
+ });
291
+ }
292
+ console.log("\n--- Animations (Framer Motion) ---");
293
+ const animationOptions = await (0, import_prompts.default)([
294
+ {
295
+ type: "confirm",
296
+ name: "pageTransitions",
297
+ message: "Add page transition animations?",
298
+ initial: true
299
+ },
300
+ {
301
+ type: "confirm",
302
+ name: "listAnimations",
303
+ message: "Animate table rows on load?",
304
+ initial: true
305
+ },
306
+ {
307
+ type: "confirm",
308
+ name: "cardAnimations",
309
+ message: "Animate stats cards?",
310
+ initial: uiOptions.includeStats
311
+ },
312
+ {
313
+ type: "select",
314
+ name: "intensity",
315
+ message: "Animation intensity:",
316
+ choices: [
317
+ { title: "Subtle (professional)", value: "subtle" },
318
+ { title: "Moderate (balanced)", value: "moderate" },
319
+ { title: "Bold (eye-catching)", value: "bold" }
320
+ ],
321
+ initial: 1
322
+ }
323
+ ]);
324
+ const config = {
325
+ pageName,
326
+ routePath: cleanRoutePath,
327
+ moduleName,
328
+ architecture,
329
+ entityName,
330
+ columns,
331
+ filters,
332
+ includeStats: uiOptions.includeStats,
333
+ includeRowSelection: uiOptions.includeRowSelection,
334
+ includeSearch: true,
335
+ // Always include search
336
+ dataFetching,
337
+ sortableColumns,
338
+ animations: {
339
+ pageTransitions: animationOptions.pageTransitions,
340
+ listAnimations: animationOptions.listAnimations,
341
+ cardAnimations: animationOptions.cardAnimations,
342
+ intensity: animationOptions.intensity
343
+ }
344
+ };
345
+ return config;
346
+ }
347
+
348
+ // src/utils/logger.ts
349
+ var import_chalk = __toESM(require("chalk"), 1);
350
+ var logger = {
351
+ success(message) {
352
+ console.log(import_chalk.default.green("\u2713"), message);
353
+ },
354
+ error(message) {
355
+ console.log(import_chalk.default.red("\u2717"), message);
356
+ },
357
+ info(message) {
358
+ console.log(import_chalk.default.blue("\u2139"), message);
359
+ },
360
+ warning(message) {
361
+ console.log(import_chalk.default.yellow("\u26A0"), message);
362
+ },
363
+ log(message) {
364
+ console.log(message);
365
+ },
366
+ title(message) {
367
+ console.log(import_chalk.default.bold.cyan(`
368
+ ${message}
369
+ `));
370
+ },
371
+ step(step, total, message) {
372
+ console.log(import_chalk.default.gray(`[${step}/${total}]`), message);
373
+ },
374
+ dim(message) {
375
+ console.log(import_chalk.default.dim(message));
376
+ }
377
+ };
378
+
379
+ // src/generators/ddd-generator.ts
380
+ var import_path2 = __toESM(require("path"), 1);
381
+
382
+ // src/utils/file-system.ts
383
+ var import_fs_extra = __toESM(require("fs-extra"), 1);
384
+ var import_path = __toESM(require("path"), 1);
385
+ async function ensureDir(dirPath) {
386
+ await import_fs_extra.default.ensureDir(dirPath);
387
+ }
388
+ async function writeFile(filePath, content) {
389
+ const dir = import_path.default.dirname(filePath);
390
+ await ensureDir(dir);
391
+ await import_fs_extra.default.writeFile(filePath, content, "utf-8");
392
+ }
393
+ async function createDirectories(dirs) {
394
+ for (const dir of dirs) {
395
+ await ensureDir(dir);
396
+ }
397
+ }
398
+ async function writeFiles(files) {
399
+ for (const file of files) {
400
+ await writeFile(file.path, file.content);
401
+ logger.dim(` Created: ${file.path}`);
402
+ }
403
+ }
404
+
405
+ // src/templates/ddd/entity.ts
406
+ function generateEntity(config) {
407
+ const { entityName, moduleName, columns } = config;
408
+ const columnFields = columns.map((c) => {
409
+ const typeMap = {
410
+ "string": "string",
411
+ "number": "number",
412
+ "boolean": "boolean",
413
+ "date": "string"
414
+ // ISO string for dates
415
+ };
416
+ return ` ${c.key}: ${typeMap[c.type]};`;
417
+ }).join("\n");
418
+ const createDTOFields = columns.filter((c) => !["id", "createdAt", "updatedAt"].includes(c.key)).map((c) => {
419
+ const typeMap = {
420
+ "string": "string",
421
+ "number": "number",
422
+ "boolean": "boolean",
423
+ "date": "string"
424
+ };
425
+ return ` ${c.key}: ${typeMap[c.type]};`;
426
+ }).join("\n");
427
+ return `/**
428
+ * Domain Entity: ${entityName}
429
+ * Generated by shadcn-page-gen
430
+ */
431
+ export interface ${entityName} {
432
+ id: string;
433
+ ${columnFields}
434
+ updatedAt: Date;
435
+ }
436
+
437
+ export interface Create${entityName}DTO {
438
+ ${createDTOFields || " // Add your fields here"}
439
+ }
440
+
441
+ export interface Update${entityName}DTO extends Partial<Create${entityName}DTO> {
442
+ id: string;
443
+ }
444
+ `;
445
+ }
446
+
447
+ // src/templates/ddd/repository-interface.ts
448
+ function generateRepositoryInterface(config) {
449
+ const { entityName, moduleName } = config;
450
+ return `import { ${entityName}, Create${entityName}DTO, Update${entityName}DTO } from '../entities/${moduleName}.entity';
451
+
452
+ /**
453
+ * Repository Interface: ${entityName}
454
+ * Generated by shadcn-page-gen
455
+ */
456
+ export interface I${entityName}Repository {
457
+ findAll(params?: any): Promise<${entityName}[]>;
458
+ findById(id: string): Promise<${entityName} | null>;
459
+ create(data: Create${entityName}DTO): Promise<${entityName}>;
460
+ update(data: Update${entityName}DTO): Promise<${entityName}>;
461
+ delete(id: string): Promise<void>;
462
+ }
463
+ `;
464
+ }
465
+
466
+ // src/templates/ddd/repository-impl.ts
467
+ function generateRepositoryImpl(config) {
468
+ const { entityName, moduleName, columns } = config;
469
+ const mockFields = columns.map((c) => {
470
+ if (c.key === "createdAt") {
471
+ return ` createdAt: new Date().toISOString(),`;
472
+ }
473
+ if (c.key === "status") {
474
+ return ` status: Math.random() > 0.5 ? 'Active' : 'Inactive',`;
475
+ }
476
+ if (c.key === "name") {
477
+ return ` name: \`${entityName} \${i + 1}\`,`;
478
+ }
479
+ if (c.type === "number") {
480
+ return ` ${c.key}: (i + 1) * 100,`;
481
+ }
482
+ if (c.type === "boolean") {
483
+ return ` ${c.key}: Math.random() > 0.5,`;
484
+ }
485
+ if (c.type === "date") {
486
+ return ` ${c.key}: new Date().toISOString(),`;
487
+ }
488
+ return ` ${c.key}: \`${c.label} \${i + 1}\`,`;
489
+ }).join("\n");
490
+ return `import { ${entityName}, Create${entityName}DTO, Update${entityName}DTO } from '../../domain/entities/${moduleName}.entity';
491
+ import { I${entityName}Repository } from '../../domain/repositories/${moduleName}.repository.interface';
492
+
493
+ /**
494
+ * Repository Implementation: ${entityName}
495
+ * Generated by shadcn-page-gen
496
+ *
497
+ * This is a mock implementation with in-memory data.
498
+ * Replace with your actual API calls.
499
+ */
500
+ export class ${entityName}Repository implements I${entityName}Repository {
501
+ // Mock data - replace with real API calls
502
+ private items: ${entityName}[] = Array.from({ length: 10 }).map((_, i) => ({
503
+ id: (i + 1).toString(),
504
+ ${mockFields}
505
+ updatedAt: new Date(),
506
+ })) as unknown as ${entityName}[];
507
+
508
+ async findAll(params?: any): Promise<${entityName}[]> {
509
+ // Simulate API delay
510
+ await new Promise(resolve => setTimeout(resolve, 500));
511
+
512
+ // TODO: Implement sorting, filtering, pagination
513
+ // const { sortBy, order, page, limit } = params || {};
514
+
515
+ return [...this.items];
516
+ }
517
+
518
+ async findById(id: string): Promise<${entityName} | null> {
519
+ await new Promise(resolve => setTimeout(resolve, 300));
520
+ const item = this.items.find(i => i.id === id);
521
+ return item || null;
522
+ }
523
+
524
+ async create(data: Create${entityName}DTO): Promise<${entityName}> {
525
+ await new Promise(resolve => setTimeout(resolve, 500));
526
+
527
+ const newItem: ${entityName} = {
528
+ id: Date.now().toString(),
529
+ ...data,
530
+ updatedAt: new Date(),
531
+ ${columns.some((c) => c.key === "createdAt") ? "createdAt: new Date().toISOString()," : ""}
532
+ } as unknown as ${entityName};
533
+
534
+ this.items.push(newItem);
535
+ return newItem;
536
+ }
537
+
538
+ async update(data: Update${entityName}DTO): Promise<${entityName}> {
539
+ await new Promise(resolve => setTimeout(resolve, 500));
540
+
541
+ const index = this.items.findIndex(i => i.id === data.id);
542
+ if (index === -1) {
543
+ throw new Error(\`${entityName} with id \${data.id} not found\`);
544
+ }
545
+
546
+ this.items[index] = {
547
+ ...this.items[index],
548
+ ...data,
549
+ updatedAt: new Date(),
550
+ } as ${entityName};
551
+
552
+ return this.items[index];
553
+ }
554
+
555
+ async delete(id: string): Promise<void> {
556
+ await new Promise(resolve => setTimeout(resolve, 500));
557
+
558
+ const index = this.items.findIndex(i => i.id === id);
559
+ if (index === -1) {
560
+ throw new Error(\`${entityName} with id \${id} not found\`);
561
+ }
562
+
563
+ this.items.splice(index, 1);
564
+ }
565
+ }
566
+ `;
567
+ }
568
+
569
+ // src/templates/ddd/use-case.ts
570
+ function generateUseCase(config) {
571
+ const { entityName, moduleName } = config;
572
+ return `import { ${entityName} } from '../../domain/entities/${moduleName}.entity';
573
+ import { I${entityName}Repository } from '../../domain/repositories/${moduleName}.repository.interface';
574
+
575
+ /**
576
+ * Use Case: Get ${entityName}s
577
+ * Generated by shadcn-page-gen
578
+ */
579
+ export class Get${entityName}sUseCase {
580
+ constructor(private readonly repository: I${entityName}Repository) {}
581
+
582
+ async execute(params?: any): Promise<${entityName}[]> {
583
+ return await this.repository.findAll(params);
584
+ }
585
+ }
586
+ `;
587
+ }
588
+
589
+ // src/templates/ddd/component.ts
590
+ function generateComponent(config) {
591
+ const {
592
+ entityName,
593
+ moduleName,
594
+ pageName,
595
+ routePath,
596
+ columns,
597
+ filters,
598
+ includeStats,
599
+ includeRowSelection,
600
+ dataFetching,
601
+ sortableColumns,
602
+ animations
603
+ } = config;
604
+ const isTanStack = dataFetching === "tanstack";
605
+ const hasAnimations = animations.listAnimations || animations.cardAnimations;
606
+ const imports = `'use client';
607
+
608
+ ${hasAnimations ? `import { motion } from 'framer-motion';` : ""}
609
+ import { useEffect, useState } from 'react';
610
+ import { ${entityName} } from '../../domain/entities/${moduleName}.entity';
611
+ import { Get${entityName}sUseCase } from '../../application/use-cases/get-${moduleName}s.use-case';
612
+ import { ${entityName}Repository } from '../../infrastructure/repositories/${moduleName}.repository';
613
+ ${isTanStack ? `import { useQuery } from '@tanstack/react-query';` : ""}
614
+ import { Button } from '@/components/ui/button';
615
+ import {
616
+ Table,
617
+ TableBody,
618
+ TableCell,
619
+ TableHead,
620
+ TableHeader,
621
+ TableRow,
622
+ } from '@/components/ui/table';
623
+ import {
624
+ Select,
625
+ SelectContent,
626
+ SelectItem,
627
+ SelectTrigger,
628
+ SelectValue,
629
+ } from '@/components/ui/select';
630
+ import { Input } from '@/components/ui/input';
631
+ ${filters.some((f) => f.type === "date") ? `import { Calendar } from '@/components/ui/calendar';
632
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
633
+ import { Calendar as CalendarIcon } from 'lucide-react';
634
+ import { format } from 'date-fns';` : ""}
635
+ import { Badge } from '@/components/ui/badge';
636
+ ${includeStats ? `import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';` : ""}
637
+ import {
638
+ Search,
639
+ Plus,
640
+ X,
641
+ RefreshCcw,
642
+ Eye,
643
+ Pencil,
644
+ Trash2,
645
+ MoreHorizontal,
646
+ ${sortableColumns.length > 0 ? "ArrowUpDown, ArrowUp, ArrowDown," : ""}
647
+ } from 'lucide-react';
648
+ import { cn } from '@/lib/utils';
649
+ import { useRouter, useSearchParams } from 'next/navigation';
650
+ import {
651
+ Pagination,
652
+ PaginationContent,
653
+ PaginationItem,
654
+ PaginationLink,
655
+ PaginationNext,
656
+ PaginationPrevious,
657
+ } from "@/components/ui/pagination";
658
+ import {
659
+ DropdownMenu,
660
+ DropdownMenuContent,
661
+ DropdownMenuItem,
662
+ DropdownMenuLabel,
663
+ DropdownMenuTrigger,
664
+ } from "@/components/ui/dropdown-menu";
665
+ ${includeRowSelection ? `import { Checkbox } from "@/components/ui/checkbox";` : ""}`;
666
+ const animationVariants = hasAnimations ? `
667
+ // Framer Motion animation variants
668
+ const containerVariants = {
669
+ hidden: { opacity: 0 },
670
+ visible: {
671
+ opacity: 1,
672
+ transition: {
673
+ staggerChildren: ${animations.intensity === "bold" ? "0.1" : animations.intensity === "subtle" ? "0.03" : "0.05"}
674
+ }
675
+ }
676
+ };
677
+
678
+ const itemVariants = {
679
+ hidden: { opacity: 0, x: -20 },
680
+ visible: {
681
+ opacity: 1,
682
+ x: 0,
683
+ transition: { duration: ${animations.intensity === "bold" ? "0.3" : animations.intensity === "subtle" ? "0.15" : "0.2"} }
684
+ }
685
+ };
686
+ ` : "";
687
+ const dataFetchingCode = isTanStack ? `
688
+ // TanStack Query data fetching
689
+ const { data, isLoading: loading, refetch } = useQuery({
690
+ queryKey: ['${moduleName}', searchParams.toString()],
691
+ queryFn: async () => {
692
+ const repo = new ${entityName}Repository();
693
+ const useCase = new Get${entityName}sUseCase(repo);
694
+ return await useCase.execute({
695
+ q: searchParams.get('q'),
696
+ page: searchParams.get('page'),
697
+ limit: pageSize,
698
+ sortBy: searchParams.get('sortBy'),
699
+ order: searchParams.get('order'),
700
+ ...Object.fromEntries(searchParams.entries())
701
+ });
702
+ }
703
+ });` : `
704
+ const [data, setData] = useState<${entityName}[]>([]);
705
+ const [loading, setLoading] = useState(true);
706
+
707
+ const fetchData = async () => {
708
+ setLoading(true);
709
+ try {
710
+ const repo = new ${entityName}Repository();
711
+ const useCase = new Get${entityName}sUseCase(repo);
712
+ const result = await useCase.execute({
713
+ q: searchParams.get('q'),
714
+ page: searchParams.get('page'),
715
+ limit: pageSize,
716
+ sortBy: searchParams.get('sortBy'),
717
+ order: searchParams.get('order'),
718
+ ...Object.fromEntries(searchParams.entries())
719
+ });
720
+ setData(result);
721
+ } catch (error) {
722
+ console.error(error);
723
+ } finally {
724
+ setLoading(false);
725
+ }
726
+ };
727
+
728
+ useEffect(() => {
729
+ fetchData();
730
+ }, [searchParams]);`;
731
+ const rowSelectionCode = includeRowSelection ? `
732
+ const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set());
733
+
734
+ const toggleRow = (id: string) => {
735
+ const newSelected = new Set(selectedRows);
736
+ if (newSelected.has(id)) newSelected.delete(id);
737
+ else newSelected.add(id);
738
+ setSelectedRows(newSelected);
739
+ };
740
+
741
+ const toggleAll = () => {
742
+ if (selectedRows.size === (data?.length || 0)) {
743
+ setSelectedRows(new Set());
744
+ } else {
745
+ setSelectedRows(new Set(data?.map(i => i.id) || []));
746
+ }
747
+ };` : "";
748
+ const statsCards = includeStats ? `
749
+ {/* Stats Cards */}
750
+ ${animations.cardAnimations ? `<motion.div
751
+ className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"
752
+ variants={containerVariants}
753
+ initial="hidden"
754
+ animate="visible"
755
+ >` : `<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">`}
756
+ ${animations.cardAnimations ? `<motion.div variants={itemVariants}>` : ""}
757
+ <Card>
758
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
759
+ <CardTitle className="text-sm font-medium">Total ${entityName}s</CardTitle>
760
+ </CardHeader>
761
+ <CardContent>
762
+ <div className="text-2xl font-bold">128</div>
763
+ <p className="text-xs text-muted-foreground">+20.1% from last month</p>
764
+ </CardContent>
765
+ </Card>
766
+ ${animations.cardAnimations ? `</motion.div>` : ""}
767
+
768
+ ${animations.cardAnimations ? `<motion.div variants={itemVariants}>` : ""}
769
+ <Card>
770
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
771
+ <CardTitle className="text-sm font-medium">Active</CardTitle>
772
+ </CardHeader>
773
+ <CardContent>
774
+ <div className="text-2xl font-bold">12</div>
775
+ <p className="text-xs text-muted-foreground">+4 since yesterday</p>
776
+ </CardContent>
777
+ </Card>
778
+ ${animations.cardAnimations ? `</motion.div>` : ""}
779
+
780
+ ${animations.cardAnimations ? `<motion.div variants={itemVariants}>` : ""}
781
+ <Card>
782
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
783
+ <CardTitle className="text-sm font-medium">Pending</CardTitle>
784
+ </CardHeader>
785
+ <CardContent>
786
+ <div className="text-2xl font-bold">4</div>
787
+ <p className="text-xs text-muted-foreground">-2 since yesterday</p>
788
+ </CardContent>
789
+ </Card>
790
+ ${animations.cardAnimations ? `</motion.div>` : ""}
791
+
792
+ ${animations.cardAnimations ? `<motion.div variants={itemVariants}>` : ""}
793
+ <Card>
794
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
795
+ <CardTitle className="text-sm font-medium">Closed</CardTitle>
796
+ </CardHeader>
797
+ <CardContent>
798
+ <div className="text-2xl font-bold">2</div>
799
+ <p className="text-xs text-muted-foreground">+1 since yesterday</p>
800
+ </CardContent>
801
+ </Card>
802
+ ${animations.cardAnimations ? `</motion.div>` : ""}
803
+ ${animations.cardAnimations ? `</motion.div>` : `</div>`}
804
+ ` : "";
805
+ const filterComponents = filters.map((f) => {
806
+ if (f.type === "select") {
807
+ return `
808
+ <Select
809
+ value={searchParams.get('${f.key}') || 'all'}
810
+ onValueChange={(value) => updateParam('${f.key}', value === 'all' ? null : value)}
811
+ >
812
+ <SelectTrigger className="w-[150px]">
813
+ <SelectValue placeholder="${f.label}" />
814
+ </SelectTrigger>
815
+ <SelectContent>
816
+ <SelectItem value="all">All ${f.label}</SelectItem>
817
+ <SelectItem value="active">Active</SelectItem>
818
+ <SelectItem value="inactive">Inactive</SelectItem>
819
+ </SelectContent>
820
+ </Select>`;
821
+ } else if (f.type === "date") {
822
+ return `
823
+ <Popover>
824
+ <PopoverTrigger asChild>
825
+ <Button
826
+ variant="outline"
827
+ className={cn(
828
+ "w-[200px] justify-start text-left font-normal",
829
+ !searchParams.get('${f.key}') && "text-muted-foreground"
830
+ )}
831
+ >
832
+ <CalendarIcon className="mr-2 h-4 w-4" />
833
+ {searchParams.get('${f.key}') ? (
834
+ format(new Date(searchParams.get('${f.key}')!), "PPP")
835
+ ) : (
836
+ <span>${f.label}</span>
837
+ )}
838
+ </Button>
839
+ </PopoverTrigger>
840
+ <PopoverContent className="w-auto p-0" align="start">
841
+ <Calendar
842
+ mode="single"
843
+ selected={searchParams.get('${f.key}') ? new Date(searchParams.get('${f.key}')!) : undefined}
844
+ onSelect={(date) => updateParam('${f.key}', date ? date.toISOString() : null)}
845
+ initialFocus
846
+ />
847
+ </PopoverContent>
848
+ </Popover>`;
849
+ }
850
+ return "";
851
+ }).join("\n");
852
+ const tableHeaders = columns.map((c) => {
853
+ const isSortable = sortableColumns.includes(c.key);
854
+ if (isSortable) {
855
+ return ` <TableHead className="cursor-pointer" onClick={() => handleSort('${c.key}')}>
856
+ <div className="flex items-center gap-1">
857
+ ${c.label.toUpperCase()}
858
+ {searchParams.get('sortBy') === '${c.key}' ? (
859
+ searchParams.get('order') === 'asc' ? <ArrowUp className="h-3 w-3" /> : <ArrowDown className="h-3 w-3" />
860
+ ) : <ArrowUpDown className="h-3 w-3 text-muted-foreground" />}
861
+ </div>
862
+ </TableHead>`;
863
+ }
864
+ return ` <TableHead>${c.label.toUpperCase()}</TableHead>`;
865
+ }).join("\n");
866
+ const tableCells = columns.map((c) => {
867
+ if (c.key === "status") {
868
+ return ` <TableCell>
869
+ <Badge variant={item.status === 'Active' ? 'default' : 'secondary'} className="rounded-full">
870
+ {item.status}
871
+ </Badge>
872
+ </TableCell>`;
873
+ }
874
+ if (c.type === "date") {
875
+ return ` <TableCell className="text-muted-foreground">{new Date(item.${c.key}).toLocaleDateString()}</TableCell>`;
876
+ }
877
+ return ` <TableCell>{item.${c.key}}</TableCell>`;
878
+ }).join("\n");
879
+ const colSpan = columns.length + 2 + (includeRowSelection ? 1 : 0);
880
+ return `${imports}
881
+
882
+ /**
883
+ * ${entityName} List Component
884
+ * Generated by shadcn-page-gen
885
+ *
886
+ * Features:
887
+ * - Search and filters
888
+ * - Sorting: ${sortableColumns.length > 0 ? sortableColumns.join(", ") : "None"}
889
+ * - Pagination
890
+ * - Row actions (View, Edit, Delete)
891
+ * ${includeStats ? "- Stats cards" : ""}
892
+ * ${includeRowSelection ? "- Row selection" : ""}
893
+ * ${animations.listAnimations || animations.cardAnimations ? `- Framer Motion animations (${animations.intensity})` : ""}
894
+ */
895
+ ${animationVariants}
896
+ export function ${entityName}List() {
897
+ const router = useRouter();
898
+ const searchParams = useSearchParams();
899
+
900
+ const [searchTerm, setSearchTerm] = useState(searchParams.get('q') || '');
901
+ const pageSize = Number(searchParams.get('limit')) || 10;
902
+ ${dataFetchingCode}
903
+ ${rowSelectionCode}
904
+
905
+ const updateParam = (key: string, value: string | null) => {
906
+ const params = new URLSearchParams(searchParams.toString());
907
+ if (value) params.set(key, value);
908
+ else params.delete(key);
909
+
910
+ if (key !== 'page') params.set('page', '1');
911
+
912
+ router.push('?' + params.toString());
913
+ };
914
+
915
+ ${sortableColumns.length > 0 ? `const handleSort = (key: string) => {
916
+ const currentSort = searchParams.get('sortBy');
917
+ const currentOrder = searchParams.get('order');
918
+
919
+ let newOrder = 'asc';
920
+ if (currentSort === key && currentOrder === 'asc') {
921
+ newOrder = 'desc';
922
+ }
923
+
924
+ const params = new URLSearchParams(searchParams.toString());
925
+ params.set('sortBy', key);
926
+ params.set('order', newOrder);
927
+ router.push('?' + params.toString());
928
+ };` : ""}
929
+
930
+ return (
931
+ <div className="space-y-6">
932
+ ${statsCards}
933
+ {/* Actions Bar */}
934
+ <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
935
+ <div className="flex flex-1 items-center gap-2">
936
+ <div className="relative flex-1">
937
+ <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
938
+ <Input
939
+ placeholder="Search..."
940
+ value={searchTerm}
941
+ onChange={(e) => setSearchTerm(e.target.value)}
942
+ onBlur={() => updateParam('q', searchTerm)}
943
+ className="pl-9 w-full md:max-w-md"
944
+ />
945
+ </div>
946
+ ${filterComponents}
947
+ {(searchParams.toString().length > 0) && (
948
+ <Button variant="ghost" size="icon" onClick={() => router.push('/${routePath}')} title="Reset Filters">
949
+ <X className="h-4 w-4" />
950
+ </Button>
951
+ )}
952
+ </div>
953
+
954
+ <div className="flex items-center gap-2">
955
+ <Button variant="outline" size="sm" onClick={() => ${isTanStack ? "refetch()" : "fetchData()"}}>
956
+ <RefreshCcw className="mr-2 h-4 w-4" /> Refresh
957
+ </Button>
958
+ <Button size="sm">
959
+ <Plus className="mr-2 h-4 w-4" /> New ${entityName}
960
+ </Button>
961
+ </div>
962
+ </div>
963
+
964
+ {/* Main Table */}
965
+ <Card>
966
+ <CardContent className="p-0">
967
+ <Table>
968
+ <TableHeader>
969
+ <TableRow>
970
+ ${includeRowSelection ? `<TableHead className="w-[50px]"><Checkbox checked={data?.length > 0 && selectedRows.size === data?.length} onCheckedChange={toggleAll} /></TableHead>` : ""}
971
+ <TableHead className="w-[100px]">ID</TableHead>
972
+ ${tableHeaders}
973
+ <TableHead className="text-right">ACTIONS</TableHead>
974
+ </TableRow>
975
+ </TableHeader>
976
+ <TableBody>
977
+ {loading ? (
978
+ <TableRow>
979
+ <TableCell colSpan={${colSpan}} className="text-center h-24 text-muted-foreground">Loading data...</TableCell>
980
+ </TableRow>
981
+ ) : !data || data.length === 0 ? (
982
+ <TableRow>
983
+ <TableCell colSpan={${colSpan}} className="text-center h-24 text-muted-foreground">No results found.</TableCell>
984
+ </TableRow>
985
+ ) : (
986
+ ${animations.listAnimations ? `
987
+ data.map((item, index) => (
988
+ <motion.tr
989
+ key={item.id}
990
+ variants={itemVariants}
991
+ initial="hidden"
992
+ animate="visible"
993
+ custom={index}
994
+ className="group"
995
+ >` : `data.map((item) => (
996
+ <TableRow key={item.id} className="group">`}
997
+ ${includeRowSelection ? `<TableCell><Checkbox checked={selectedRows.has(item.id)} onCheckedChange={() => toggleRow(item.id)} /></TableCell>` : ""}
998
+ <TableCell className="font-medium">{item.id}</TableCell>
999
+ ${tableCells}
1000
+ <TableCell className="text-right">
1001
+ <div className="flex justify-end gap-2">
1002
+ <Button variant="ghost" size="icon" className="h-8 w-8 text-blue-500 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-950">
1003
+ <Eye className="h-4 w-4" />
1004
+ </Button>
1005
+ <Button variant="ghost" size="icon" className="h-8 w-8 text-green-500 hover:text-green-600 hover:bg-green-50 dark:hover:bg-green-950">
1006
+ <Pencil className="h-4 w-4" />
1007
+ </Button>
1008
+ <Button variant="ghost" size="icon" className="h-8 w-8 text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-950">
1009
+ <Trash2 className="h-4 w-4" />
1010
+ </Button>
1011
+ <DropdownMenu>
1012
+ <DropdownMenuTrigger asChild>
1013
+ <Button variant="ghost" size="icon" className="h-8 w-8">
1014
+ <MoreHorizontal className="h-4 w-4" />
1015
+ </Button>
1016
+ </DropdownMenuTrigger>
1017
+ <DropdownMenuContent align="end">
1018
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
1019
+ <DropdownMenuItem>View Details</DropdownMenuItem>
1020
+ <DropdownMenuItem>Edit Record</DropdownMenuItem>
1021
+ </DropdownMenuContent>
1022
+ </DropdownMenu>
1023
+ </div>
1024
+ </TableCell>
1025
+ ${animations.listAnimations ? `</motion.tr>` : `</TableRow>`}
1026
+ ))
1027
+ )}
1028
+ </TableBody>
1029
+ </Table>
1030
+ </CardContent>
1031
+ </Card>
1032
+
1033
+ {/* Pagination */}
1034
+ <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
1035
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
1036
+ <span>Show</span>
1037
+ <Select value={pageSize.toString()} onValueChange={(v) => updateParam('limit', v)}>
1038
+ <SelectTrigger className="h-8 w-[70px]">
1039
+ <SelectValue placeholder={pageSize.toString()} />
1040
+ </SelectTrigger>
1041
+ <SelectContent side="top">
1042
+ {[10, 20, 30, 50, 100].map((size) => (
1043
+ <SelectItem key={size} value={size.toString()}>
1044
+ {size}
1045
+ </SelectItem>
1046
+ ))}
1047
+ </SelectContent>
1048
+ </Select>
1049
+ <span>entries</span>
1050
+ </div>
1051
+ <Pagination className="justify-end w-auto">
1052
+ <PaginationContent>
1053
+ <PaginationItem>
1054
+ <PaginationPrevious href="#" />
1055
+ </PaginationItem>
1056
+ <PaginationItem>
1057
+ <PaginationLink href="#" isActive>1</PaginationLink>
1058
+ </PaginationItem>
1059
+ <PaginationItem>
1060
+ <PaginationLink href="#">2</PaginationLink>
1061
+ </PaginationItem>
1062
+ <PaginationItem>
1063
+ <PaginationLink href="#">3</PaginationLink>
1064
+ </PaginationItem>
1065
+ <PaginationItem>
1066
+ <PaginationNext href="#" />
1067
+ </PaginationItem>
1068
+ </PaginationContent>
1069
+ </Pagination>
1070
+ </div>
1071
+ </div>
1072
+ );
1073
+ }
1074
+ `;
1075
+ }
1076
+
1077
+ // src/templates/ddd/page.ts
1078
+ function generatePage(config) {
1079
+ const { pageName, moduleName, entityName } = config;
1080
+ return `import { Suspense } from 'react';
1081
+ import { ${entityName}List } from '@/modules/${moduleName}/presentation/components/${moduleName}-list';
1082
+
1083
+ /**
1084
+ * ${pageName} Page
1085
+ * Generated by shadcn-page-gen
1086
+ */
1087
+ export default function ${entityName}Page() {
1088
+ return (
1089
+ <div className="container mx-auto py-10 space-y-8">
1090
+ <div>
1091
+ <h1 className="text-3xl font-bold tracking-tight">${pageName}</h1>
1092
+ <p className="text-muted-foreground">
1093
+ Manage your ${pageName.toLowerCase()}.
1094
+ </p>
1095
+ </div>
1096
+
1097
+ <Suspense fallback={<div>Loading...</div>}>
1098
+ <${entityName}List />
1099
+ </Suspense>
1100
+ </div>
1101
+ );
1102
+ }
1103
+ `;
1104
+ }
1105
+
1106
+ // src/templates/ddd/template.ts
1107
+ function generateTemplate(config) {
1108
+ const { animations } = config;
1109
+ const getAnimationConfig = () => {
1110
+ switch (animations.intensity) {
1111
+ case "subtle":
1112
+ return {
1113
+ initial: "{ opacity: 0, y: 10 }",
1114
+ animate: "{ opacity: 1, y: 0 }",
1115
+ transition: "{ duration: 0.2 }"
1116
+ };
1117
+ case "bold":
1118
+ return {
1119
+ initial: "{ opacity: 0, scale: 0.95, y: 30 }",
1120
+ animate: "{ opacity: 1, scale: 1, y: 0 }",
1121
+ transition: "{ duration: 0.4, ease: [0.43, 0.13, 0.23, 0.96] }"
1122
+ };
1123
+ default:
1124
+ return {
1125
+ initial: "{ opacity: 0, y: 20 }",
1126
+ animate: "{ opacity: 1, y: 0 }",
1127
+ transition: '{ duration: 0.3, ease: "easeInOut" }'
1128
+ };
1129
+ }
1130
+ };
1131
+ const animConfig = getAnimationConfig();
1132
+ return `'use client';
1133
+
1134
+ import { motion } from 'framer-motion';
1135
+
1136
+ /**
1137
+ * Page Template with Framer Motion transitions
1138
+ * Generated by shadcn-page-gen
1139
+ */
1140
+ export default function Template({ children }: { children: React.ReactNode }) {
1141
+ return (
1142
+ <motion.div
1143
+ initial=${animConfig.initial}
1144
+ animate=${animConfig.animate}
1145
+ exit={{ opacity: 0, y: -20 }}
1146
+ transition=${animConfig.transition}
1147
+ >
1148
+ {children}
1149
+ </motion.div>
1150
+ );
1151
+ }
1152
+ `;
1153
+ }
1154
+
1155
+ // src/generators/ddd-generator.ts
1156
+ var DDDGenerator = class {
1157
+ constructor(config) {
1158
+ this.config = config;
1159
+ }
1160
+ async generate() {
1161
+ const files = [];
1162
+ const cwd = process.cwd();
1163
+ const { moduleName, routePath } = this.config;
1164
+ const moduleDir = import_path2.default.join(cwd, "modules", moduleName);
1165
+ const appDir = import_path2.default.join(cwd, "app", "(dashboard)", routePath);
1166
+ const dirs = [
1167
+ import_path2.default.join(moduleDir, "domain", "entities"),
1168
+ import_path2.default.join(moduleDir, "domain", "repositories"),
1169
+ import_path2.default.join(moduleDir, "application", "use-cases"),
1170
+ import_path2.default.join(moduleDir, "infrastructure", "repositories"),
1171
+ import_path2.default.join(moduleDir, "presentation", "components"),
1172
+ appDir
1173
+ ];
1174
+ await createDirectories(dirs);
1175
+ files.push({
1176
+ path: import_path2.default.join(moduleDir, "domain", "entities", `${moduleName}.entity.ts`),
1177
+ content: generateEntity(this.config)
1178
+ });
1179
+ files.push({
1180
+ path: import_path2.default.join(moduleDir, "domain", "repositories", `${moduleName}.repository.interface.ts`),
1181
+ content: generateRepositoryInterface(this.config)
1182
+ });
1183
+ files.push({
1184
+ path: import_path2.default.join(moduleDir, "infrastructure", "repositories", `${moduleName}.repository.ts`),
1185
+ content: generateRepositoryImpl(this.config)
1186
+ });
1187
+ files.push({
1188
+ path: import_path2.default.join(moduleDir, "application", "use-cases", `get-${moduleName}s.use-case.ts`),
1189
+ content: generateUseCase(this.config)
1190
+ });
1191
+ files.push({
1192
+ path: import_path2.default.join(moduleDir, "presentation", "components", `${moduleName}-list.tsx`),
1193
+ content: generateComponent(this.config)
1194
+ });
1195
+ files.push({
1196
+ path: import_path2.default.join(appDir, "page.tsx"),
1197
+ content: generatePage(this.config)
1198
+ });
1199
+ if (this.config.animations.pageTransitions) {
1200
+ files.push({
1201
+ path: import_path2.default.join(appDir, "template.tsx"),
1202
+ content: generateTemplate(this.config)
1203
+ });
1204
+ }
1205
+ await writeFiles(files);
1206
+ const instructions = this.generateInstructions();
1207
+ return { files, instructions };
1208
+ }
1209
+ generateInstructions() {
1210
+ const instructions = [];
1211
+ instructions.push(`Navigate to your page: http://localhost:3000/${this.config.routePath}`);
1212
+ const deps = [];
1213
+ if (this.config.dataFetching === "tanstack") {
1214
+ deps.push("@tanstack/react-query");
1215
+ }
1216
+ if (this.config.animations.pageTransitions || this.config.animations.listAnimations) {
1217
+ deps.push("framer-motion");
1218
+ }
1219
+ if (deps.length > 0) {
1220
+ instructions.push(`Install dependencies: npm install ${deps.join(" ")}`);
1221
+ }
1222
+ instructions.push("Customize the generated code to fit your needs");
1223
+ instructions.push("Connect to your real API (replace mock repository)");
1224
+ if (this.config.dataFetching === "tanstack") {
1225
+ instructions.push("Ensure your app is wrapped in <QueryClientProvider>");
1226
+ }
1227
+ return instructions;
1228
+ }
1229
+ };
1230
+
1231
+ // src/generators/simplified-generator.ts
1232
+ var import_path3 = __toESM(require("path"), 1);
1233
+
1234
+ // src/templates/simplified/component.ts
1235
+ function generateSimplifiedComponent(config) {
1236
+ const {
1237
+ entityName,
1238
+ moduleName,
1239
+ pageName,
1240
+ routePath,
1241
+ columns,
1242
+ filters,
1243
+ includeStats,
1244
+ includeRowSelection,
1245
+ dataFetching,
1246
+ sortableColumns,
1247
+ animations
1248
+ } = config;
1249
+ const isTanStack = dataFetching === "tanstack";
1250
+ const hasAnimations = animations.listAnimations || animations.cardAnimations;
1251
+ const mockDataInterface = `interface ${entityName} {
1252
+ id: string;
1253
+ ${columns.map((c) => {
1254
+ const typeMap = {
1255
+ "string": "string",
1256
+ "number": "number",
1257
+ "boolean": "boolean",
1258
+ "date": "string"
1259
+ };
1260
+ return ` ${c.key}: ${typeMap[c.type]};`;
1261
+ }).join("\n")}
1262
+ updatedAt: Date;
1263
+ }`;
1264
+ const mockDataFields = columns.map((c) => {
1265
+ if (c.key === "createdAt") {
1266
+ return ` createdAt: new Date().toISOString(),`;
1267
+ }
1268
+ if (c.key === "status") {
1269
+ return ` status: Math.random() > 0.5 ? 'Active' : 'Inactive',`;
1270
+ }
1271
+ if (c.key === "name") {
1272
+ return ` name: \`${entityName} \${i + 1}\`,`;
1273
+ }
1274
+ if (c.type === "number") {
1275
+ return ` ${c.key}: (i + 1) * 100,`;
1276
+ }
1277
+ if (c.type === "boolean") {
1278
+ return ` ${c.key}: Math.random() > 0.5,`;
1279
+ }
1280
+ if (c.type === "date") {
1281
+ return ` ${c.key}: new Date().toISOString(),`;
1282
+ }
1283
+ return ` ${c.key}: \`${c.label} \${i + 1}\`,`;
1284
+ }).join("\n");
1285
+ const mockData = `// Mock data - replace with your API call
1286
+ const MOCK_DATA: ${entityName}[] = Array.from({ length: 10 }).map((_, i) => ({
1287
+ id: (i + 1).toString(),
1288
+ ${mockDataFields}
1289
+ updatedAt: new Date(),
1290
+ })) as unknown as ${entityName}[];`;
1291
+ const fetchFunction = isTanStack ? "" : `
1292
+ // Fetch data function - replace with your API call
1293
+ const fetchData = async () => {
1294
+ setLoading(true);
1295
+ try {
1296
+ // Simulate API delay
1297
+ await new Promise(resolve => setTimeout(resolve, 500));
1298
+
1299
+ // TODO: Replace with real API call
1300
+ // const response = await fetch('/api/${moduleName}');
1301
+ // const result = await response.json();
1302
+
1303
+ setData(MOCK_DATA);
1304
+ } catch (error) {
1305
+ console.error('Error fetching data:', error);
1306
+ } finally {
1307
+ setLoading(false);
1308
+ }
1309
+ };
1310
+
1311
+ useEffect(() => {
1312
+ fetchData();
1313
+ }, [searchParams]);`;
1314
+ const dataFetchingSetup = isTanStack ? `
1315
+ // TanStack Query data fetching
1316
+ const { data, isLoading: loading, refetch } = useQuery({
1317
+ queryKey: ['${moduleName}', searchParams.toString()],
1318
+ queryFn: async () => {
1319
+ // Simulate API delay
1320
+ await new Promise(resolve => setTimeout(resolve, 500));
1321
+
1322
+ // TODO: Replace with real API call
1323
+ // const response = await fetch('/api/${moduleName}');
1324
+ // return await response.json();
1325
+
1326
+ return MOCK_DATA;
1327
+ }
1328
+ });` : `
1329
+ const [data, setData] = useState<${entityName}[]>([]);
1330
+ const [loading, setLoading] = useState(true);
1331
+ ${fetchFunction}`;
1332
+ const imports = `'use client';
1333
+
1334
+ ${hasAnimations ? `import { motion } from 'framer-motion';` : ""}
1335
+ import { useEffect, useState } from 'react';
1336
+ ${isTanStack ? `import { useQuery } from '@tanstack/react-query';` : ""}
1337
+ import { Button } from '@/components/ui/button';
1338
+ import {
1339
+ Table,
1340
+ TableBody,
1341
+ TableCell,
1342
+ TableHead,
1343
+ TableHeader,
1344
+ TableRow,
1345
+ } from '@/components/ui/table';
1346
+ import {
1347
+ Select,
1348
+ SelectContent,
1349
+ SelectItem,
1350
+ SelectTrigger,
1351
+ SelectValue,
1352
+ } from '@/components/ui/select';
1353
+ import { Input } from '@/components/ui/input';
1354
+ ${filters.some((f) => f.type === "date") ? `import { Calendar } from '@/components/ui/calendar';
1355
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
1356
+ import { Calendar as CalendarIcon } from 'lucide-react';
1357
+ import { format } from 'date-fns';` : ""}
1358
+ import { Badge } from '@/components/ui/badge';
1359
+ ${includeStats ? `import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';` : ""}
1360
+ import {
1361
+ Search,
1362
+ Plus,
1363
+ X,
1364
+ RefreshCcw,
1365
+ Eye,
1366
+ Pencil,
1367
+ Trash2,
1368
+ MoreHorizontal,
1369
+ ${sortableColumns.length > 0 ? "ArrowUpDown, ArrowUp, ArrowDown," : ""}
1370
+ } from 'lucide-react';
1371
+ import { cn } from '@/lib/utils';
1372
+ import { useRouter, useSearchParams } from 'next/navigation';
1373
+ import {
1374
+ Pagination,
1375
+ PaginationContent,
1376
+ PaginationItem,
1377
+ PaginationLink,
1378
+ PaginationNext,
1379
+ PaginationPrevious,
1380
+ } from "@/components/ui/pagination";
1381
+ import {
1382
+ DropdownMenu,
1383
+ DropdownMenuContent,
1384
+ DropdownMenuItem,
1385
+ DropdownMenuLabel,
1386
+ DropdownMenuTrigger,
1387
+ } from "@/components/ui/dropdown-menu";
1388
+ ${includeRowSelection ? `import { Checkbox } from "@/components/ui/checkbox";` : ""}`;
1389
+ const animationVariants = hasAnimations ? `
1390
+ // Framer Motion animation variants
1391
+ const containerVariants = {
1392
+ hidden: { opacity: 0 },
1393
+ visible: {
1394
+ opacity: 1,
1395
+ transition: {
1396
+ staggerChildren: ${animations.intensity === "bold" ? "0.1" : animations.intensity === "subtle" ? "0.03" : "0.05"}
1397
+ }
1398
+ }
1399
+ };
1400
+
1401
+ const itemVariants = {
1402
+ hidden: { opacity: 0, x: -20 },
1403
+ visible: {
1404
+ opacity: 1,
1405
+ x: 0,
1406
+ transition: { duration: ${animations.intensity === "bold" ? "0.3" : animations.intensity === "subtle" ? "0.15" : "0.2"} }
1407
+ }
1408
+ };
1409
+ ` : "";
1410
+ const rowSelectionCode = includeRowSelection ? `
1411
+ const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set());
1412
+
1413
+ const toggleRow = (id: string) => {
1414
+ const newSelected = new Set(selectedRows);
1415
+ if (newSelected.has(id)) newSelected.delete(id);
1416
+ else newSelected.add(id);
1417
+ setSelectedRows(newSelected);
1418
+ };
1419
+
1420
+ const toggleAll = () => {
1421
+ if (selectedRows.size === (data?.length || 0)) {
1422
+ setSelectedRows(new Set());
1423
+ } else {
1424
+ setSelectedRows(new Set(data?.map(i => i.id) || []));
1425
+ }
1426
+ };` : "";
1427
+ const statsCards = includeStats ? `
1428
+ {/* Stats Cards */}
1429
+ ${animations.cardAnimations ? `<motion.div
1430
+ className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"
1431
+ variants={containerVariants}
1432
+ initial="hidden"
1433
+ animate="visible"
1434
+ >` : `<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">`}
1435
+ ${animations.cardAnimations ? `<motion.div variants={itemVariants}>` : ""}
1436
+ <Card>
1437
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
1438
+ <CardTitle className="text-sm font-medium">Total ${entityName}s</CardTitle>
1439
+ </CardHeader>
1440
+ <CardContent>
1441
+ <div className="text-2xl font-bold">128</div>
1442
+ <p className="text-xs text-muted-foreground">+20.1% from last month</p>
1443
+ </CardContent>
1444
+ </Card>
1445
+ ${animations.cardAnimations ? `</motion.div>` : ""}
1446
+
1447
+ ${animations.cardAnimations ? `<motion.div variants={itemVariants}>` : ""}
1448
+ <Card>
1449
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
1450
+ <CardTitle className="text-sm font-medium">Active</CardTitle>
1451
+ </CardHeader>
1452
+ <CardContent>
1453
+ <div className="text-2xl font-bold">12</div>
1454
+ <p className="text-xs text-muted-foreground">+4 since yesterday</p>
1455
+ </CardContent>
1456
+ </Card>
1457
+ ${animations.cardAnimations ? `</motion.div>` : ""}
1458
+
1459
+ ${animations.cardAnimations ? `<motion.div variants={itemVariants}>` : ""}
1460
+ <Card>
1461
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
1462
+ <CardTitle className="text-sm font-medium">Pending</CardTitle>
1463
+ </CardHeader>
1464
+ <CardContent>
1465
+ <div className="text-2xl font-bold">4</div>
1466
+ <p className="text-xs text-muted-foreground">-2 since yesterday</p>
1467
+ </CardContent>
1468
+ </Card>
1469
+ ${animations.cardAnimations ? `</motion.div>` : ""}
1470
+
1471
+ ${animations.cardAnimations ? `<motion.div variants={itemVariants}>` : ""}
1472
+ <Card>
1473
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
1474
+ <CardTitle className="text-sm font-medium">Closed</CardTitle>
1475
+ </CardHeader>
1476
+ <CardContent>
1477
+ <div className="text-2xl font-bold">2</div>
1478
+ <p className="text-xs text-muted-foreground">+1 since yesterday</p>
1479
+ </CardContent>
1480
+ </Card>
1481
+ ${animations.cardAnimations ? `</motion.div>` : ""}
1482
+ ${animations.cardAnimations ? `</motion.div>` : `</div>`}
1483
+ ` : "";
1484
+ const filterComponents = filters.map((f) => {
1485
+ if (f.type === "select") {
1486
+ return `
1487
+ <Select
1488
+ value={searchParams.get('${f.key}') || 'all'}
1489
+ onValueChange={(value) => updateParam('${f.key}', value === 'all' ? null : value)}
1490
+ >
1491
+ <SelectTrigger className="w-[150px]">
1492
+ <SelectValue placeholder="${f.label}" />
1493
+ </SelectTrigger>
1494
+ <SelectContent>
1495
+ <SelectItem value="all">All ${f.label}</SelectItem>
1496
+ <SelectItem value="active">Active</SelectItem>
1497
+ <SelectItem value="inactive">Inactive</SelectItem>
1498
+ </SelectContent>
1499
+ </Select>`;
1500
+ } else if (f.type === "date") {
1501
+ return `
1502
+ <Popover>
1503
+ <PopoverTrigger asChild>
1504
+ <Button
1505
+ variant="outline"
1506
+ className={cn(
1507
+ "w-[200px] justify-start text-left font-normal",
1508
+ !searchParams.get('${f.key}') && "text-muted-foreground"
1509
+ )}
1510
+ >
1511
+ <CalendarIcon className="mr-2 h-4 w-4" />
1512
+ {searchParams.get('${f.key}') ? (
1513
+ format(new Date(searchParams.get('${f.key}')!), "PPP")
1514
+ ) : (
1515
+ <span>${f.label}</span>
1516
+ )}
1517
+ </Button>
1518
+ </PopoverTrigger>
1519
+ <PopoverContent className="w-auto p-0" align="start">
1520
+ <Calendar
1521
+ mode="single"
1522
+ selected={searchParams.get('${f.key}') ? new Date(searchParams.get('${f.key}')!) : undefined}
1523
+ onSelect={(date) => updateParam('${f.key}', date ? date.toISOString() : null)}
1524
+ initialFocus
1525
+ />
1526
+ </PopoverContent>
1527
+ </Popover>`;
1528
+ }
1529
+ return "";
1530
+ }).join("\n");
1531
+ const tableHeaders = columns.map((c) => {
1532
+ const isSortable = sortableColumns.includes(c.key);
1533
+ if (isSortable) {
1534
+ return ` <TableHead className="cursor-pointer" onClick={() => handleSort('${c.key}')}>
1535
+ <div className="flex items-center gap-1">
1536
+ ${c.label.toUpperCase()}
1537
+ {searchParams.get('sortBy') === '${c.key}' ? (
1538
+ searchParams.get('order') === 'asc' ? <ArrowUp className="h-3 w-3" /> : <ArrowDown className="h-3 w-3" />
1539
+ ) : <ArrowUpDown className="h-3 w-3 text-muted-foreground" />}
1540
+ </div>
1541
+ </TableHead>`;
1542
+ }
1543
+ return ` <TableHead>${c.label.toUpperCase()}</TableHead>`;
1544
+ }).join("\n");
1545
+ const tableCells = columns.map((c) => {
1546
+ if (c.key === "status") {
1547
+ return ` <TableCell>
1548
+ <Badge variant={item.status === 'Active' ? 'default' : 'secondary'} className="rounded-full">
1549
+ {item.status}
1550
+ </Badge>
1551
+ </TableCell>`;
1552
+ }
1553
+ if (c.type === "date") {
1554
+ return ` <TableCell className="text-muted-foreground">{new Date(item.${c.key}).toLocaleDateString()}</TableCell>`;
1555
+ }
1556
+ return ` <TableCell>{item.${c.key}}</TableCell>`;
1557
+ }).join("\n");
1558
+ const colSpan = columns.length + 2 + (includeRowSelection ? 1 : 0);
1559
+ return `${imports}
1560
+
1561
+ /**
1562
+ * ${entityName} List Component (Simplified Architecture)
1563
+ * Generated by shadcn-page-gen
1564
+ */
1565
+
1566
+ ${mockDataInterface}
1567
+
1568
+ ${mockData}
1569
+ ${animationVariants}
1570
+ export function ${entityName}List() {
1571
+ const router = useRouter();
1572
+ const searchParams = useSearchParams();
1573
+
1574
+ const [searchTerm, setSearchTerm] = useState(searchParams.get('q') || '');
1575
+ const pageSize = Number(searchParams.get('limit')) || 10;
1576
+ ${dataFetchingSetup}
1577
+ ${rowSelectionCode}
1578
+
1579
+ const updateParam = (key: string, value: string | null) => {
1580
+ const params = new URLSearchParams(searchParams.toString());
1581
+ if (value) params.set(key, value);
1582
+ else params.delete(key);
1583
+
1584
+ if (key !== 'page') params.set('page', '1');
1585
+
1586
+ router.push('?' + params.toString());
1587
+ };
1588
+
1589
+ ${sortableColumns.length > 0 ? `const handleSort = (key: string) => {
1590
+ const currentSort = searchParams.get('sortBy');
1591
+ const currentOrder = searchParams.get('order');
1592
+
1593
+ let newOrder = 'asc';
1594
+ if (currentSort === key && currentOrder === 'asc') {
1595
+ newOrder = 'desc';
1596
+ }
1597
+
1598
+ const params = new URLSearchParams(searchParams.toString());
1599
+ params.set('sortBy', key);
1600
+ params.set('order', newOrder);
1601
+ router.push('?' + params.toString());
1602
+ };` : ""}
1603
+
1604
+ return (
1605
+ <div className="space-y-6">
1606
+ ${statsCards}
1607
+ {/* Actions Bar */}
1608
+ <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
1609
+ <div className="flex flex-1 items-center gap-2">
1610
+ <div className="relative flex-1">
1611
+ <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
1612
+ <Input
1613
+ placeholder="Search..."
1614
+ value={searchTerm}
1615
+ onChange={(e) => setSearchTerm(e.target.value)}
1616
+ onBlur={() => updateParam('q', searchTerm)}
1617
+ className="pl-9 w-full md:max-w-md"
1618
+ />
1619
+ </div>
1620
+ ${filterComponents}
1621
+ {(searchParams.toString().length > 0) && (
1622
+ <Button variant="ghost" size="icon" onClick={() => router.push('/${routePath}')} title="Reset Filters">
1623
+ <X className="h-4 w-4" />
1624
+ </Button>
1625
+ )}
1626
+ </div>
1627
+
1628
+ <div className="flex items-center gap-2">
1629
+ <Button variant="outline" size="sm" onClick={() => ${isTanStack ? "refetch()" : "fetchData()"}}>
1630
+ <RefreshCcw className="mr-2 h-4 w-4" /> Refresh
1631
+ </Button>
1632
+ <Button size="sm">
1633
+ <Plus className="mr-2 h-4 w-4" /> New ${entityName}
1634
+ </Button>
1635
+ </div>
1636
+ </div>
1637
+
1638
+ {/* Main Table */}
1639
+ <Card>
1640
+ <CardContent className="p-0">
1641
+ <Table>
1642
+ <TableHeader>
1643
+ <TableRow>
1644
+ ${includeRowSelection ? `<TableHead className="w-[50px]"><Checkbox checked={data?.length > 0 && selectedRows.size === data?.length} onCheckedChange={toggleAll} /></TableHead>` : ""}
1645
+ <TableHead className="w-[100px]">ID</TableHead>
1646
+ ${tableHeaders}
1647
+ <TableHead className="text-right">ACTIONS</TableHead>
1648
+ </TableRow>
1649
+ </TableHeader>
1650
+ <TableBody>
1651
+ {loading ? (
1652
+ <TableRow>
1653
+ <TableCell colSpan={${colSpan}} className="text-center h-24 text-muted-foreground">Loading data...</TableCell>
1654
+ </TableRow>
1655
+ ) : !data || data.length === 0 ? (
1656
+ <TableRow>
1657
+ <TableCell colSpan={${colSpan}} className="text-center h-24 text-muted-foreground">No results found.</TableCell>
1658
+ </TableRow>
1659
+ ) : (
1660
+ ${animations.listAnimations ? `
1661
+ data.map((item, index) => (
1662
+ <motion.tr
1663
+ key={item.id}
1664
+ variants={itemVariants}
1665
+ initial="hidden"
1666
+ animate="visible"
1667
+ custom={index}
1668
+ className="group"
1669
+ >` : `data.map((item) => (
1670
+ <TableRow key={item.id} className="group">`}
1671
+ ${includeRowSelection ? `<TableCell><Checkbox checked={selectedRows.has(item.id)} onCheckedChange={() => toggleRow(item.id)} /></TableCell>` : ""}
1672
+ <TableCell className="font-medium">{item.id}</TableCell>
1673
+ ${tableCells}
1674
+ <TableCell className="text-right">
1675
+ <div className="flex justify-end gap-2">
1676
+ <Button variant="ghost" size="icon" className="h-8 w-8 text-blue-500 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-950">
1677
+ <Eye className="h-4 w-4" />
1678
+ </Button>
1679
+ <Button variant="ghost" size="icon" className="h-8 w-8 text-green-500 hover:text-green-600 hover:bg-green-50 dark:hover:bg-green-950">
1680
+ <Pencil className="h-4 w-4" />
1681
+ </Button>
1682
+ <Button variant="ghost" size="icon" className="h-8 w-8 text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-950">
1683
+ <Trash2 className="h-4 w-4" />
1684
+ </Button>
1685
+ <DropdownMenu>
1686
+ <DropdownMenuTrigger asChild>
1687
+ <Button variant="ghost" size="icon" className="h-8 w-8">
1688
+ <MoreHorizontal className="h-4 w-4" />
1689
+ </Button>
1690
+ </DropdownMenuTrigger>
1691
+ <DropdownMenuContent align="end">
1692
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
1693
+ <DropdownMenuItem>View Details</DropdownMenuItem>
1694
+ <DropdownMenuItem>Edit Record</DropdownMenuItem>
1695
+ </DropdownMenuContent>
1696
+ </DropdownMenu>
1697
+ </div>
1698
+ </TableCell>
1699
+ ${animations.listAnimations ? `</motion.tr>` : `</TableRow>`}
1700
+ ))
1701
+ )}
1702
+ </TableBody>
1703
+ </Table>
1704
+ </CardContent>
1705
+ </Card>
1706
+
1707
+ {/* Pagination */}
1708
+ <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
1709
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
1710
+ <span>Show</span>
1711
+ <Select value={pageSize.toString()} onValueChange={(v) => updateParam('limit', v)}>
1712
+ <SelectTrigger className="h-8 w-[70px]">
1713
+ <SelectValue placeholder={pageSize.toString()} />
1714
+ </SelectTrigger>
1715
+ <SelectContent side="top">
1716
+ {[10, 20, 30, 50, 100].map((size) => (
1717
+ <SelectItem key={size} value={size.toString()}>
1718
+ {size}
1719
+ </SelectItem>
1720
+ ))}
1721
+ </SelectContent>
1722
+ </Select>
1723
+ <span>entries</span>
1724
+ </div>
1725
+ <Pagination className="justify-end w-auto">
1726
+ <PaginationContent>
1727
+ <PaginationItem>
1728
+ <PaginationPrevious href="#" />
1729
+ </PaginationItem>
1730
+ <PaginationItem>
1731
+ <PaginationLink href="#" isActive>1</PaginationLink>
1732
+ </PaginationItem>
1733
+ <PaginationItem>
1734
+ <PaginationLink href="#">2</PaginationLink>
1735
+ </PaginationItem>
1736
+ <PaginationItem>
1737
+ <PaginationLink href="#">3</PaginationLink>
1738
+ </PaginationItem>
1739
+ <PaginationItem>
1740
+ <PaginationNext href="#" />
1741
+ </PaginationItem>
1742
+ </PaginationContent>
1743
+ </Pagination>
1744
+ </div>
1745
+ </div>
1746
+ );
1747
+ }
1748
+ `;
1749
+ }
1750
+
1751
+ // src/templates/simplified/page.ts
1752
+ function generateSimplifiedPage(config) {
1753
+ const { pageName, moduleName, entityName } = config;
1754
+ return `import { Suspense } from 'react';
1755
+ import { ${entityName}List } from '@/components/${moduleName}/${moduleName}-list';
1756
+
1757
+ /**
1758
+ * ${pageName} Page
1759
+ * Generated by shadcn-page-gen (Simplified Architecture)
1760
+ */
1761
+ export default function ${entityName}Page() {
1762
+ return (
1763
+ <div className="container mx-auto py-10 space-y-8">
1764
+ <div>
1765
+ <h1 className="text-3xl font-bold tracking-tight">${pageName}</h1>
1766
+ <p className="text-muted-foreground">
1767
+ Manage your ${pageName.toLowerCase()}.
1768
+ </p>
1769
+ </div>
1770
+
1771
+ <Suspense fallback={<div>Loading...</div>}>
1772
+ <${entityName}List />
1773
+ </Suspense>
1774
+ </div>
1775
+ );
1776
+ }
1777
+ `;
1778
+ }
1779
+
1780
+ // src/generators/simplified-generator.ts
1781
+ var SimplifiedGenerator = class {
1782
+ constructor(config) {
1783
+ this.config = config;
1784
+ }
1785
+ async generate() {
1786
+ const files = [];
1787
+ const cwd = process.cwd();
1788
+ const { moduleName, routePath } = this.config;
1789
+ const componentDir = import_path3.default.join(cwd, "components", moduleName);
1790
+ const appDir = import_path3.default.join(cwd, "app", "(dashboard)", routePath);
1791
+ await createDirectories([componentDir, appDir]);
1792
+ files.push({
1793
+ path: import_path3.default.join(componentDir, `${moduleName}-list.tsx`),
1794
+ content: generateSimplifiedComponent(this.config)
1795
+ });
1796
+ files.push({
1797
+ path: import_path3.default.join(appDir, "page.tsx"),
1798
+ content: generateSimplifiedPage(this.config)
1799
+ });
1800
+ if (this.config.animations.pageTransitions) {
1801
+ files.push({
1802
+ path: import_path3.default.join(appDir, "template.tsx"),
1803
+ content: generateTemplate(this.config)
1804
+ });
1805
+ }
1806
+ await writeFiles(files);
1807
+ const instructions = this.generateInstructions();
1808
+ return { files, instructions };
1809
+ }
1810
+ generateInstructions() {
1811
+ const instructions = [];
1812
+ instructions.push(`Navigate to your page: http://localhost:3000/${this.config.routePath}`);
1813
+ const deps = [];
1814
+ if (this.config.dataFetching === "tanstack") {
1815
+ deps.push("@tanstack/react-query");
1816
+ }
1817
+ if (this.config.animations.pageTransitions || this.config.animations.listAnimations) {
1818
+ deps.push("framer-motion");
1819
+ }
1820
+ if (deps.length > 0) {
1821
+ instructions.push(`Install dependencies: npm install ${deps.join(" ")}`);
1822
+ }
1823
+ instructions.push("Customize the generated code to fit your needs");
1824
+ instructions.push("Replace mock data with your real API");
1825
+ if (this.config.dataFetching === "tanstack") {
1826
+ instructions.push("Ensure your app is wrapped in <QueryClientProvider>");
1827
+ }
1828
+ return instructions;
1829
+ }
1830
+ };
1831
+
1832
+ // src/generators/index.ts
1833
+ var PageGenerator = class {
1834
+ constructor(config) {
1835
+ this.config = config;
1836
+ this.generator = config.architecture === "ddd" ? new DDDGenerator(config) : new SimplifiedGenerator(config);
1837
+ }
1838
+ generator;
1839
+ /**
1840
+ * Generates all files and returns result
1841
+ */
1842
+ async generate() {
1843
+ return await this.generator.generate();
1844
+ }
1845
+ };
1846
+
1847
+ // src/cli/index.ts
1848
+ async function run() {
1849
+ try {
1850
+ console.clear();
1851
+ logger.title("\u{1F3A8} shadcn-page-gen");
1852
+ logger.dim("Generate production-ready Next.js pages with shadcn/ui, Tailwind v4, and Framer Motion\n");
1853
+ const config = await collectConfiguration();
1854
+ if (!config) {
1855
+ logger.warning("Operation cancelled");
1856
+ process.exit(0);
1857
+ }
1858
+ console.log("\n");
1859
+ logger.title("\u{1F4CB} Configuration Summary");
1860
+ logger.info(`Page Name: ${config.pageName}`);
1861
+ logger.info(`Route: /${config.routePath}`);
1862
+ logger.info(`Architecture: ${config.architecture.toUpperCase()}`);
1863
+ logger.info(`Data Fetching: ${config.dataFetching}`);
1864
+ logger.info(`Animations: ${config.animations.intensity} intensity`);
1865
+ console.log("");
1866
+ const spinner = (0, import_ora.default)("Generating files...").start();
1867
+ try {
1868
+ const generator = new PageGenerator(config);
1869
+ const result = await generator.generate();
1870
+ spinner.succeed("Files generated successfully!");
1871
+ console.log("");
1872
+ logger.title("\u{1F4C1} Generated Files");
1873
+ result.files.forEach((file) => {
1874
+ logger.dim(` ${file.path}`);
1875
+ });
1876
+ console.log("");
1877
+ logger.title("\u{1F389} Success!");
1878
+ logger.success("Your page has been generated!");
1879
+ console.log("");
1880
+ logger.title("\u{1F4DD} Next Steps");
1881
+ result.instructions.forEach((instruction, index) => {
1882
+ logger.step(index + 1, result.instructions.length, instruction);
1883
+ });
1884
+ console.log("");
1885
+ logger.dim("Happy coding! \u{1F680}\n");
1886
+ } catch (error) {
1887
+ spinner.fail("Generation failed");
1888
+ throw error;
1889
+ }
1890
+ } catch (error) {
1891
+ logger.error("An error occurred:");
1892
+ console.error(error);
1893
+ process.exit(1);
1894
+ }
1895
+ }
1896
+ // Annotate the CommonJS export names for ESM import in node:
1897
+ 0 && (module.exports = {
1898
+ run
1899
+ });
1900
+ //# sourceMappingURL=index.cjs.map