@shivasankaran18/stackd 1.1.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/.github/workflows/ci.yml +30 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/apps/cli/package.json +32 -0
- package/apps/cli/src/cli.ts +271 -0
- package/apps/cli/src/commands/create.ts +162 -0
- package/apps/cli/src/scripts/Auth/jwt.ts +83 -0
- package/apps/cli/src/scripts/Auth/nextAuth.ts +146 -0
- package/apps/cli/src/scripts/Auth/passport.ts +234 -0
- package/apps/cli/src/scripts/backend/django.ts +30 -0
- package/apps/cli/src/scripts/backend/expressjs.ts +72 -0
- package/apps/cli/src/scripts/backend/expressts.ts +95 -0
- package/apps/cli/src/scripts/frontend/angularjs.ts +0 -0
- package/apps/cli/src/scripts/frontend/angularts.ts +29 -0
- package/apps/cli/src/scripts/frontend/nextjs.ts +72 -0
- package/apps/cli/src/scripts/frontend/reactjs.ts +36 -0
- package/apps/cli/src/scripts/frontend/reactts.ts +34 -0
- package/apps/cli/src/scripts/frontend/vuejs.ts +43 -0
- package/apps/cli/src/scripts/frontend/vuets.ts +53 -0
- package/apps/cli/src/scripts/orms/drizzleSetup.ts +102 -0
- package/apps/cli/src/scripts/orms/mongoSetup.ts +68 -0
- package/apps/cli/src/scripts/orms/prismaSetup.ts +14 -0
- package/apps/cli/src/scripts/ui/shadcn.ts +228 -0
- package/apps/cli/src/scripts/ui/tailwindcss.ts +126 -0
- package/apps/cli/tsconfig.json +111 -0
- package/apps/web/app/api/auth/[...nextauth]/route.ts +7 -0
- package/apps/web/app/api/scaffold/route.ts +274 -0
- package/apps/web/app/favicon.ico +0 -0
- package/apps/web/app/fonts/GeistMonoVF.woff +0 -0
- package/apps/web/app/fonts/GeistVF.woff +0 -0
- package/apps/web/app/globals.css +158 -0
- package/apps/web/app/home/page.tsx +22 -0
- package/apps/web/app/layout.tsx +35 -0
- package/apps/web/app/page.module.css +188 -0
- package/apps/web/app/page.tsx +1 -0
- package/apps/web/app/providers.tsx +9 -0
- package/apps/web/app/scaffold/page.tsx +472 -0
- package/apps/web/components/Sidebar.tsx +108 -0
- package/apps/web/components/theme-provider.tsx +9 -0
- package/apps/web/components/ui/button.tsx +57 -0
- package/apps/web/components/ui/card.tsx +76 -0
- package/apps/web/components/ui/dropdown-menu.tsx +200 -0
- package/apps/web/components/ui/input.tsx +22 -0
- package/apps/web/components/ui/label.tsx +26 -0
- package/apps/web/components/ui/scroll-area.tsx +48 -0
- package/apps/web/components/ui/sonner.tsx +31 -0
- package/apps/web/components/ui/steps.tsx +36 -0
- package/apps/web/components/ui/switch.tsx +29 -0
- package/apps/web/components.json +21 -0
- package/apps/web/eslint.config.js +4 -0
- package/apps/web/lib/auth.ts +35 -0
- package/apps/web/lib/redis.ts +13 -0
- package/apps/web/lib/utils.ts +8 -0
- package/apps/web/next-env.d.ts +5 -0
- package/apps/web/next.config.js +4 -0
- package/apps/web/package.json +52 -0
- package/apps/web/postcss.config.js +6 -0
- package/apps/web/public/file-text.svg +3 -0
- package/apps/web/public/globe.svg +10 -0
- package/apps/web/public/next.svg +1 -0
- package/apps/web/public/turborepo-dark.svg +19 -0
- package/apps/web/public/turborepo-light.svg +19 -0
- package/apps/web/public/vercel.svg +10 -0
- package/apps/web/public/window.svg +3 -0
- package/apps/web/tailwind.config.js +65 -0
- package/apps/web/tsconfig.json +23 -0
- package/apps/web/types/global.d.ts +4 -0
- package/docker-compose.yml +14 -0
- package/package.json +55 -0
- package/packages/eslint-config/README.md +3 -0
- package/packages/eslint-config/base.js +32 -0
- package/packages/eslint-config/next.js +49 -0
- package/packages/eslint-config/package.json +24 -0
- package/packages/eslint-config/react-internal.js +39 -0
- package/packages/scripts/Auth/jwt.ts +83 -0
- package/packages/scripts/Auth/nextAuth.ts +146 -0
- package/packages/scripts/Auth/passport.ts +234 -0
- package/packages/scripts/backend/django.ts +30 -0
- package/packages/scripts/backend/expressjs.ts +72 -0
- package/packages/scripts/backend/expressts.ts +95 -0
- package/packages/scripts/frontend/angularjs.ts +0 -0
- package/packages/scripts/frontend/angularts.ts +29 -0
- package/packages/scripts/frontend/nextjs.ts +72 -0
- package/packages/scripts/frontend/reactjs.ts +36 -0
- package/packages/scripts/frontend/reactts.ts +34 -0
- package/packages/scripts/frontend/vuejs.ts +43 -0
- package/packages/scripts/frontend/vuets.ts +53 -0
- package/packages/scripts/orms/drizzleSetup.ts +102 -0
- package/packages/scripts/orms/mongoSetup.ts +68 -0
- package/packages/scripts/orms/prismaSetup.ts +14 -0
- package/packages/scripts/ui/shadcn.ts +228 -0
- package/packages/scripts/ui/tailwindcss.ts +126 -0
- package/packages/typescript-config/base.json +19 -0
- package/packages/typescript-config/nextjs.json +12 -0
- package/packages/typescript-config/package.json +9 -0
- package/packages/typescript-config/react-library.json +7 -0
- package/packages/ui/eslint.config.mjs +4 -0
- package/packages/ui/package.json +27 -0
- package/packages/ui/src/button.tsx +20 -0
- package/packages/ui/src/card.tsx +27 -0
- package/packages/ui/src/code.tsx +11 -0
- package/packages/ui/tsconfig.json +8 -0
- package/packages/ui/turbo/generators/config.ts +30 -0
- package/packages/ui/turbo/generators/templates/component.hbs +8 -0
- package/stackd.ts +134 -0
- package/start-web.sh +5 -0
- package/tsconfig.json +111 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/turbo.json +21 -0
@@ -0,0 +1,472 @@
|
|
1
|
+
'use client';
|
2
|
+
import React, { useState, useEffect } from 'react';
|
3
|
+
import { Button } from '@/components/ui/button';
|
4
|
+
import { Input } from '@/components/ui/input';
|
5
|
+
import { Card } from '@/components/ui/card';
|
6
|
+
import { Layout, Server, Database, FolderOpen } from 'lucide-react';
|
7
|
+
import { Steps } from '@/components/ui/steps';
|
8
|
+
import { toast } from 'sonner';
|
9
|
+
import { Label } from '@/components/ui/label';
|
10
|
+
import { motion } from 'framer-motion';
|
11
|
+
|
12
|
+
interface ProjectConfig {
|
13
|
+
projectName: string;
|
14
|
+
projectPath: string;
|
15
|
+
frontendPort: number;
|
16
|
+
backendPort: number;
|
17
|
+
frontend: string | 'Skip' | null;
|
18
|
+
backend: string | 'Skip' | null;
|
19
|
+
database: string | 'Skip' | null;
|
20
|
+
orm: string | 'Skip' | null;
|
21
|
+
dbUrl: string;
|
22
|
+
giturl: string | null;
|
23
|
+
ui : string | null;
|
24
|
+
}
|
25
|
+
|
26
|
+
const SkipButton = ({ onSkip }: { onSkip: () => void }) => (
|
27
|
+
<Button
|
28
|
+
variant="outline"
|
29
|
+
onClick={onSkip}
|
30
|
+
className="text-orange-500 hover:text-orange-600 hover:bg-orange-50 dark:hover:bg-orange-950/10 border-orange-200 hover:border-orange-300 dark:border-orange-800"
|
31
|
+
>
|
32
|
+
Skip
|
33
|
+
</Button>
|
34
|
+
);
|
35
|
+
|
36
|
+
const Navbar = () => (
|
37
|
+
<nav className="navbar backdrop-blur-sm">
|
38
|
+
<div className="max-w-6xl mx-auto px-8 py-3 flex justify-center">
|
39
|
+
<div className="flex flex-col items-center">
|
40
|
+
<pre className="text-cyan-500 text-xs leading-none font-bold">
|
41
|
+
{` ██████╗████████╗ █████╗ ██████╗██╗ ██╗'██████╗
|
42
|
+
██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝██╔══██╗
|
43
|
+
╚█████╗ ██║ ███████║██║ █████═╝ ██║ ██║
|
44
|
+
╚═══██╗ ██║ ██╔══██║██║ ██╔═██╗ ██║ ██║
|
45
|
+
██████╔╝ ██║ ██║ ██║╚██████╗██║ ██╗██████╔╝
|
46
|
+
╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═════╝`}
|
47
|
+
</pre>
|
48
|
+
<p className="text-sm text-cyan-500/80 mt-2">Full Stack Project Generator</p>
|
49
|
+
</div>
|
50
|
+
|
51
|
+
<a
|
52
|
+
href="https://github.com/ShyamSunder06/STACKD"
|
53
|
+
target="_blank"
|
54
|
+
rel="noopener noreferrer"
|
55
|
+
className="absolute right-8 text-muted-foreground hover:text-foreground"
|
56
|
+
>
|
57
|
+
<svg
|
58
|
+
viewBox="0 0 24 24"
|
59
|
+
className="h-6 w-6"
|
60
|
+
fill="currentColor"
|
61
|
+
>
|
62
|
+
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
63
|
+
</svg>
|
64
|
+
</a>
|
65
|
+
</div>
|
66
|
+
</nav>
|
67
|
+
);
|
68
|
+
|
69
|
+
const TerminalLogs = ({ logs }: { logs: string[] }) => (
|
70
|
+
<div className="bg-black rounded-lg p-4 font-mono text-sm text-green-400 max-h-96 overflow-auto">
|
71
|
+
{logs.map((log, i) => (
|
72
|
+
<div key={i} className="whitespace-pre-wrap">
|
73
|
+
<span className="text-blue-400">$</span> {log}
|
74
|
+
</div>
|
75
|
+
))}
|
76
|
+
</div>
|
77
|
+
);
|
78
|
+
|
79
|
+
const SuccessAnimation = ({ projectPath }: { projectPath: string }) => (
|
80
|
+
<motion.div
|
81
|
+
initial={{ opacity: 0, scale: 0.5 }}
|
82
|
+
animate={{ opacity: 1, scale: 1 }}
|
83
|
+
className="text-center p-8"
|
84
|
+
>
|
85
|
+
<motion.div
|
86
|
+
animate={{ rotate: 360 }}
|
87
|
+
transition={{ duration: 0.5 }}
|
88
|
+
className="text-6xl mb-4"
|
89
|
+
>
|
90
|
+
🎉
|
91
|
+
</motion.div>
|
92
|
+
<h2 className="text-2xl font-bold mb-4">Congratulations!</h2>
|
93
|
+
<p className="text-muted-foreground mb-6">
|
94
|
+
Your project is ready. Happy coding!
|
95
|
+
</p>
|
96
|
+
<div className="bg-secondary p-4 rounded-lg mb-6">
|
97
|
+
<p className="font-medium mb-2">Project Location:</p>
|
98
|
+
<code className="text-sm">{projectPath}</code>
|
99
|
+
</div>
|
100
|
+
<Button
|
101
|
+
onClick={() => window.open(`file://${projectPath}`, '_blank')}
|
102
|
+
className="gap-2"
|
103
|
+
>
|
104
|
+
<FolderOpen className="h-4 w-4" />
|
105
|
+
Open Project Folder
|
106
|
+
</Button>
|
107
|
+
</motion.div>
|
108
|
+
);
|
109
|
+
|
110
|
+
const ScaffoldPage = () => {
|
111
|
+
const [step, setStep] = useState(0);
|
112
|
+
const [config, setConfig] = useState<ProjectConfig>({
|
113
|
+
projectName: '',
|
114
|
+
projectPath: process.cwd(),
|
115
|
+
frontendPort: 3000,
|
116
|
+
backendPort: 3001,
|
117
|
+
frontend: null,
|
118
|
+
backend: null,
|
119
|
+
database: null,
|
120
|
+
orm: null,
|
121
|
+
auth: null,
|
122
|
+
dbUrl: '',
|
123
|
+
giturl: null,
|
124
|
+
ui : null,
|
125
|
+
});
|
126
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
127
|
+
const [logs, setLogs] = useState<string[]>([]);
|
128
|
+
const [isSuccess, setIsSuccess] = useState(false);
|
129
|
+
const [generatedPath, setGeneratedPath] = useState('');
|
130
|
+
|
131
|
+
useEffect(() => {
|
132
|
+
if (isGenerating) {
|
133
|
+
const eventSource = new EventSource('/api/scaffold/logs');
|
134
|
+
|
135
|
+
eventSource.onmessage = (event) => {
|
136
|
+
setLogs(prev => [...prev, event.data]);
|
137
|
+
};
|
138
|
+
|
139
|
+
eventSource.onerror = () => {
|
140
|
+
eventSource.close();
|
141
|
+
};
|
142
|
+
|
143
|
+
return () => eventSource.close();
|
144
|
+
}
|
145
|
+
}, [isGenerating]);
|
146
|
+
|
147
|
+
const steps = [
|
148
|
+
{
|
149
|
+
title: "Project Settings",
|
150
|
+
description: "Basic configuration",
|
151
|
+
icon: <FolderOpen className="w-5 h-5" />,
|
152
|
+
component: (
|
153
|
+
<div className="space-y-4 w-full max-w-md">
|
154
|
+
<div>
|
155
|
+
<Label htmlFor="projectName">Project Name</Label>
|
156
|
+
<Input
|
157
|
+
id="projectName"
|
158
|
+
placeholder="my-fullstack-app"
|
159
|
+
value={config.projectName}
|
160
|
+
onChange={(e) => setConfig(prev => ({ ...prev, projectName: e.target.value }))}
|
161
|
+
/>
|
162
|
+
</div>
|
163
|
+
<div>
|
164
|
+
<Label htmlFor="projectPath">Project Location</Label>
|
165
|
+
<Input
|
166
|
+
id="projectPath"
|
167
|
+
placeholder="/path/to/your/project"
|
168
|
+
value={config.projectPath}
|
169
|
+
onChange={(e) => setConfig(prev => ({ ...prev, projectPath: e.target.value }))}
|
170
|
+
/>
|
171
|
+
</div>
|
172
|
+
<div className="grid grid-cols-2 gap-4">
|
173
|
+
<div>
|
174
|
+
<Label htmlFor="frontendPort">Frontend Port</Label>
|
175
|
+
<Input
|
176
|
+
id="frontendPort"
|
177
|
+
type="number"
|
178
|
+
value={config.frontendPort}
|
179
|
+
onChange={(e) => setConfig(prev => ({ ...prev, frontendPort: parseInt(e.target.value) }))}
|
180
|
+
/>
|
181
|
+
</div>
|
182
|
+
<div>
|
183
|
+
<Label htmlFor="backendPort">Backend Port</Label>
|
184
|
+
<Input
|
185
|
+
id="backendPort"
|
186
|
+
type="number"
|
187
|
+
value={config.backendPort}
|
188
|
+
onChange={(e) => setConfig(prev => ({ ...prev, backendPort: parseInt(e.target.value) }))}
|
189
|
+
/>
|
190
|
+
</div>
|
191
|
+
</div>
|
192
|
+
</div>
|
193
|
+
)
|
194
|
+
},
|
195
|
+
{
|
196
|
+
title: "Frontend",
|
197
|
+
description: "Choose your frontend",
|
198
|
+
icon: <Layout className="w-5 h-5" />,
|
199
|
+
options: [
|
200
|
+
{ id: 'react-ts', name: 'React + TypeScript', description: 'React with TypeScript template', features: ['Vite', 'TypeScript', 'React Router', 'TailwindCSS'] },
|
201
|
+
{ id: 'react', name: 'React (JavaScript)', description: 'React with JavaScript template', features: ['Vite', 'JavaScript', 'React Router', 'TailwindCSS'] },
|
202
|
+
{ id: 'django', name: 'Django Templates', description: 'Django framework', features: ['Full-stack', 'Django ORM', 'Django Admin'] },
|
203
|
+
{ id: 'vue-ts', name: 'Vue + TypeScript', description: 'Vue 3 with TypeScript template', features: ['Vite', 'TypeScript', 'Vue Router', 'Pinia', 'TailwindCSS'] },
|
204
|
+
{ id: 'vue', name: 'Vue (JavaScript)', description: 'Vue 3 with JavaScript template', features: ['Vite', 'JavaScript', 'Vue Router', 'Pinia', 'TailwindCSS'] },
|
205
|
+
{ id: 'angularts', name: 'Angular (Typescript)', description: 'Angular 16 with Typescript template', features: ['Angular CLI', 'Typescript', 'Angular Router', 'Angular Material', 'TailwindCSS'] },
|
206
|
+
{ id: 'nextjs', name: 'Next.js', description: 'Next.js with TypeScript template', features: ['Next.js', 'TypeScript', 'TailwindCSS'] },
|
207
|
+
{ id: 'Skip', name: 'Skip', description: 'Skip frontend configuration', features: ['Skip this step'] },
|
208
|
+
]
|
209
|
+
},
|
210
|
+
{
|
211
|
+
title : "UI",
|
212
|
+
description : "Choose your UI",
|
213
|
+
icon : <Layout className="w-5 h-5" />,
|
214
|
+
options : [
|
215
|
+
{ id: 'shadcn', name: 'Shadcn', description: 'Shadcn UI', features: ['Shadcn UI', 'TailwindCSS'] },
|
216
|
+
{ id: 'tailwind', name: 'TailwindCSS', description: 'TailwindCSS', features: ['TailwindCSS', 'React'] },
|
217
|
+
{ id: 'Skip', name: 'Skip', description: 'Skip UI configuration', features: ['Skip this step'] },
|
218
|
+
]
|
219
|
+
},
|
220
|
+
{
|
221
|
+
title: "Backend",
|
222
|
+
description: "Select your backend",
|
223
|
+
icon: <Server className="w-5 h-5" />,
|
224
|
+
options: [
|
225
|
+
{ id: 'express-ts', name: 'Express + TypeScript', description: 'Express with TypeScript setup', features: ['TypeScript', 'API Routes', 'Middleware', 'CORS'] },
|
226
|
+
{ id: 'express', name: 'Express (JavaScript)', description: 'Express with JavaScript setup', features: ['JavaScript', 'API Routes', 'Middleware', 'CORS'] },
|
227
|
+
{ id: 'django', name: 'Django', description: 'Django framework', features: ['Full-stack', 'Django ORM', 'Django Admin'] },
|
228
|
+
{ id: 'Skip', name: 'Skip', description: 'Skip backend configuration', features: ['Skip this step'] }
|
229
|
+
]
|
230
|
+
},
|
231
|
+
{
|
232
|
+
title: "Database",
|
233
|
+
description: "Pick your database",
|
234
|
+
icon: <Database className="w-5 h-5" />,
|
235
|
+
options: [
|
236
|
+
{ id: 'postgresql', name: 'PostgreSQL', description: 'Powerful, open source database', features: ['Prisma ORM', 'Migrations', 'TypeScript'] },
|
237
|
+
{ id: 'mongodb', name: 'MongoDB', description: 'NoSQL document database', features: ['Mongoose', 'Schemas', 'TypeScript'] },
|
238
|
+
{ id: 'Skip', name: 'Skip', description: 'Skip database configuration', features: ['Skip this step'] }
|
239
|
+
]
|
240
|
+
},
|
241
|
+
{
|
242
|
+
title: "ORM",
|
243
|
+
description: "Select your ORM",
|
244
|
+
icon: <Database className="w-5 h-5" />,
|
245
|
+
options: config.database === 'postgresql' ? [
|
246
|
+
{ id: 'prisma', name: 'Prisma', description: 'Prisma ORM', features: ['Type-safe', 'Migrations'] },
|
247
|
+
{ id: 'drizzle', name: 'Drizzle', description: 'Drizzle ORM', features: ['Lightweight', 'Flexible'] },
|
248
|
+
{ id: 'Skip', name: 'Skip', description: 'Skip ORM configuration', features: ['Skip this step'] }
|
249
|
+
] : config.database === 'mongodb' ? [
|
250
|
+
{ id: 'mongoose', name: 'Mongoose', description: 'Mongoose ORM', features: ['Schemas', 'Validation'] },
|
251
|
+
{ id: 'Skip', name: 'Skip', description: 'Skip ORM configuration', features: ['Skip this step'] }
|
252
|
+
] : [
|
253
|
+
{ id: 'Skip', name: 'Skip', description: 'Skip ORM configuration', features: ['Skip this step'] }
|
254
|
+
]
|
255
|
+
},
|
256
|
+
{
|
257
|
+
title: "auth",
|
258
|
+
description: "Choose your authentication method",
|
259
|
+
icon: <Server className="w-5 h-5" />,
|
260
|
+
options: [
|
261
|
+
{ id: 'jwt', name: 'JWT', description: 'JSON Web Tokens', features: ['Stateless', 'Secure'] },
|
262
|
+
{ id: 'nextauth', name: 'NextAuth', description: 'NextAuth.js', features: ['Strategies', 'Middleware'] },
|
263
|
+
{ id: 'passport', name: 'Passport', description: 'Passport.js', features: ['Strategies', 'Middleware'] },
|
264
|
+
{ id: 'Skip', name: 'Skip', description: 'Skip authentication configuration', features: ['Skip this step'] }
|
265
|
+
]
|
266
|
+
},
|
267
|
+
{
|
268
|
+
title: "Database URL",
|
269
|
+
description: "Enter your database URL",
|
270
|
+
icon: <Database className="w-5 h-5" />,
|
271
|
+
component: config.database !== 'Skip' ? (
|
272
|
+
<div className="space-y-4 w-full max-w-md">
|
273
|
+
<div>
|
274
|
+
<Label htmlFor="dbUrl">Database URL</Label>
|
275
|
+
<Input
|
276
|
+
id="dbUrl"
|
277
|
+
placeholder="Enter your database URL"
|
278
|
+
value={config.dbUrl}
|
279
|
+
onChange={(e) => setConfig(prev => ({ ...prev, dbUrl: e.target.value }))}
|
280
|
+
/>
|
281
|
+
</div>
|
282
|
+
</div>
|
283
|
+
) : null
|
284
|
+
},{
|
285
|
+
title: "Git URL",
|
286
|
+
description: "Enter your git URL",
|
287
|
+
icon: <Database className="w-5 h-5" />,
|
288
|
+
component: config.giturl !== 'Skip' ? (
|
289
|
+
<div className="space-y-4 w-full max-w-md">
|
290
|
+
<div>
|
291
|
+
<Label htmlFor="giturl">Git URL</Label>
|
292
|
+
<Input
|
293
|
+
id="giturl"
|
294
|
+
placeholder="Enter your git URL"
|
295
|
+
value={config.giturl || ''}
|
296
|
+
onChange={(e) => setConfig(prev => ({ ...prev, giturl: e.target.value }))}
|
297
|
+
/>
|
298
|
+
</div>
|
299
|
+
</div>
|
300
|
+
) : null
|
301
|
+
}
|
302
|
+
];
|
303
|
+
|
304
|
+
const handleSelect = (key: keyof ProjectConfig, value: string) => {
|
305
|
+
setConfig(prev => ({ ...prev, [key]: value }));
|
306
|
+
if (step < steps.length - 1) {
|
307
|
+
setStep(step + 1);
|
308
|
+
}
|
309
|
+
};
|
310
|
+
|
311
|
+
const validateConfig = () => {
|
312
|
+
if (!config.projectName.trim()) {
|
313
|
+
toast.error('Please enter a project name');
|
314
|
+
return false;
|
315
|
+
}
|
316
|
+
if (!config.projectPath.trim()) {
|
317
|
+
toast.error('Please enter a project location');
|
318
|
+
return false;
|
319
|
+
}
|
320
|
+
if (config.frontendPort === config.backendPort) {
|
321
|
+
toast.error('Frontend and backend ports must be different');
|
322
|
+
return false;
|
323
|
+
}
|
324
|
+
return true;
|
325
|
+
};
|
326
|
+
|
327
|
+
const handleGenerate = async () => {
|
328
|
+
if (!validateConfig()) return;
|
329
|
+
|
330
|
+
try {
|
331
|
+
setIsGenerating(true);
|
332
|
+
setLogs([]);
|
333
|
+
|
334
|
+
const response = await fetch('/api/scaffold', {
|
335
|
+
method: 'POST',
|
336
|
+
headers: { 'Content-Type': 'application/json' },
|
337
|
+
body: JSON.stringify(config)
|
338
|
+
});
|
339
|
+
|
340
|
+
if (!response.ok) throw new Error('Failed to generate project');
|
341
|
+
|
342
|
+
const result = await response.json();
|
343
|
+
|
344
|
+
if (result.success) {
|
345
|
+
setGeneratedPath(result.projectPath);
|
346
|
+
setIsSuccess(true);
|
347
|
+
}
|
348
|
+
} catch (error) {
|
349
|
+
toast.error('Failed to generate project');
|
350
|
+
} finally {
|
351
|
+
setIsGenerating(false);
|
352
|
+
}
|
353
|
+
};
|
354
|
+
|
355
|
+
return (
|
356
|
+
<div className="min-h-screen bg-background">
|
357
|
+
<Navbar />
|
358
|
+
<div className="p-8">
|
359
|
+
<div className="max-w-6xl mx-auto">
|
360
|
+
<div className="mb-8">
|
361
|
+
<h1 className="text-4xl font-bold mb-2">Create Your Project</h1>
|
362
|
+
<p className="text-muted-foreground">
|
363
|
+
Configure your full-stack application
|
364
|
+
</p>
|
365
|
+
</div>
|
366
|
+
|
367
|
+
<Steps
|
368
|
+
steps={steps.map(s => ({
|
369
|
+
title: s.title,
|
370
|
+
description: s.description,
|
371
|
+
icon: s.icon
|
372
|
+
}))}
|
373
|
+
currentStep={step}
|
374
|
+
onStepClick={setStep}
|
375
|
+
/>
|
376
|
+
|
377
|
+
<div className="mt-8">
|
378
|
+
{steps[step]?.component ? (
|
379
|
+
<div className="flex justify-center">
|
380
|
+
{steps[step].component}
|
381
|
+
</div>
|
382
|
+
) : (
|
383
|
+
<div className="relative">
|
384
|
+
<div className="grid md:grid-cols-3 gap-4 card-grid">
|
385
|
+
{steps[step]?.options?.filter(option => option.id !== 'Skip').map((option) => (
|
386
|
+
<Card
|
387
|
+
key={option.id}
|
388
|
+
className={`card p-4 cursor-pointer interactive-element ${
|
389
|
+
config[steps[step]?.title.toLowerCase() as keyof ProjectConfig] === option.id
|
390
|
+
? 'border-primary/50 bg-primary/5 card-selected'
|
391
|
+
: ''
|
392
|
+
}`}
|
393
|
+
onClick={() => handleSelect(steps[step]?.title.toLowerCase() as keyof ProjectConfig, option.id)}
|
394
|
+
>
|
395
|
+
<div className="relative z-10">
|
396
|
+
<h3 className="font-medium mb-2 text-lg">{option.name}</h3>
|
397
|
+
<p className="text-sm text-muted-foreground mb-4">
|
398
|
+
{option.description}
|
399
|
+
</p>
|
400
|
+
<div className="space-y-1">
|
401
|
+
{option.features.map((feature, index) => (
|
402
|
+
<div key={index} className="text-xs text-muted-foreground flex items-center gap-1">
|
403
|
+
<span className="text-primary">•</span> {feature}
|
404
|
+
</div>
|
405
|
+
))}
|
406
|
+
</div>
|
407
|
+
</div>
|
408
|
+
</Card>
|
409
|
+
))}
|
410
|
+
</div>
|
411
|
+
</div>
|
412
|
+
)}
|
413
|
+
|
414
|
+
<div className="mt-8 flex justify-between">
|
415
|
+
<Button
|
416
|
+
variant="outline"
|
417
|
+
onClick={() => setStep(Math.max(0, step - 1))}
|
418
|
+
disabled={step === 0}
|
419
|
+
className="px-6"
|
420
|
+
>
|
421
|
+
Previous
|
422
|
+
</Button>
|
423
|
+
|
424
|
+
{step === steps.length - 1 ? (
|
425
|
+
<Button onClick={handleGenerate} className="px-6">
|
426
|
+
Generate Project
|
427
|
+
</Button>
|
428
|
+
) : (
|
429
|
+
<div className="flex gap-2">
|
430
|
+
{step > 0 && !steps[step].component && (
|
431
|
+
<SkipButton
|
432
|
+
onSkip={() => handleSelect(
|
433
|
+
steps[step]?.title.toLowerCase() as keyof ProjectConfig,
|
434
|
+
'Skip'
|
435
|
+
)}
|
436
|
+
/>
|
437
|
+
)}
|
438
|
+
<Button
|
439
|
+
onClick={() => setStep(Math.min(steps.length - 1, step + 1))}
|
440
|
+
disabled={
|
441
|
+
step === 0
|
442
|
+
? !config.projectName || !config.projectPath
|
443
|
+
: step === steps.length - 2 && config.database !== 'Skip' // Database URL step
|
444
|
+
? !config.dbUrl
|
445
|
+
: step === steps.length - 1 && config.giturl !== 'Skip' // Git URL step
|
446
|
+
? !config.giturl
|
447
|
+
: !config[steps[step]?.title.toLowerCase() as keyof ProjectConfig]
|
448
|
+
}
|
449
|
+
className="px-6"
|
450
|
+
>
|
451
|
+
Next
|
452
|
+
</Button>
|
453
|
+
</div>
|
454
|
+
)}
|
455
|
+
</div>
|
456
|
+
</div>
|
457
|
+
|
458
|
+
{isSuccess ? (
|
459
|
+
<SuccessAnimation projectPath={generatedPath} />
|
460
|
+
) : isGenerating ? (
|
461
|
+
<div className="space-y-4">
|
462
|
+
<h2 className="text-xl font-semibold">Generating Your Project...</h2>
|
463
|
+
<TerminalLogs logs={logs} />
|
464
|
+
</div>
|
465
|
+
) : null}
|
466
|
+
</div>
|
467
|
+
</div>
|
468
|
+
</div>
|
469
|
+
);
|
470
|
+
};
|
471
|
+
|
472
|
+
export default ScaffoldPage;
|
@@ -0,0 +1,108 @@
|
|
1
|
+
'use client'
|
2
|
+
import React, { useState } from 'react';
|
3
|
+
import { motion } from 'framer-motion';
|
4
|
+
import {
|
5
|
+
Home,
|
6
|
+
GitFork,
|
7
|
+
Github,
|
8
|
+
ChevronFirst,
|
9
|
+
ChevronLast,
|
10
|
+
FolderGit2,
|
11
|
+
Menu
|
12
|
+
} from 'lucide-react';
|
13
|
+
import { useRouter } from 'next/navigation';
|
14
|
+
|
15
|
+
export const Sidebar = () => {
|
16
|
+
const [isOpen, setIsOpen] = useState(true);
|
17
|
+
const router = useRouter();
|
18
|
+
const toggleSidebar = () => {
|
19
|
+
setIsOpen(!isOpen);
|
20
|
+
};
|
21
|
+
|
22
|
+
const menuItems = [
|
23
|
+
{ icon: Home, label: "Home", href: "/home" },
|
24
|
+
{ icon: FolderGit2, label: "Local Repositories", href: "/local-repos" },
|
25
|
+
{ icon: GitFork, label: "Remote Repositories", href: "/commit" },
|
26
|
+
{ icon: Github, label: "GitHub Summary", href: "/user" },
|
27
|
+
];
|
28
|
+
|
29
|
+
return (
|
30
|
+
<motion.div
|
31
|
+
animate={{ width: isOpen ? 280 : 72 }}
|
32
|
+
transition={{ duration: 0.3 }}
|
33
|
+
className="relative text-neutral-400 h-screen bg-[#1e1e1e] border-r-4 border-cyan-800"
|
34
|
+
>
|
35
|
+
<div className="flex items-center justify-between p-4 border-b border-neutral-800">
|
36
|
+
{isOpen && (
|
37
|
+
<motion.span
|
38
|
+
initial={{ opacity: 0 }}
|
39
|
+
animate={{ opacity: 1 }}
|
40
|
+
transition={{ delay: 0.1 }}
|
41
|
+
className="text-lg font-semibold text-neutral-200"
|
42
|
+
>
|
43
|
+
Dashboard
|
44
|
+
</motion.span>
|
45
|
+
)}
|
46
|
+
<button
|
47
|
+
onClick={toggleSidebar}
|
48
|
+
className="absolute right-2 p-2 hover:bg-cyan-800 rounded-lg transition-colors"
|
49
|
+
>
|
50
|
+
{isOpen ? (
|
51
|
+
<ChevronFirst className="h-5 w-5" />
|
52
|
+
) : (
|
53
|
+
<ChevronLast className="h-5 w-5" />
|
54
|
+
)}
|
55
|
+
</button>
|
56
|
+
</div>
|
57
|
+
|
58
|
+
<div className="space-y-1 py-4">
|
59
|
+
{menuItems.map((item, index) => (
|
60
|
+
<motion.div
|
61
|
+
key={index}
|
62
|
+
whileHover={{ x: 4 }}
|
63
|
+
className={`flex items-center cursor-pointer
|
64
|
+
${isOpen ? 'px-4' : 'justify-center px-2'}
|
65
|
+
py-3 mx-2 rounded-lg hover:bg-neutral-800 hover:text-cyan-500
|
66
|
+
transition-colors group relative`}
|
67
|
+
onClick={() => router.push(item.href)}
|
68
|
+
>
|
69
|
+
<item.icon className="h-5 w-5 min-w-[20px]" />
|
70
|
+
{isOpen && (
|
71
|
+
<motion.span
|
72
|
+
initial={{ opacity: 0 }}
|
73
|
+
animate={{ opacity: 1 }}
|
74
|
+
transition={{ delay: 0.1 }}
|
75
|
+
className="ml-4 text-sm"
|
76
|
+
>
|
77
|
+
{item.label}
|
78
|
+
</motion.span>
|
79
|
+
)}
|
80
|
+
{!isOpen && (
|
81
|
+
<div className="absolute left-full rounded-md px-2 py-1 ml-6 bg-neutral-900 text-neutral-200 text-sm
|
82
|
+
invisible opacity-0 -translate-x-3 transition-all group-hover:visible group-hover:opacity-100 group-hover:translate-x-0">
|
83
|
+
{item.label}
|
84
|
+
</div>
|
85
|
+
)}
|
86
|
+
</motion.div>
|
87
|
+
))}
|
88
|
+
</div>
|
89
|
+
|
90
|
+
|
91
|
+
<div className="absolute bottom-0 w-full border-t border-neutral-800 p-4">
|
92
|
+
<div className={`flex items-center ${isOpen ? '' : 'justify-center'}`}>
|
93
|
+
<Menu className="h-5 w-5 min-w-[20px]" />
|
94
|
+
{isOpen && (
|
95
|
+
<motion.span
|
96
|
+
initial={{ opacity: 0 }}
|
97
|
+
animate={{ opacity: 1 }}
|
98
|
+
transition={{ delay: 0.1 }}
|
99
|
+
className="ml-4 text-sm"
|
100
|
+
>
|
101
|
+
Toggle Menu
|
102
|
+
</motion.span>
|
103
|
+
)}
|
104
|
+
</div>
|
105
|
+
</div>
|
106
|
+
</motion.div>
|
107
|
+
);
|
108
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"use client"
|
2
|
+
|
3
|
+
import * as React from "react"
|
4
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
5
|
+
import { type ThemeProviderProps } from "next-themes/dist/types"
|
6
|
+
|
7
|
+
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
8
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
9
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import * as React from "react"
|
2
|
+
import { Slot } from "@radix-ui/react-slot"
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
4
|
+
|
5
|
+
import { cn } from "@/lib/utils"
|
6
|
+
|
7
|
+
const buttonVariants = cva(
|
8
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
9
|
+
{
|
10
|
+
variants: {
|
11
|
+
variant: {
|
12
|
+
default:
|
13
|
+
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
14
|
+
destructive:
|
15
|
+
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
16
|
+
outline:
|
17
|
+
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
18
|
+
secondary:
|
19
|
+
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
20
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
22
|
+
},
|
23
|
+
size: {
|
24
|
+
default: "h-9 px-4 py-2",
|
25
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
26
|
+
lg: "h-10 rounded-md px-8",
|
27
|
+
icon: "h-9 w-9",
|
28
|
+
},
|
29
|
+
},
|
30
|
+
defaultVariants: {
|
31
|
+
variant: "default",
|
32
|
+
size: "default",
|
33
|
+
},
|
34
|
+
}
|
35
|
+
)
|
36
|
+
|
37
|
+
export interface ButtonProps
|
38
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
39
|
+
VariantProps<typeof buttonVariants> {
|
40
|
+
asChild?: boolean
|
41
|
+
}
|
42
|
+
|
43
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
44
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
45
|
+
const Comp = asChild ? Slot : "button"
|
46
|
+
return (
|
47
|
+
<Comp
|
48
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
49
|
+
ref={ref}
|
50
|
+
{...props}
|
51
|
+
/>
|
52
|
+
)
|
53
|
+
}
|
54
|
+
)
|
55
|
+
Button.displayName = "Button"
|
56
|
+
|
57
|
+
export { Button, buttonVariants }
|