update stack architech

This commit is contained in:
Aman Varshney
2025-04-01 02:27:48 +05:30
parent 579654f166
commit bcedaee16b

View File

@@ -11,64 +11,6 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
const triggerConfetti = () => {
const createConfettiElement = (color: string) => {
const confetti = document.createElement("div");
confetti.style.position = "fixed";
confetti.style.width = `${Math.random() * 10 + 5}px`;
confetti.style.height = `${Math.random() * 10 + 5}px`;
confetti.style.backgroundColor = color;
confetti.style.borderRadius = "50%";
confetti.style.zIndex = "9999";
const startX = window.innerWidth / 2 + (Math.random() - 0.5) * 200;
const startY = window.innerHeight / 2;
confetti.style.left = `${startX}px`;
confetti.style.top = `${startY}px`;
document.body.appendChild(confetti);
const angle = Math.random() * Math.PI * 2;
const velocity = Math.random() * 5 + 3;
const vx = Math.cos(angle) * velocity;
let vy = Math.sin(angle) * velocity - 2;
let posX = startX;
let posY = startY;
let opacity = 1;
let rotation = 0;
const animate = () => {
posX += vx;
posY += vy;
vy += 0.1;
opacity -= 0.01;
rotation += 5;
confetti.style.left = `${posX}px`;
confetti.style.top = `${posY}px`;
confetti.style.opacity = `${opacity}`;
confetti.style.transform = `rotate(${rotation}deg)`;
if (opacity > 0 && posY < window.innerHeight) {
requestAnimationFrame(animate);
} else {
confetti.remove();
}
};
requestAnimationFrame(animate);
};
const colors = ["#ff6b6b", "#4ecdc4", "#45b7d1", "#ffd166", "#f8a5c2"];
for (let i = 0; i < 30; i++) {
setTimeout(() => {
createConfettiElement(colors[Math.floor(Math.random() * colors.length)]);
}, Math.random() * 500);
}
};
const validateProjectName = (name: string): string | undefined => { const validateProjectName = (name: string): string | undefined => {
const INVALID_CHARS = ["<", ">", ":", '"', "|", "?", "*"]; const INVALID_CHARS = ["<", ">", ":", '"', "|", "?", "*"];
const MAX_LENGTH = 255; const MAX_LENGTH = 255;
@@ -298,6 +240,14 @@ const TECH_OPTIONS = {
color: "from-indigo-500 to-indigo-700", color: "from-indigo-500 to-indigo-700",
default: false, default: false,
}, },
{
id: "ai",
name: "AI Example",
description: "AI integration example",
icon: "🤖",
color: "from-purple-500 to-purple-700",
default: false,
},
], ],
git: [ git: [
{ {
@@ -370,7 +320,7 @@ const DEFAULT_STACK: StackState = {
const StackArchitect = () => { const StackArchitect = () => {
const [stack, setStack] = useState<StackState>(DEFAULT_STACK); const [stack, setStack] = useState<StackState>(DEFAULT_STACK);
const [command, setCommand] = useState( const [command, setCommand] = useState(
"npx create-better-t-stack my-better-t-app --yes", "npx create-better-t-stack@latest my-better-t-app --yes",
); );
const [activeTab, setActiveTab] = useState("frontend"); const [activeTab, setActiveTab] = useState("frontend");
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
@@ -424,7 +374,9 @@ const StackArchitect = () => {
notes.examples = []; notes.examples = [];
if (!stack.frontend.includes("web")) { if (!stack.frontend.includes("web")) {
notes.examples.push("Todo example is only available with React Web."); notes.examples.push(
"Todo and Ai example are only available with React Web.",
);
} }
setCompatNotes(notes); setCompatNotes(notes);
@@ -441,85 +393,74 @@ const StackArchitect = () => {
} }
const projectName = stackState.projectName || "my-better-t-app"; const projectName = stackState.projectName || "my-better-t-app";
const flags: string[] = []; const flags: string[] = ["--yes"]; // Start with yes flag
const isAllDefault = // Only add flags that differ from defaults
stackState.frontend.length === 1 &&
stackState.frontend[0] === "web" &&
stackState.runtime === "bun" &&
stackState.backendFramework === "hono" &&
stackState.database === "sqlite" &&
stackState.orm === "drizzle" &&
stackState.auth === "true" &&
stackState.turso === "false" &&
stackState.packageManager === "bun" &&
stackState.addons.length === 0 &&
stackState.examples.length === 0 &&
stackState.git === "true" &&
stackState.install === "true";
if (isAllDefault) { // Frontend (default is web)
return `${base} ${projectName} --yes`; if (stackState.frontend.length === 1 && stackState.frontend[0] === "none") {
flags.push("--frontend none");
} else if (
!(stackState.frontend.length === 1 && stackState.frontend[0] === "web")
) {
flags.push(`--frontend ${stackState.frontend.join(",")}`);
} }
flags.push("--yes"); // Database (default is sqlite)
if (stackState.database !== "sqlite") {
if (!stackState.frontend.includes("web")) { flags.push(`--database ${stackState.database}`);
flags.push("--no-web");
} }
if (stackState.frontend.includes("native")) { // ORM (default is drizzle)
flags.push("--native"); if (stackState.database !== "none" && stackState.orm !== "drizzle") {
} flags.push(`--orm ${stackState.orm}`);
if (stackState.runtime !== "bun") {
flags.push(`--runtime ${stackState.runtime}`);
}
if (stackState.backendFramework !== "hono") {
flags.push(`--${stackState.backendFramework}`);
}
if (stackState.database === "postgres") {
flags.push("--postgres");
} else if (stackState.database === "none") {
flags.push("--no-database");
}
if (stackState.orm === "prisma" && stackState.database !== "none") {
flags.push("--prisma");
} }
// Auth (default is true)
if (stackState.auth === "false") { if (stackState.auth === "false") {
flags.push("--no-auth"); flags.push("--no-auth");
} }
if (stackState.turso === "true" && stackState.database === "sqlite") { // Turso (default is false)
if (stackState.turso === "true") {
flags.push("--turso"); flags.push("--turso");
} }
// Backend (default is hono)
if (stackState.backendFramework !== "hono") {
flags.push(`--backend ${stackState.backendFramework}`);
}
// Runtime (default is bun)
if (stackState.runtime !== "bun") {
flags.push(`--runtime ${stackState.runtime}`);
}
// Package manager (default is bun)
if (stackState.packageManager !== "bun") { if (stackState.packageManager !== "bun") {
flags.push(`--${stackState.packageManager}`); flags.push(`--package-manager ${stackState.packageManager}`);
}
if (stackState.addons.length > 0) {
for (const addon of stackState.addons) {
flags.push(`--${addon}`);
}
}
if (stackState.examples.length > 0) {
flags.push(`--examples ${stackState.examples.join(",")}`);
} }
// Git (default is true)
if (stackState.git === "false") { if (stackState.git === "false") {
flags.push("--no-git"); flags.push("--no-git");
} }
// Install (default is true)
if (stackState.install === "false") { if (stackState.install === "false") {
flags.push("--no-install"); flags.push("--no-install");
} }
// Addons (default is none)
if (stackState.addons.length > 0) {
flags.push(`--addons ${stackState.addons.join(",")}`);
}
// Examples (default is none)
if (stackState.examples.length > 0) {
flags.push(`--examples ${stackState.examples.join(",")}`);
}
return `${base} ${projectName} ${flags.join(" ")}`; return `${base} ${projectName} ${flags.join(" ")}`;
}, []); }, []);
@@ -534,7 +475,9 @@ const StackArchitect = () => {
...prev, ...prev,
frontend: ["none"], frontend: ["none"],
auth: "false", auth: "false",
examples: prev.examples.filter((ex) => ex !== "todo"), examples: prev.examples.filter(
(ex) => ex !== "todo" && ex !== "ai",
),
addons: prev.addons.filter( addons: prev.addons.filter(
(addon) => addon !== "pwa" && addon !== "tauri", (addon) => addon !== "pwa" && addon !== "tauri",
), ),
@@ -542,41 +485,21 @@ const StackArchitect = () => {
} }
if (currentSelection.includes(techId)) { if (currentSelection.includes(techId)) {
if (techId === "web") { if (currentSelection.length === 1) {
const newFrontend = currentSelection.filter( // Don't remove the last frontend option
(id) => id !== techId, return prev;
);
if (newFrontend.length === 0) {
return {
...prev,
frontend: ["none"],
auth: "false",
examples: prev.examples.filter((ex) => ex !== "todo"),
addons: prev.addons.filter(
(addon) => addon !== "pwa" && addon !== "tauri",
),
};
}
return {
...prev,
frontend: newFrontend,
auth: "false",
examples: prev.examples.filter((ex) => ex !== "todo"),
addons: prev.addons.filter(
(addon) => addon !== "pwa" && addon !== "tauri",
),
};
} }
const newFrontend = currentSelection.filter((id) => id !== techId); const newFrontend = currentSelection.filter((id) => id !== techId);
if (newFrontend.length === 0) { if (techId === "web") {
return { return {
...prev, ...prev,
frontend: ["none"], frontend: newFrontend,
auth: "false", auth: "false",
examples: prev.examples.filter(
(ex) => ex !== "todo" && ex !== "ai",
),
addons: prev.addons.filter( addons: prev.addons.filter(
(addon) => addon !== "pwa" && addon !== "tauri", (addon) => addon !== "pwa" && addon !== "tauri",
), ),
@@ -589,23 +512,19 @@ const StackArchitect = () => {
}; };
} }
if (techId === "web") { // Adding a frontend option
const cleanedSelection = currentSelection.filter( if (currentSelection.includes("none")) {
(id) => id !== "none", // Replace "none" with the selected option
);
return { return {
...prev, ...prev,
frontend: [...cleanedSelection, techId], frontend: [techId],
auth: "true", ...(techId === "web" && { auth: "true" }),
}; };
} }
const cleanedSelection = currentSelection.filter(
(id) => id !== "none",
);
return { return {
...prev, ...prev,
frontend: [...cleanedSelection, techId], frontend: [...currentSelection, techId],
...(techId === "web" && { auth: "true" }),
}; };
} }
@@ -681,8 +600,6 @@ const StackArchitect = () => {
[category]: techId, [category]: techId,
}; };
}); });
triggerConfetti();
}, },
[], [],
); );
@@ -796,8 +713,10 @@ const StackArchitect = () => {
(activeTab === "turso" && stack.database !== "sqlite") || (activeTab === "turso" && stack.database !== "sqlite") ||
(activeTab === "auth" && !stack.frontend.includes("web")) || (activeTab === "auth" && !stack.frontend.includes("web")) ||
(activeTab === "examples" && (activeTab === "examples" &&
tech.id === "todo" && ((tech.id === "todo" &&
!stack.frontend.includes("web")) || !stack.frontend.includes("web")) ||
(tech.id === "ai" &&
!stack.frontend.includes("web")))) ||
(activeTab === "addons" && (activeTab === "addons" &&
(tech.id === "pwa" || tech.id === "tauri") && (tech.id === "pwa" || tech.id === "tauri") &&
!stack.frontend.includes("web")); !stack.frontend.includes("web"));
@@ -806,14 +725,14 @@ const StackArchitect = () => {
<motion.div <motion.div
key={tech.id} key={tech.id}
className={` className={`
p-2 px-3 rounded p-2 px-3 rounded
${isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"} ${isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
${ ${
isSelected isSelected
? "bg-blue-100 dark:bg-blue-900/40 border border-blue-300 dark:border-blue-500/50" ? "bg-blue-100 dark:bg-blue-900/40 border border-blue-300 dark:border-blue-500/50"
: "hover:bg-gray-200 dark:hover:bg-gray-800 border border-gray-300 dark:border-gray-700" : "hover:bg-gray-200 dark:hover:bg-gray-800 border border-gray-300 dark:border-gray-700"
} }
`} `}
whileHover={!isDisabled ? { scale: 1.02 } : undefined} whileHover={!isDisabled ? { scale: 1.02 } : undefined}
whileTap={!isDisabled ? { scale: 0.98 } : undefined} whileTap={!isDisabled ? { scale: 0.98 } : undefined}
onClick={() => onClick={() =>
@@ -946,6 +865,21 @@ const StackArchitect = () => {
</span> </span>
) : null; ) : null;
})} })}
{stack.examples.length > 0 &&
stack.examples.map((exampleId) => {
const example = TECH_OPTIONS.examples.find(
(e) => e.id === exampleId,
);
return example ? (
<span
key={exampleId}
className="inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-teal-100 dark:bg-teal-900/30 text-teal-800 dark:text-teal-300 border border-teal-300 dark:border-teal-700/30"
>
{example.icon} {example.name}
</span>
) : null;
})}
</div> </div>
</div> </div>
</div> </div>
@@ -956,13 +890,13 @@ const StackArchitect = () => {
type="button" type="button"
key={category} key={category}
className={` className={`
py-2 px-4 text-xs font-mono whitespace-nowrap transition-colors py-2 px-4 text-xs font-mono whitespace-nowrap transition-colors
${ ${
activeTab === category activeTab === category
? "bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 border-t-2 border-blue-500" ? "bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 border-t-2 border-blue-500"
: "text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-800" : "text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-800"
} }
`} `}
onClick={() => setActiveTab(category)} onClick={() => setActiveTab(category)}
> >
{category} {category}