Skip to content

Commit 2a09d25

Browse files
committed
feat: add Backend Frameworks section to Hub below Official Templates
- Add BackendFramework interface and data for Django, FastAPI, Flask, Node.js - Create BackendFrameworkCard component similar to TemplateCard - Integrate Backend Frameworks section in Hub page between Official Templates and Community Templates - Update CreateAppDialog to support backend framework selection - Backend frameworks can now be selected and used to create apps with proper scaffolding
1 parent 8bbd292 commit 2a09d25

File tree

4 files changed

+165
-1
lines changed

4 files changed

+165
-1
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import React from "react";
2+
import { useSettings } from "@/hooks/useSettings";
3+
import type { BackendFramework } from "@/shared/backendFrameworks";
4+
import { Button } from "./ui/button";
5+
import { cn } from "@/lib/utils";
6+
7+
interface BackendFrameworkCardProps {
8+
framework: BackendFramework;
9+
isSelected: boolean;
10+
onSelect: (frameworkId: string) => void;
11+
onCreateApp: () => void;
12+
}
13+
14+
export const BackendFrameworkCard: React.FC<BackendFrameworkCardProps> = ({
15+
framework,
16+
isSelected,
17+
onSelect,
18+
onCreateApp,
19+
}) => {
20+
const { settings } = useSettings();
21+
22+
const handleCardClick = () => {
23+
onSelect(framework.id);
24+
};
25+
26+
return (
27+
<div
28+
onClick={handleCardClick}
29+
className={`
30+
bg-white dark:bg-gray-800 rounded-xl shadow-sm overflow-hidden
31+
transform transition-all duration-300 ease-in-out
32+
cursor-pointer group relative
33+
${
34+
isSelected
35+
? "ring-2 ring-blue-500 dark:ring-blue-400 shadow-xl"
36+
: "hover:shadow-lg hover:-translate-y-1"
37+
}
38+
`}
39+
>
40+
<div className="relative">
41+
<img
42+
src={framework.imageUrl}
43+
alt={framework.title}
44+
className={`w-full h-52 object-cover transition-opacity duration-300 group-hover:opacity-80 ${
45+
isSelected ? "opacity-75" : ""
46+
}`}
47+
/>
48+
{isSelected && (
49+
<span className="absolute top-3 right-3 bg-blue-600 text-white text-xs font-bold px-3 py-1.5 rounded-md shadow-lg">
50+
Selected
51+
</span>
52+
)}
53+
</div>
54+
<div className="p-4">
55+
<div className="flex justify-between items-center mb-1.5">
56+
<h2
57+
className={`text-lg font-semibold ${
58+
isSelected
59+
? "text-blue-600 dark:text-blue-400"
60+
: "text-gray-900 dark:text-white"
61+
}`}
62+
>
63+
{framework.title}
64+
</h2>
65+
<span
66+
className={`text-xs font-semibold px-2 py-0.5 rounded-full ${
67+
isSelected
68+
? "bg-blue-100 text-blue-700 dark:bg-blue-600 dark:text-blue-100"
69+
: "bg-green-100 text-green-800 dark:bg-green-700 dark:text-green-200"
70+
}`}
71+
>
72+
{framework.language}
73+
</span>
74+
</div>
75+
<p className="text-sm text-gray-500 dark:text-gray-400 mb-3 h-16 overflow-y-auto">
76+
{framework.description}
77+
</p>
78+
79+
<Button
80+
onClick={(e) => {
81+
e.stopPropagation();
82+
onCreateApp();
83+
}}
84+
size="sm"
85+
className={cn(
86+
"w-full bg-indigo-600 hover:bg-indigo-700 text-white font-semibold mt-2",
87+
settings?.selectedBackendFramework !== framework.id && "invisible",
88+
)}
89+
>
90+
Create App
91+
</Button>
92+
</div>
93+
</div>
94+
);
95+
};

