claudeship 0.2.23 → 0.2.25
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/apps/server/dist/chat/chat.module.js +8 -1
- package/apps/server/dist/chat/chat.module.js.map +1 -1
- package/apps/server/dist/chat/chat.service.d.ts +5 -1
- package/apps/server/dist/chat/chat.service.js +35 -4
- package/apps/server/dist/chat/chat.service.js.map +1 -1
- package/apps/server/dist/chat/framework-detector.service.d.ts +17 -0
- package/apps/server/dist/chat/framework-detector.service.js +193 -0
- package/apps/server/dist/chat/framework-detector.service.js.map +1 -0
- package/apps/server/dist/chat/prompts/backend/django.d.ts +1 -0
- package/apps/server/dist/chat/prompts/backend/django.js +207 -0
- package/apps/server/dist/chat/prompts/backend/django.js.map +1 -0
- package/apps/server/dist/chat/prompts/backend/express.d.ts +1 -0
- package/apps/server/dist/chat/prompts/backend/express.js +260 -0
- package/apps/server/dist/chat/prompts/backend/express.js.map +1 -0
- package/apps/server/dist/chat/prompts/backend/fastapi.d.ts +1 -0
- package/apps/server/dist/chat/prompts/backend/fastapi.js +246 -0
- package/apps/server/dist/chat/prompts/backend/fastapi.js.map +1 -0
- package/apps/server/dist/chat/prompts/backend/index.d.ts +4 -0
- package/apps/server/dist/chat/prompts/backend/index.js +12 -0
- package/apps/server/dist/chat/prompts/backend/index.js.map +1 -0
- package/apps/server/dist/chat/prompts/backend/nestjs.d.ts +1 -0
- package/apps/server/dist/chat/prompts/backend/nestjs.js +270 -0
- package/apps/server/dist/chat/prompts/backend/nestjs.js.map +1 -0
- package/apps/server/dist/chat/prompts/frontend/expo.d.ts +1 -0
- package/apps/server/dist/chat/prompts/frontend/expo.js +208 -0
- package/apps/server/dist/chat/prompts/frontend/expo.js.map +1 -0
- package/apps/server/dist/chat/prompts/frontend/flutter.d.ts +1 -0
- package/apps/server/dist/chat/prompts/frontend/flutter.js +271 -0
- package/apps/server/dist/chat/prompts/frontend/flutter.js.map +1 -0
- package/apps/server/dist/chat/prompts/frontend/index.d.ts +4 -0
- package/apps/server/dist/chat/prompts/frontend/index.js +12 -0
- package/apps/server/dist/chat/prompts/frontend/index.js.map +1 -0
- package/apps/server/dist/chat/prompts/frontend/nextjs.d.ts +1 -0
- package/apps/server/dist/chat/prompts/frontend/nextjs.js +195 -0
- package/apps/server/dist/chat/prompts/frontend/nextjs.js.map +1 -0
- package/apps/server/dist/chat/prompts/frontend/react-native.d.ts +1 -0
- package/apps/server/dist/chat/prompts/frontend/react-native.js +224 -0
- package/apps/server/dist/chat/prompts/frontend/react-native.js.map +1 -0
- package/apps/server/dist/chat/prompts/frontend/react-vite.d.ts +1 -0
- package/apps/server/dist/chat/prompts/frontend/react-vite.js +187 -0
- package/apps/server/dist/chat/prompts/frontend/react-vite.js.map +1 -0
- package/apps/server/dist/chat/prompts/frontend/svelte.d.ts +1 -0
- package/apps/server/dist/chat/prompts/frontend/svelte.js +255 -0
- package/apps/server/dist/chat/prompts/frontend/svelte.js.map +1 -0
- package/apps/server/dist/chat/prompts/frontend/vue.d.ts +1 -0
- package/apps/server/dist/chat/prompts/frontend/vue.js +267 -0
- package/apps/server/dist/chat/prompts/frontend/vue.js.map +1 -0
- package/apps/server/dist/chat/prompts/index.d.ts +4 -0
- package/apps/server/dist/chat/prompts/index.js +20 -1
- package/apps/server/dist/chat/prompts/index.js.map +1 -1
- package/apps/server/dist/chat/prompts/prompt-builder.service.d.ts +15 -0
- package/apps/server/dist/chat/prompts/prompt-builder.service.js +177 -0
- package/apps/server/dist/chat/prompts/prompt-builder.service.js.map +1 -0
- package/apps/server/dist/chat/prompts/sections/core.d.ts +9 -0
- package/apps/server/dist/chat/prompts/sections/core.js +149 -0
- package/apps/server/dist/chat/prompts/sections/core.js.map +1 -0
- package/apps/server/dist/project/project.controller.d.ts +6 -0
- package/apps/server/dist/project/project.service.d.ts +6 -0
- package/apps/server/dist/tsconfig.tsbuildinfo +1 -1
- package/apps/server/package.json +1 -1
- package/apps/server/prisma/dev.db +0 -0
- package/apps/server/prisma/migrations/20260127071040_add_frontend_framework/migration.sql +24 -0
- package/apps/server/prisma/migrations/20260127071520_add_app_type_and_mobile/migration.sql +25 -0
- package/apps/server/prisma/schema.prisma +33 -5
- package/apps/web/.next/BUILD_ID +1 -1
- package/apps/web/.next/app-build-manifest.json +10 -8
- package/apps/web/.next/app-path-routes-manifest.json +2 -2
- package/apps/web/.next/build-manifest.json +2 -2
- package/apps/web/.next/cache/.previewinfo +1 -1
- package/apps/web/.next/cache/.rscinfo +1 -1
- package/apps/web/.next/cache/.tsbuildinfo +1 -1
- package/apps/web/.next/cache/config.json +3 -3
- package/apps/web/.next/cache/eslint/.cache_j3uhuz +1 -1
- package/apps/web/.next/cache/webpack/client-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/index.pack +0 -0
- package/apps/web/.next/prerender-manifest.json +3 -3
- package/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/_not-found.html +1 -1
- package/apps/web/.next/server/app/_not-found.rsc +2 -2
- package/apps/web/.next/server/app/index.html +1 -1
- package/apps/web/.next/server/app/index.rsc +3 -3
- package/apps/web/.next/server/app/page.js +2 -2
- package/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/project/[id]/page.js +2 -2
- package/apps/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/settings/page.js +1 -1
- package/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/settings.html +1 -1
- package/apps/web/.next/server/app/settings.rsc +2 -2
- package/apps/web/.next/server/app-paths-manifest.json +2 -2
- package/apps/web/.next/server/pages/404.html +1 -1
- package/apps/web/.next/server/pages/500.html +1 -1
- package/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/apps/web/.next/static/chunks/18-22b1cf4231121555.js +1 -0
- package/apps/web/.next/static/chunks/700-75e1212e819e279c.js +1 -0
- package/apps/web/.next/static/chunks/app/page-6ea560755549086e.js +1 -0
- package/apps/web/.next/static/chunks/app/project/[id]/page-3e4777b355c4aec9.js +1 -0
- package/apps/web/.next/static/css/45ddb08a7b4470d5.css +3 -0
- package/apps/web/.next/trace +18 -18
- package/apps/web/package.json +1 -1
- package/apps/web/src/app/page.tsx +2 -5
- package/apps/web/src/components/project/CreateProjectModal.tsx +175 -80
- package/apps/web/src/components/project/ProjectCard.tsx +122 -31
- package/apps/web/src/stores/useProjectStore.ts +2 -1
- package/package.json +1 -1
- package/packages/shared/src/index.ts +1 -0
- package/packages/shared/src/types/project.ts +18 -3
- package/packages/shared/src/types/tech-stack.ts +74 -0
- package/apps/web/.next/static/chunks/700-a927807facd2c50d.js +0 -1
- package/apps/web/.next/static/chunks/app/page-93b78578e7896d90.js +0 -1
- package/apps/web/.next/static/chunks/app/project/[id]/page-e9304c25ba897608.js +0 -1
- package/apps/web/.next/static/css/70f2a13cf3d254d8.css +0 -3
- /package/apps/web/.next/static/{c-H9phuqk4ohtFNK0geg2 → IMWKpuHss3gLOeJ7K93sg}/_buildManifest.js +0 -0
- /package/apps/web/.next/static/{c-H9phuqk4ohtFNK0geg2 → IMWKpuHss3gLOeJ7K93sg}/_ssgManifest.js +0 -0
package/apps/web/package.json
CHANGED
|
@@ -7,7 +7,7 @@ import { ProjectList } from "@/components/project/ProjectList";
|
|
|
7
7
|
import { CreateProjectModal } from "@/components/project/CreateProjectModal";
|
|
8
8
|
import { useProjectStore } from "@/stores/useProjectStore";
|
|
9
9
|
import { useTranslation } from "@/lib/i18n";
|
|
10
|
-
import type {
|
|
10
|
+
import type { CreateProjectInput } from "@claudeship/shared";
|
|
11
11
|
|
|
12
12
|
export default function Home() {
|
|
13
13
|
const router = useRouter();
|
|
@@ -20,10 +20,7 @@ export default function Home() {
|
|
|
20
20
|
fetchProjects();
|
|
21
21
|
}, [fetchProjects]);
|
|
22
22
|
|
|
23
|
-
const handleCreateProject = async (data: {
|
|
24
|
-
name: string;
|
|
25
|
-
projectType: ProjectType;
|
|
26
|
-
}) => {
|
|
23
|
+
const handleCreateProject = async (data: CreateProjectInput) => {
|
|
27
24
|
try {
|
|
28
25
|
const project = await createProject(data);
|
|
29
26
|
setIsModalOpen(false);
|
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
} from "@/components/ui/dialog";
|
|
12
12
|
import { Button } from "@/components/ui/button";
|
|
13
13
|
import { Input } from "@/components/ui/input";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
AppType,
|
|
16
|
+
FrontendFramework,
|
|
17
|
+
BackendFramework,
|
|
18
|
+
} from "@claudeship/shared";
|
|
15
19
|
import { useTranslation } from "@/lib/i18n";
|
|
16
20
|
|
|
17
21
|
interface CreateProjectModalProps {
|
|
@@ -19,12 +23,80 @@ interface CreateProjectModalProps {
|
|
|
19
23
|
onOpenChange: (open: boolean) => void;
|
|
20
24
|
onSubmit: (data: {
|
|
21
25
|
name: string;
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
appType: AppType;
|
|
27
|
+
frontendFramework?: FrontendFramework;
|
|
28
|
+
backendFramework?: BackendFramework;
|
|
24
29
|
}) => void;
|
|
25
30
|
isLoading?: boolean;
|
|
26
31
|
}
|
|
27
32
|
|
|
33
|
+
// App type configurations
|
|
34
|
+
const APP_TYPE_CONFIG = {
|
|
35
|
+
[AppType.FULLSTACK_WEB]: {
|
|
36
|
+
icon: "🌐",
|
|
37
|
+
label: "풀스택 웹앱",
|
|
38
|
+
description: "프론트엔드 + 백엔드",
|
|
39
|
+
showFrontend: true,
|
|
40
|
+
showBackend: true,
|
|
41
|
+
webOnly: true,
|
|
42
|
+
},
|
|
43
|
+
[AppType.FRONTEND_ONLY]: {
|
|
44
|
+
icon: "⚡",
|
|
45
|
+
label: "프론트엔드",
|
|
46
|
+
description: "정적 사이트 / SPA",
|
|
47
|
+
showFrontend: true,
|
|
48
|
+
showBackend: false,
|
|
49
|
+
webOnly: true,
|
|
50
|
+
},
|
|
51
|
+
[AppType.API_ONLY]: {
|
|
52
|
+
icon: "🔌",
|
|
53
|
+
label: "API 서버",
|
|
54
|
+
description: "백엔드만",
|
|
55
|
+
showFrontend: false,
|
|
56
|
+
showBackend: true,
|
|
57
|
+
webOnly: false,
|
|
58
|
+
},
|
|
59
|
+
[AppType.MOBILE]: {
|
|
60
|
+
icon: "📱",
|
|
61
|
+
label: "모바일 앱",
|
|
62
|
+
description: "React Native / Flutter",
|
|
63
|
+
showFrontend: true,
|
|
64
|
+
showBackend: false,
|
|
65
|
+
webOnly: false,
|
|
66
|
+
},
|
|
67
|
+
[AppType.MOBILE_WITH_API]: {
|
|
68
|
+
icon: "📱🔌",
|
|
69
|
+
label: "모바일 + API",
|
|
70
|
+
description: "모바일 앱 + 백엔드",
|
|
71
|
+
showFrontend: true,
|
|
72
|
+
showBackend: true,
|
|
73
|
+
webOnly: false,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Web frontend frameworks
|
|
78
|
+
const WEB_FRONTENDS = [
|
|
79
|
+
{ value: FrontendFramework.REACT_VITE, icon: "⚛️", label: "React + Vite" },
|
|
80
|
+
{ value: FrontendFramework.NEXTJS, icon: "▲", label: "Next.js" },
|
|
81
|
+
{ value: FrontendFramework.VUE, icon: "💚", label: "Vue 3" },
|
|
82
|
+
{ value: FrontendFramework.SVELTE, icon: "🔶", label: "SvelteKit" },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// Mobile frontend frameworks
|
|
86
|
+
const MOBILE_FRONTENDS = [
|
|
87
|
+
{ value: FrontendFramework.EXPO, icon: "📱", label: "Expo" },
|
|
88
|
+
{ value: FrontendFramework.REACT_NATIVE, icon: "⚛️", label: "React Native" },
|
|
89
|
+
{ value: FrontendFramework.FLUTTER, icon: "🐦", label: "Flutter" },
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
// Backend frameworks
|
|
93
|
+
const BACKENDS = [
|
|
94
|
+
{ value: BackendFramework.EXPRESS, icon: "🟢", label: "Express" },
|
|
95
|
+
{ value: BackendFramework.FASTAPI, icon: "🐍", label: "FastAPI" },
|
|
96
|
+
{ value: BackendFramework.DJANGO, icon: "🎸", label: "Django" },
|
|
97
|
+
{ value: BackendFramework.NESTJS, icon: "🔴", label: "NestJS" },
|
|
98
|
+
];
|
|
99
|
+
|
|
28
100
|
export function CreateProjectModal({
|
|
29
101
|
open,
|
|
30
102
|
onOpenChange,
|
|
@@ -33,32 +105,66 @@ export function CreateProjectModal({
|
|
|
33
105
|
}: CreateProjectModalProps) {
|
|
34
106
|
const { t } = useTranslation();
|
|
35
107
|
const [name, setName] = useState("");
|
|
36
|
-
const [
|
|
108
|
+
const [appType, setAppType] = useState<AppType>(AppType.FULLSTACK_WEB);
|
|
109
|
+
const [frontendFramework, setFrontendFramework] = useState<FrontendFramework>(
|
|
110
|
+
FrontendFramework.REACT_VITE
|
|
111
|
+
);
|
|
37
112
|
const [backendFramework, setBackendFramework] = useState<BackendFramework>(
|
|
38
|
-
BackendFramework.
|
|
113
|
+
BackendFramework.EXPRESS
|
|
39
114
|
);
|
|
40
115
|
|
|
116
|
+
const config = APP_TYPE_CONFIG[appType];
|
|
117
|
+
const isMobileApp =
|
|
118
|
+
appType === AppType.MOBILE || appType === AppType.MOBILE_WITH_API;
|
|
119
|
+
|
|
120
|
+
const handleAppTypeChange = (newAppType: AppType) => {
|
|
121
|
+
setAppType(newAppType);
|
|
122
|
+
// Reset frameworks based on app type
|
|
123
|
+
const newConfig = APP_TYPE_CONFIG[newAppType];
|
|
124
|
+
const isMobile =
|
|
125
|
+
newAppType === AppType.MOBILE || newAppType === AppType.MOBILE_WITH_API;
|
|
126
|
+
|
|
127
|
+
if (newConfig.showFrontend) {
|
|
128
|
+
setFrontendFramework(
|
|
129
|
+
isMobile ? FrontendFramework.EXPO : FrontendFramework.REACT_VITE
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (newConfig.showBackend) {
|
|
133
|
+
setBackendFramework(BackendFramework.EXPRESS);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
41
137
|
const handleSubmit = (e: React.FormEvent) => {
|
|
42
138
|
e.preventDefault();
|
|
43
139
|
if (!name.trim()) return;
|
|
44
|
-
|
|
140
|
+
|
|
141
|
+
onSubmit({
|
|
142
|
+
name: name.trim(),
|
|
143
|
+
appType,
|
|
144
|
+
frontendFramework: config.showFrontend ? frontendFramework : undefined,
|
|
145
|
+
backendFramework: config.showBackend ? backendFramework : undefined,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Reset form
|
|
45
149
|
setName("");
|
|
46
|
-
|
|
47
|
-
|
|
150
|
+
setAppType(AppType.FULLSTACK_WEB);
|
|
151
|
+
setFrontendFramework(FrontendFramework.REACT_VITE);
|
|
152
|
+
setBackendFramework(BackendFramework.EXPRESS);
|
|
48
153
|
};
|
|
49
154
|
|
|
50
155
|
return (
|
|
51
156
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
52
|
-
<DialogContent className="max-w-lg">
|
|
157
|
+
<DialogContent className="max-w-lg max-h-[90vh] overflow-y-auto">
|
|
53
158
|
<form onSubmit={handleSubmit}>
|
|
54
159
|
<DialogHeader>
|
|
55
160
|
<DialogTitle>{t("project.createTitle")}</DialogTitle>
|
|
56
161
|
<DialogDescription>
|
|
57
|
-
|
|
162
|
+
새 프로젝트의 이름과 타입을 선택하세요
|
|
58
163
|
</DialogDescription>
|
|
59
164
|
</DialogHeader>
|
|
60
165
|
|
|
61
166
|
<div className="grid gap-4 py-4">
|
|
167
|
+
{/* Project Name */}
|
|
62
168
|
<div className="grid gap-2">
|
|
63
169
|
<label htmlFor="name" className="text-sm font-medium">
|
|
64
170
|
{t("project.name")}
|
|
@@ -72,83 +178,72 @@ export function CreateProjectModal({
|
|
|
72
178
|
/>
|
|
73
179
|
</div>
|
|
74
180
|
|
|
181
|
+
{/* App Type Selection */}
|
|
75
182
|
<div className="grid gap-2">
|
|
76
|
-
<label className="text-sm font-medium"
|
|
183
|
+
<label className="text-sm font-medium">앱 타입</label>
|
|
77
184
|
<div className="grid grid-cols-2 gap-2">
|
|
78
|
-
|
|
79
|
-
type="button"
|
|
80
|
-
variant={projectType === ProjectType.WEB ? "default" : "outline"}
|
|
81
|
-
className="h-16 flex-col gap-1"
|
|
82
|
-
onClick={() => setProjectType(ProjectType.WEB)}
|
|
83
|
-
>
|
|
84
|
-
<span className="text-xl">🌐</span>
|
|
85
|
-
<span className="text-xs">{t("project.typeWeb")}</span>
|
|
86
|
-
</Button>
|
|
87
|
-
<Button
|
|
88
|
-
type="button"
|
|
89
|
-
variant={projectType === ProjectType.NATIVE ? "default" : "outline"}
|
|
90
|
-
className="h-16 flex-col gap-1"
|
|
91
|
-
onClick={() => setProjectType(ProjectType.NATIVE)}
|
|
92
|
-
>
|
|
93
|
-
<span className="text-xl">📱</span>
|
|
94
|
-
<span className="text-xs">{t("project.typeNative")}</span>
|
|
95
|
-
</Button>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
{projectType === ProjectType.WEB && (
|
|
100
|
-
<div className="grid gap-2">
|
|
101
|
-
<label className="text-sm font-medium">{t("project.backendStack")}</label>
|
|
102
|
-
<div className="grid grid-cols-3 gap-2">
|
|
185
|
+
{Object.entries(APP_TYPE_CONFIG).map(([type, cfg]) => (
|
|
103
186
|
<Button
|
|
187
|
+
key={type}
|
|
104
188
|
type="button"
|
|
105
|
-
variant={
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
: "outline"
|
|
109
|
-
}
|
|
110
|
-
className="h-20 flex-col gap-1"
|
|
111
|
-
onClick={() => setBackendFramework(BackendFramework.NONE)}
|
|
189
|
+
variant={appType === type ? "default" : "outline"}
|
|
190
|
+
className="h-16 flex-col gap-0.5"
|
|
191
|
+
onClick={() => handleAppTypeChange(type as AppType)}
|
|
112
192
|
>
|
|
113
|
-
<span className="text-lg"
|
|
114
|
-
<span className="text-xs
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
</Button>
|
|
118
|
-
<Button
|
|
119
|
-
type="button"
|
|
120
|
-
variant={
|
|
121
|
-
backendFramework === BackendFramework.EXPRESS
|
|
122
|
-
? "default"
|
|
123
|
-
: "outline"
|
|
124
|
-
}
|
|
125
|
-
className="h-20 flex-col gap-1"
|
|
126
|
-
onClick={() => setBackendFramework(BackendFramework.EXPRESS)}
|
|
127
|
-
>
|
|
128
|
-
<span className="text-lg">🟢</span>
|
|
129
|
-
<span className="text-xs text-center leading-tight">
|
|
130
|
-
Express
|
|
131
|
-
<br />
|
|
132
|
-
(Node.js)
|
|
133
|
-
</span>
|
|
134
|
-
</Button>
|
|
135
|
-
<Button
|
|
136
|
-
type="button"
|
|
137
|
-
variant={
|
|
138
|
-
backendFramework === BackendFramework.FASTAPI
|
|
139
|
-
? "default"
|
|
140
|
-
: "outline"
|
|
141
|
-
}
|
|
142
|
-
className="h-20 flex-col gap-1"
|
|
143
|
-
onClick={() => setBackendFramework(BackendFramework.FASTAPI)}
|
|
144
|
-
>
|
|
145
|
-
<span className="text-lg">🐍</span>
|
|
146
|
-
<span className="text-xs text-center leading-tight">
|
|
147
|
-
FastAPI
|
|
148
|
-
<br />
|
|
149
|
-
(Python)
|
|
193
|
+
<span className="text-lg">{cfg.icon}</span>
|
|
194
|
+
<span className="text-xs font-medium">{cfg.label}</span>
|
|
195
|
+
<span className="text-[10px] text-muted-foreground">
|
|
196
|
+
{cfg.description}
|
|
150
197
|
</span>
|
|
151
198
|
</Button>
|
|
199
|
+
))}
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
{/* Frontend Framework Selection */}
|
|
204
|
+
{config.showFrontend && (
|
|
205
|
+
<div className="grid gap-2">
|
|
206
|
+
<label className="text-sm font-medium">
|
|
207
|
+
{isMobileApp ? "모바일 프레임워크" : "프론트엔드"}
|
|
208
|
+
</label>
|
|
209
|
+
<div className="grid grid-cols-4 gap-2">
|
|
210
|
+
{(isMobileApp ? MOBILE_FRONTENDS : WEB_FRONTENDS).map((fw) => (
|
|
211
|
+
<Button
|
|
212
|
+
key={fw.value}
|
|
213
|
+
type="button"
|
|
214
|
+
variant={
|
|
215
|
+
frontendFramework === fw.value ? "default" : "outline"
|
|
216
|
+
}
|
|
217
|
+
className="h-14 flex-col gap-0.5"
|
|
218
|
+
onClick={() => setFrontendFramework(fw.value)}
|
|
219
|
+
>
|
|
220
|
+
<span className="text-base">{fw.icon}</span>
|
|
221
|
+
<span className="text-[10px]">{fw.label}</span>
|
|
222
|
+
</Button>
|
|
223
|
+
))}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
|
|
228
|
+
{/* Backend Framework Selection */}
|
|
229
|
+
{config.showBackend && (
|
|
230
|
+
<div className="grid gap-2">
|
|
231
|
+
<label className="text-sm font-medium">백엔드</label>
|
|
232
|
+
<div className="grid grid-cols-4 gap-2">
|
|
233
|
+
{BACKENDS.map((fw) => (
|
|
234
|
+
<Button
|
|
235
|
+
key={fw.value}
|
|
236
|
+
type="button"
|
|
237
|
+
variant={
|
|
238
|
+
backendFramework === fw.value ? "default" : "outline"
|
|
239
|
+
}
|
|
240
|
+
className="h-14 flex-col gap-0.5"
|
|
241
|
+
onClick={() => setBackendFramework(fw.value)}
|
|
242
|
+
>
|
|
243
|
+
<span className="text-base">{fw.icon}</span>
|
|
244
|
+
<span className="text-[10px]">{fw.label}</span>
|
|
245
|
+
</Button>
|
|
246
|
+
))}
|
|
152
247
|
</div>
|
|
153
248
|
</div>
|
|
154
249
|
)}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import Link from "next/link";
|
|
4
|
-
import { Trash2, Globe, Smartphone, Server, Database } from "lucide-react";
|
|
4
|
+
import { Trash2, Globe, Smartphone, Server, Database, Zap, Code } from "lucide-react";
|
|
5
5
|
import { Card } from "@/components/ui/card";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import type { ProjectListItem } from "@claudeship/shared";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
AppType,
|
|
10
|
+
BackendFramework,
|
|
11
|
+
FrontendFramework,
|
|
12
|
+
DatabaseProvider,
|
|
13
|
+
} from "@claudeship/shared";
|
|
9
14
|
import { useTranslation } from "@/lib/i18n";
|
|
10
15
|
|
|
11
16
|
interface ProjectCardProps {
|
|
@@ -35,43 +40,111 @@ function formatRelativeTime(date: Date, locale: string): string {
|
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
function
|
|
39
|
-
switch (
|
|
40
|
-
case
|
|
43
|
+
function getAppTypeIcon(appType: AppType) {
|
|
44
|
+
switch (appType) {
|
|
45
|
+
case AppType.FULLSTACK_WEB:
|
|
41
46
|
return <Globe className="h-5 w-5" />;
|
|
42
|
-
case
|
|
47
|
+
case AppType.FRONTEND_ONLY:
|
|
48
|
+
return <Zap className="h-5 w-5" />;
|
|
49
|
+
case AppType.API_ONLY:
|
|
50
|
+
return <Server className="h-5 w-5" />;
|
|
51
|
+
case AppType.MOBILE:
|
|
52
|
+
return <Smartphone className="h-5 w-5" />;
|
|
53
|
+
case AppType.MOBILE_WITH_API:
|
|
43
54
|
return <Smartphone className="h-5 w-5" />;
|
|
44
55
|
default:
|
|
45
|
-
return <
|
|
56
|
+
return <Code className="h-5 w-5" />;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getGradientClass(appType: AppType) {
|
|
61
|
+
switch (appType) {
|
|
62
|
+
case AppType.FULLSTACK_WEB:
|
|
63
|
+
return "from-blue-500/20 to-indigo-500/5";
|
|
64
|
+
case AppType.FRONTEND_ONLY:
|
|
65
|
+
return "from-yellow-500/20 to-orange-500/5";
|
|
66
|
+
case AppType.API_ONLY:
|
|
67
|
+
return "from-green-500/20 to-emerald-500/5";
|
|
68
|
+
case AppType.MOBILE:
|
|
69
|
+
return "from-purple-500/20 to-pink-500/5";
|
|
70
|
+
case AppType.MOBILE_WITH_API:
|
|
71
|
+
return "from-violet-500/20 to-purple-500/5";
|
|
72
|
+
default:
|
|
73
|
+
return "from-gray-500/20 to-slate-500/5";
|
|
46
74
|
}
|
|
47
75
|
}
|
|
48
76
|
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
function getAppTypeLabel(appType: AppType): string {
|
|
78
|
+
switch (appType) {
|
|
79
|
+
case AppType.FULLSTACK_WEB:
|
|
80
|
+
return "Fullstack";
|
|
81
|
+
case AppType.FRONTEND_ONLY:
|
|
82
|
+
return "Frontend";
|
|
83
|
+
case AppType.API_ONLY:
|
|
84
|
+
return "API";
|
|
85
|
+
case AppType.MOBILE:
|
|
86
|
+
return "Mobile";
|
|
87
|
+
case AppType.MOBILE_WITH_API:
|
|
88
|
+
return "Mobile+API";
|
|
89
|
+
default:
|
|
90
|
+
return "Unknown";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getFrontendLabel(framework: FrontendFramework): string {
|
|
95
|
+
switch (framework) {
|
|
96
|
+
case FrontendFramework.REACT_VITE:
|
|
97
|
+
return "React";
|
|
98
|
+
case FrontendFramework.NEXTJS:
|
|
99
|
+
return "Next.js";
|
|
100
|
+
case FrontendFramework.VUE:
|
|
101
|
+
return "Vue";
|
|
102
|
+
case FrontendFramework.SVELTE:
|
|
103
|
+
return "Svelte";
|
|
104
|
+
case FrontendFramework.REACT_NATIVE:
|
|
105
|
+
return "RN";
|
|
106
|
+
case FrontendFramework.EXPO:
|
|
107
|
+
return "Expo";
|
|
108
|
+
case FrontendFramework.FLUTTER:
|
|
109
|
+
return "Flutter";
|
|
110
|
+
default:
|
|
111
|
+
return "";
|
|
52
112
|
}
|
|
53
|
-
|
|
54
|
-
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getBackendLabel(framework: BackendFramework): string {
|
|
116
|
+
switch (framework) {
|
|
117
|
+
case BackendFramework.EXPRESS:
|
|
118
|
+
return "Express";
|
|
119
|
+
case BackendFramework.FASTAPI:
|
|
120
|
+
return "FastAPI";
|
|
121
|
+
case BackendFramework.DJANGO:
|
|
122
|
+
return "Django";
|
|
123
|
+
case BackendFramework.NESTJS:
|
|
124
|
+
return "NestJS";
|
|
125
|
+
default:
|
|
126
|
+
return "";
|
|
55
127
|
}
|
|
56
|
-
return "from-blue-500/20 to-indigo-500/5";
|
|
57
128
|
}
|
|
58
129
|
|
|
59
130
|
export function ProjectCard({ project, onDelete }: ProjectCardProps) {
|
|
60
131
|
const { locale } = useTranslation();
|
|
61
|
-
const gradientClass = getGradientClass(project.
|
|
132
|
+
const gradientClass = getGradientClass(project.appType);
|
|
62
133
|
|
|
63
134
|
return (
|
|
64
135
|
<Link href={`/project/${project.id}`}>
|
|
65
136
|
<Card className="group relative cursor-pointer overflow-hidden border transition-all hover:border-primary/50 hover:shadow-lg">
|
|
66
137
|
{/* Gradient background */}
|
|
67
|
-
<div
|
|
138
|
+
<div
|
|
139
|
+
className={`absolute inset-0 bg-gradient-to-br ${gradientClass} opacity-0 transition-opacity group-hover:opacity-100`}
|
|
140
|
+
/>
|
|
68
141
|
|
|
69
142
|
<div className="relative p-4">
|
|
70
143
|
{/* Header */}
|
|
71
144
|
<div className="flex items-start justify-between gap-2">
|
|
72
145
|
<div className="flex items-center gap-3">
|
|
73
146
|
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
74
|
-
{
|
|
147
|
+
{getAppTypeIcon(project.appType)}
|
|
75
148
|
</div>
|
|
76
149
|
<div className="min-w-0 flex-1">
|
|
77
150
|
<h3 className="truncate font-semibold">{project.name}</h3>
|
|
@@ -98,25 +171,43 @@ export function ProjectCard({ project, onDelete }: ProjectCardProps) {
|
|
|
98
171
|
|
|
99
172
|
{/* Tags */}
|
|
100
173
|
<div className="mt-4 flex flex-wrap gap-2">
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
174
|
+
{/* App Type */}
|
|
175
|
+
<span className="rounded-full bg-secondary px-2.5 py-0.5 text-xs text-muted-foreground">
|
|
176
|
+
{getAppTypeLabel(project.appType)}
|
|
177
|
+
</span>
|
|
178
|
+
|
|
179
|
+
{/* Frontend Framework */}
|
|
180
|
+
{project.frontendFramework &&
|
|
181
|
+
project.frontendFramework !== FrontendFramework.NONE && (
|
|
182
|
+
<span className="inline-flex items-center gap-1 rounded-full bg-blue-500/10 text-blue-600 px-2.5 py-0.5 text-xs font-medium">
|
|
183
|
+
{getFrontendLabel(project.frontendFramework)}
|
|
184
|
+
</span>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
{/* Backend Framework */}
|
|
188
|
+
{project.backendFramework &&
|
|
189
|
+
project.backendFramework !== BackendFramework.NONE && (
|
|
190
|
+
<span className="inline-flex items-center gap-1 rounded-full bg-green-500/10 text-green-600 px-2.5 py-0.5 text-xs font-medium">
|
|
191
|
+
<Server className="h-3 w-3" />
|
|
192
|
+
{getBackendLabel(project.backendFramework)}
|
|
193
|
+
</span>
|
|
194
|
+
)}
|
|
195
|
+
|
|
196
|
+
{/* Database Provider */}
|
|
107
197
|
{project.databaseProvider && (
|
|
108
|
-
<span
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
198
|
+
<span
|
|
199
|
+
className={`inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ${
|
|
200
|
+
project.databaseProvider === DatabaseProvider.POSTGRES_DOCKER
|
|
201
|
+
? "bg-blue-500/10 text-blue-600"
|
|
202
|
+
: "bg-amber-500/10 text-amber-600"
|
|
203
|
+
}`}
|
|
204
|
+
>
|
|
113
205
|
<Database className="h-3 w-3" />
|
|
114
|
-
{project.databaseProvider === DatabaseProvider.POSTGRES_DOCKER
|
|
206
|
+
{project.databaseProvider === DatabaseProvider.POSTGRES_DOCKER
|
|
207
|
+
? "PostgreSQL"
|
|
208
|
+
: "SQLite"}
|
|
115
209
|
</span>
|
|
116
210
|
)}
|
|
117
|
-
<span className="rounded-full bg-secondary px-2.5 py-0.5 text-xs text-muted-foreground">
|
|
118
|
-
{project.projectType === ProjectType.WEB ? "Web" : "Native"}
|
|
119
|
-
</span>
|
|
120
211
|
</div>
|
|
121
212
|
</div>
|
|
122
213
|
</Card>
|
|
@@ -44,7 +44,8 @@ export const useProjectStore = create<ProjectState>((set, get) => ({
|
|
|
44
44
|
{
|
|
45
45
|
id: project.id,
|
|
46
46
|
name: project.name,
|
|
47
|
-
|
|
47
|
+
appType: project.appType,
|
|
48
|
+
frontendFramework: project.frontendFramework,
|
|
48
49
|
backendFramework: project.backendFramework,
|
|
49
50
|
updatedAt: project.updatedAt,
|
|
50
51
|
},
|
package/package.json
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { FrontendFramework, TechStackConfig, AppType } from "./tech-stack";
|
|
2
|
+
|
|
3
|
+
// @deprecated Use AppType instead
|
|
1
4
|
export enum ProjectType {
|
|
2
5
|
WEB = "WEB",
|
|
3
6
|
NATIVE = "NATIVE",
|
|
@@ -7,8 +10,13 @@ export enum BackendFramework {
|
|
|
7
10
|
NONE = "NONE",
|
|
8
11
|
EXPRESS = "EXPRESS",
|
|
9
12
|
FASTAPI = "FASTAPI",
|
|
13
|
+
DJANGO = "DJANGO",
|
|
14
|
+
NESTJS = "NESTJS",
|
|
10
15
|
}
|
|
11
16
|
|
|
17
|
+
export { FrontendFramework, AppType } from "./tech-stack";
|
|
18
|
+
export type { TechStackConfig } from "./tech-stack";
|
|
19
|
+
|
|
12
20
|
export enum DatabaseProvider {
|
|
13
21
|
POSTGRES_DOCKER = "POSTGRES_DOCKER",
|
|
14
22
|
SQLITE = "SQLITE",
|
|
@@ -23,13 +31,18 @@ export const backendFrameworkLabels: Record<BackendFramework, string> = {
|
|
|
23
31
|
[BackendFramework.NONE]: "프론트엔드 전용",
|
|
24
32
|
[BackendFramework.EXPRESS]: "Express (Node.js)",
|
|
25
33
|
[BackendFramework.FASTAPI]: "FastAPI (Python)",
|
|
34
|
+
[BackendFramework.DJANGO]: "Django (Python)",
|
|
35
|
+
[BackendFramework.NESTJS]: "NestJS (Node.js)",
|
|
26
36
|
};
|
|
27
37
|
|
|
28
38
|
export interface Project {
|
|
29
39
|
id: string;
|
|
30
40
|
name: string;
|
|
31
|
-
|
|
41
|
+
appType: AppType;
|
|
42
|
+
projectType: ProjectType; // @deprecated
|
|
43
|
+
frontendFramework: FrontendFramework;
|
|
32
44
|
backendFramework: BackendFramework;
|
|
45
|
+
techStackConfig?: TechStackConfig;
|
|
33
46
|
path: string;
|
|
34
47
|
description?: string;
|
|
35
48
|
createdAt: Date;
|
|
@@ -38,7 +51,8 @@ export interface Project {
|
|
|
38
51
|
|
|
39
52
|
export interface CreateProjectInput {
|
|
40
53
|
name: string;
|
|
41
|
-
|
|
54
|
+
appType: AppType;
|
|
55
|
+
frontendFramework?: FrontendFramework;
|
|
42
56
|
backendFramework?: BackendFramework;
|
|
43
57
|
description?: string;
|
|
44
58
|
}
|
|
@@ -46,7 +60,8 @@ export interface CreateProjectInput {
|
|
|
46
60
|
export interface ProjectListItem {
|
|
47
61
|
id: string;
|
|
48
62
|
name: string;
|
|
49
|
-
|
|
63
|
+
appType: AppType;
|
|
64
|
+
frontendFramework: FrontendFramework;
|
|
50
65
|
backendFramework: BackendFramework;
|
|
51
66
|
databaseProvider?: DatabaseProvider | null;
|
|
52
67
|
updatedAt: Date;
|