$
diff --git a/apps/web/src/app/(home)/_components/CustomizableSection.tsx b/apps/web/src/app/(home)/_components/CustomizableSection.tsx
index 2ab0de7..13e668e 100644
--- a/apps/web/src/app/(home)/_components/CustomizableSection.tsx
+++ b/apps/web/src/app/(home)/_components/CustomizableSection.tsx
@@ -1,5 +1,3 @@
-import { cn } from "@/lib/utils";
-import { Code, Sliders, Terminal, TerminalSquare } from "lucide-react";
import { motion } from "motion/react";
import StackArchitect from "./StackArchitech";
@@ -32,48 +30,6 @@ export default function CustomizableSection() {
Configure your ideal TypeScript environment with all the options you
need
-
-
-
-
- Runtime Options
-
- --runtime
-
-
-
- Framework Options
-
- --framework
-
-
-
- Database Options
-
- --database
-
-
-
- Addon Options
-
- --addons
-
-
-
-
-
- Bun or Node
- Hono or Elysia
- SQLite or PostgreSQL
- Drizzle or Prisma
- Authentication
- Optional Addons
@@ -82,22 +38,10 @@ export default function CustomizableSection() {
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.6, delay: 0.2 }}
- className="relative"
+ className="relative border"
>
);
}
-
-function Badge({
- children,
- color,
-}: { children: React.ReactNode; color: string }) {
- const baseClasses =
- "rounded-full border px-2.5 py-1 font-medium text-xs shadow-sm";
-
- const colorClasses = `bg-[--color-${color}]/10 text-[--color-${color}] border-[--color-${color}]/30`;
-
- return
{children};
-}
diff --git a/apps/web/src/app/(home)/_components/Navbar.tsx b/apps/web/src/app/(home)/_components/Navbar.tsx
index 45bffac..8584be2 100644
--- a/apps/web/src/app/(home)/_components/Navbar.tsx
+++ b/apps/web/src/app/(home)/_components/Navbar.tsx
@@ -75,7 +75,7 @@ const Navbar = () => {
)}
>
{
: "pointer-events-none opacity-0",
)}
>
-
+
diff --git a/apps/web/src/app/(home)/_components/StackArchitech.tsx b/apps/web/src/app/(home)/_components/StackArchitech.tsx
index 03c1814..c421b7e 100644
--- a/apps/web/src/app/(home)/_components/StackArchitech.tsx
+++ b/apps/web/src/app/(home)/_components/StackArchitech.tsx
@@ -1,5 +1,6 @@
"use client";
+import { ThemeToggle } from "@/components/theme-toggle";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
DEFAULT_STACK,
@@ -7,6 +8,7 @@ import {
type StackState,
TECH_OPTIONS,
} from "@/lib/constant";
+import { stackParsers, stackQueryStatesOptions } from "@/lib/stack-url-state";
import { cn } from "@/lib/utils";
import {
Check,
@@ -24,6 +26,7 @@ import {
import { motion } from "motion/react";
import Image from "next/image";
import Link from "next/link";
+import { useQueryStates } from "nuqs";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
const validateProjectName = (name: string): string | undefined => {
@@ -106,8 +109,44 @@ const TechIcon = ({
);
};
+const getBadgeColors = (category: string): string => {
+ switch (category) {
+ case "frontend":
+ return "border-blue-300 bg-blue-100 text-blue-800 dark:border-blue-700/30 dark:bg-blue-900/30 dark:text-blue-300";
+ case "runtime":
+ return "border-amber-300 bg-amber-100 text-amber-800 dark:border-amber-700/30 dark:bg-amber-900/30 dark:text-amber-300";
+ case "backendFramework":
+ return "border-sky-300 bg-sky-100 text-sky-800 dark:border-sky-700/30 dark:bg-sky-900/30 dark:text-sky-300";
+ case "api":
+ return "border-indigo-300 bg-indigo-100 text-indigo-800 dark:border-indigo-700/30 dark:bg-indigo-900/30 dark:text-indigo-300";
+ case "database":
+ return "border-emerald-300 bg-emerald-100 text-emerald-800 dark:border-emerald-700/30 dark:bg-emerald-900/30 dark:text-emerald-300";
+ case "orm":
+ return "border-cyan-300 bg-cyan-100 text-cyan-800 dark:border-cyan-700/30 dark:bg-cyan-900/30 dark:text-cyan-300";
+ case "auth":
+ return "border-green-300 bg-green-100 text-green-800 dark:border-green-700/30 dark:bg-green-900/30 dark:text-green-300";
+ case "dbSetup":
+ return "border-pink-300 bg-pink-100 text-pink-800 dark:border-pink-700/30 dark:bg-pink-900/30 dark:text-pink-300";
+ case "addons":
+ return "border-violet-300 bg-violet-100 text-violet-800 dark:border-violet-700/30 dark:bg-violet-900/30 dark:text-violet-300";
+ case "examples":
+ return "border-teal-300 bg-teal-100 text-teal-800 dark:border-teal-700/30 dark:bg-teal-900/30 dark:text-teal-300";
+ case "packageManager":
+ return "border-orange-300 bg-orange-100 text-orange-800 dark:border-orange-700/30 dark:bg-orange-900/30 dark:text-orange-300";
+ case "git":
+ case "install":
+ return "border-gray-300 bg-gray-100 text-gray-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400";
+ default:
+ return "border-gray-300 bg-gray-100 text-gray-800 dark:border-gray-700/30 dark:bg-gray-900/30 dark:text-gray-300";
+ }
+};
+
const StackArchitect = () => {
- const [stack, setStack] = useState
(DEFAULT_STACK);
+ const [stack, setStack] = useQueryStates(
+ stackParsers,
+ stackQueryStatesOptions,
+ );
+
const [command, setCommand] = useState("");
const [copied, setCopied] = useState(false);
const [compatNotes, setCompatNotes] = useState<
@@ -154,7 +193,9 @@ const StackArchitect = () => {
// biome-ignore lint/correctness/useExhaustiveDependencies:
useEffect(() => {
- setStack((currentStack) => {
+ const calculateAdjustedStack = (
+ currentStack: StackState,
+ ): StackState | null => {
const nextStack = { ...currentStack };
let changed = false;
@@ -247,8 +288,13 @@ const StackArchitect = () => {
);
if (nextStack.examples.length !== originalExamplesLength) changed = true;
- return changed ? nextStack : currentStack;
- });
+ return changed ? nextStack : null;
+ };
+
+ const adjustedStack = calculateAdjustedStack(stack);
+ if (adjustedStack) {
+ setStack(adjustedStack);
+ }
}, [
stack.database,
stack.orm,
@@ -259,6 +305,7 @@ const StackArchitect = () => {
stack.addons,
stack.examples,
stack.backendFramework,
+ setStack,
]);
const generateCommand = useCallback((stackState: StackState) => {
@@ -282,7 +329,7 @@ const StackArchitect = () => {
key: K,
value: StackState[K],
) => {
- const defaultValue = DEFAULT_STACK[key];
+ const defaultValue = stackParsers[key]?.defaultValue;
if (Array.isArray(defaultValue) && Array.isArray(value)) {
return (
@@ -550,9 +597,9 @@ const StackArchitect = () => {
const handleTechSelect = useCallback(
(category: keyof typeof TECH_OPTIONS, techId: string) => {
- setStack((prev) => {
- const currentStack = { ...prev };
+ setStack((currentStack) => {
const catKey = category as keyof StackState;
+ const update: Partial = {};
if (
catKey === "frontend" ||
@@ -575,7 +622,7 @@ const StackArchitect = () => {
} else if (isSelected) {
nextArray = nextArray.filter((id) => id !== techId);
if (nextArray.length === 0) {
- return prev;
+ return {};
}
} else {
nextArray = nextArray.filter((id) => id !== "none");
@@ -591,16 +638,23 @@ const StackArchitect = () => {
nextArray.push(techId);
}
}
- return { ...currentStack, [catKey]: [...new Set(nextArray)] };
+
+ if (
+ JSON.stringify([...new Set(nextArray)].sort()) !==
+ JSON.stringify(currentArray.sort())
+ ) {
+ update[catKey] = [...new Set(nextArray)];
+ }
+ } else {
+ if (currentStack[catKey] !== techId) {
+ update[catKey] = techId;
+ }
}
- if (currentStack[catKey] === techId) {
- return prev;
- }
- return { ...currentStack, [catKey]: techId };
+ return update;
});
},
- [],
+ [setStack],
);
const getDisabledReason = useCallback(
@@ -709,12 +763,11 @@ const StackArchitect = () => {
const resetStack = useCallback(() => {
setStack(DEFAULT_STACK);
- setProjectNameError(validateProjectName(DEFAULT_STACK.projectName || ""));
setShowHelp(false);
setShowPresets(false);
setActiveCategory(CATEGORY_ORDER[0]);
contentRef.current?.scrollTo(0, 0);
- }, []);
+ }, [setStack]);
useEffect(() => {
setProjectNameError(validateProjectName(stack.projectName || ""));
@@ -728,29 +781,28 @@ const StackArchitect = () => {
const loadSavedStack = useCallback(() => {
if (lastSavedStack) {
setStack(lastSavedStack);
- setProjectNameError(
- validateProjectName(lastSavedStack.projectName || ""),
- );
setShowHelp(false);
setShowPresets(false);
setActiveCategory(CATEGORY_ORDER[0]);
contentRef.current?.scrollTo(0, 0);
}
- }, [lastSavedStack]);
+ }, [lastSavedStack, setStack]);
- const applyPreset = useCallback((presetId: string) => {
- const preset = PRESET_TEMPLATES.find(
- (template) => template.id === presetId,
- );
- if (preset) {
- setStack(preset.stack);
- setProjectNameError(validateProjectName(preset.stack.projectName || ""));
- setShowPresets(false);
- setShowHelp(false);
- setActiveCategory(CATEGORY_ORDER[0]);
- contentRef.current?.scrollTo(0, 0);
- }
- }, []);
+ const applyPreset = useCallback(
+ (presetId: string) => {
+ const preset = PRESET_TEMPLATES.find(
+ (template) => template.id === presetId,
+ );
+ if (preset) {
+ setStack(preset.stack);
+ setShowPresets(false);
+ setShowHelp(false);
+ setActiveCategory(CATEGORY_ORDER[0]);
+ contentRef.current?.scrollTo(0, 0);
+ }
+ },
+ [setStack],
+ );
const handleSidebarClick = (category: string) => {
setActiveCategory(category);
@@ -768,19 +820,23 @@ const StackArchitect = () => {
return (
-
Home
-
+
+
+ Home
+
+
+
Create Better T Stack
-
+
+
@@ -862,7 +918,7 @@ const StackArchitect = () => {
type="button"
key={preset.id}
onClick={() => applyPreset(preset.id)}
- className="rounded border border-border bg-card p-2 text-left transition-colors hover:bg-muted"
+ className="rounded border border-border bg-background p-2 text-left transition-colors hover:bg-muted"
>
{preset.name}
@@ -876,7 +932,7 @@ const StackArchitect = () => {
)}
-
+
-
-
-
-
$
+
+
+ $
{command}
@@ -948,13 +1002,24 @@ const StackArchitect = () => {
{copied ? (
-
+ <>
+
+ Copied
+ >
) : (
-
+ <>
+
+ Copy
+ >
)}
@@ -1052,7 +1117,7 @@ const StackArchitect = () => {
{getCategoryDisplayName(category)}
{compatNotes[category]?.hasIssue && (
- {" "}
+ {" "}
)}
@@ -1093,16 +1158,14 @@ const StackArchitect = () => {
className={cn(
"mb-4 rounded-md border p-3",
notesInfo.hasIssue
- ? "border-[--color-chart-5] bg-[--color-chart-5]/10"
+ ? "border-chart-5 bg-chart-5/10"
: "border-primary bg-primary/10",
)}
>
@@ -1116,7 +1179,7 @@ const StackArchitect = () => {
className={cn(
"list-inside list-disc space-y-1 text-xs",
notesInfo.hasIssue
- ? "text-[--color-chart-5]/90"
+ ? "text-chart-5/90"
: "text-primary/90",
)}
>
@@ -1235,36 +1298,4 @@ const StackArchitect = () => {
);
};
-const getBadgeColors = (category: string): string => {
- switch (category) {
- case "frontend":
- return "border-blue-300 bg-blue-100 text-blue-800 dark:border-blue-700/30 dark:bg-blue-900/30 dark:text-blue-300";
- case "runtime":
- return "border-amber-300 bg-amber-100 text-amber-800 dark:border-amber-700/30 dark:bg-amber-900/30 dark:text-amber-300";
- case "backendFramework":
- return "border-sky-300 bg-sky-100 text-sky-800 dark:border-sky-700/30 dark:bg-sky-900/30 dark:text-sky-300";
- case "api":
- return "border-indigo-300 bg-indigo-100 text-indigo-800 dark:border-indigo-700/30 dark:bg-indigo-900/30 dark:text-indigo-300";
- case "database":
- return "border-emerald-300 bg-emerald-100 text-emerald-800 dark:border-emerald-700/30 dark:bg-emerald-900/30 dark:text-emerald-300";
- case "orm":
- return "border-cyan-300 bg-cyan-100 text-cyan-800 dark:border-cyan-700/30 dark:bg-cyan-900/30 dark:text-cyan-300";
- case "auth":
- return "border-green-300 bg-green-100 text-green-800 dark:border-green-700/30 dark:bg-green-900/30 dark:text-green-300";
- case "dbSetup":
- return "border-pink-300 bg-pink-100 text-pink-800 dark:border-pink-700/30 dark:bg-pink-900/30 dark:text-pink-300";
- case "addons":
- return "border-violet-300 bg-violet-100 text-violet-800 dark:border-violet-700/30 dark:bg-violet-900/30 dark:text-violet-300";
- case "examples":
- return "border-teal-300 bg-teal-100 text-teal-800 dark:border-teal-700/30 dark:bg-teal-900/30 dark:text-teal-300";
- case "packageManager":
- return "border-orange-300 bg-orange-100 text-orange-800 dark:border-orange-700/30 dark:bg-orange-900/30 dark:text-orange-300";
- case "git":
- case "install":
- return "border-gray-300 bg-gray-100 text-gray-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400";
- default:
- return "border-gray-300 bg-gray-100 text-gray-800 dark:border-gray-700/30 dark:bg-gray-900/30 dark:text-gray-300";
- }
-};
-
export default StackArchitect;
diff --git a/apps/web/src/app/(home)/_components/Testimonials.tsx b/apps/web/src/app/(home)/_components/Testimonials.tsx
index de2049b..a1f1ba7 100644
--- a/apps/web/src/app/(home)/_components/Testimonials.tsx
+++ b/apps/web/src/app/(home)/_components/Testimonials.tsx
@@ -119,7 +119,7 @@ export default function Testimonials() {
transition={{ duration: 0.5, delay: 0.3 }}
className="relative mt-4 sm:mt-8"
>
-
+
diff --git a/apps/web/src/app/(home)/layout.tsx b/apps/web/src/app/(home)/layout.tsx
index 0b15109..20db734 100644
--- a/apps/web/src/app/(home)/layout.tsx
+++ b/apps/web/src/app/(home)/layout.tsx
@@ -1,9 +1,9 @@
-import type { ReactNode } from "react";
+import { type ReactNode, Suspense } from "react";
export default function Layout({ children }: { children: ReactNode }) {
return (
- {children}
+ {children}
);
}
diff --git a/apps/web/src/app/(home)/page.tsx b/apps/web/src/app/(home)/page.tsx
index 85be40b..465038c 100644
--- a/apps/web/src/app/(home)/page.tsx
+++ b/apps/web/src/app/(home)/page.tsx
@@ -119,13 +119,13 @@ export default function HomePage() {