src/components/CreateAppDialog.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ export function CreateAppDialog({
9292
<DialogHeader>
9393
<DialogTitle>Create New App</DialogTitle>
9494
<DialogDescription>
95-
{`Create a new app using the ${template?.title} template.`}
95+
{template
96+
? `Create a new app using the ${template.title} template.`
97+
: selectedBackendFramework
98+
? `Create a new app with the ${selectedBackendFramework} backend framework.`
99+
: "Create a new app."}
96100
</DialogDescription>
97101
</DialogHeader>
98102

src/pages/hub.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import { useRouter } from "@tanstack/react-router";
55
import { useSettings } from "@/hooks/useSettings";
66
import { useTemplates } from "@/hooks/useTemplates";
77
import { TemplateCard } from "@/components/TemplateCard";
8+
import { BackendFrameworkCard } from "@/components/BackendFrameworkCard";
89
import { CreateAppDialog } from "@/components/CreateAppDialog";
910
import { NeonConnector } from "@/components/NeonConnector";
11+
import { backendFrameworksData, type BackendFramework } from "@/shared/backendFrameworks";
1012

1113
const HubPage: React.FC = () => {
1214
const router = useRouter();
@@ -19,6 +21,10 @@ const HubPage: React.FC = () => {
1921
updateSettings({ selectedTemplateId: templateId });
2022
};
2123

24+
const handleBackendFrameworkSelect = (frameworkId: string) => {
25+
updateSettings({ selectedBackendFramework: frameworkId });
26+
};
27+
2228
const handleCreateApp = () => {
2329
setIsCreateDialogOpen(true);
2430
};
@@ -28,6 +34,8 @@ const HubPage: React.FC = () => {
2834
const communityTemplates =
2935
templates?.filter((template) => !template.isOfficial) || [];
3036

37+
const selectedBackendFramework = settings?.selectedBackendFramework;
38+
3139
return (
3240
<div className="min-h-screen px-8 py-4">
3341
<div className="max-w-5xl mx-auto pb-12">
@@ -70,6 +78,24 @@ const HubPage: React.FC = () => {
7078
</section>
7179
)}
7280

81+
{/* Backend Frameworks Section */}
82+
<section className="mb-12">
83+
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">
84+
Backend frameworks
85+
</h2>
86+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
87+
{backendFrameworksData.map((framework) => (
88+
<BackendFrameworkCard
89+
key={framework.id}
90+
framework={framework}
91+
isSelected={framework.id === selectedBackendFramework}
92+
onSelect={handleBackendFrameworkSelect}
93+
onCreateApp={handleCreateApp}
94+
/>
95+
))}
96+
</div>
97+
</section>
98+
7399
{/* Community Templates Section */}
74100
{communityTemplates.length > 0 && (
75101
<section className="mb-12">
@@ -97,6 +123,7 @@ const HubPage: React.FC = () => {
97123
open={isCreateDialogOpen}
98124
onOpenChange={setIsCreateDialogOpen}
99125
template={templates.find((t) => t.id === settings?.selectedTemplateId)}
126+
selectedBackendFramework={settings?.selectedBackendFramework}
100127
/>
101128
</div>
102129
);

src/shared/backendFrameworks.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export interface BackendFramework {
2+
id: string;
3+
title: string;
4+
description: string;
5+
imageUrl: string;
6+
language: string;
7+
}
8+
9+
export const backendFrameworksData: BackendFramework[] = [
10+
{
11+
id: "django",
12+
title: "Django",
13+
description: "High-level Python web framework that encourages rapid development and clean, pragmatic design.",
14+
imageUrl: "assets/backend-frameworks/django-screenshot.svg",
15+
language: "Python",
16+
},
17+
{
18+
id: "fastapi",
19+
title: "FastAPI",
20+
description: "Modern, fast web framework for building APIs with Python 3.7+ based on standard Python type hints.",
21+
imageUrl: "assets/backend-frameworks/fastapi-screenshot.svg",
22+
language: "Python",
23+
},
24+
{
25+
id: "flask",
26+
title: "Flask",
27+
description: "Lightweight WSGI web application framework designed to make getting started quick and easy.",
28+
imageUrl: "assets/backend-frameworks/flask-screenshot.svg",
29+
language: "Python",
30+
},
31+
{
32+
id: "nodejs",
33+
title: "Node.js + Express",
34+
description: "JavaScript runtime built on Chrome's V8 JavaScript engine with Express.js web application framework.",
35+
imageUrl: "assets/backend-frameworks/nodejs-screenshot.svg",
36+
language: "JavaScript",
37+
},
38+
];

0 commit comments

Comments
 (0)