mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
added react flow to show the flow of better-t-stack, add customization option and more
This commit is contained in:
60
apps/web/src/app/(home)/_components/CustomizableSection.tsx
Normal file
60
apps/web/src/app/(home)/_components/CustomizableSection.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { motion } from "framer-motion";
|
||||
import CustomizableStack from "./CustomizableStack";
|
||||
|
||||
export default function CustomizableSection() {
|
||||
return (
|
||||
<section className="w-full max-w-6xl mx-auto space-y-12 mt-24 relative">
|
||||
<div className="text-center space-y-6 relative z-10">
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-4xl md:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 pb-2"
|
||||
>
|
||||
Your Stack, Your Choice
|
||||
</motion.h2>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
className="space-y-4 max-w-3xl mx-auto"
|
||||
>
|
||||
<p className="text-xl text-gray-300 leading-relaxed">
|
||||
Better-T Stack comes with carefully selected defaults, but we
|
||||
understand one size doesn't fit all.
|
||||
<span className="text-purple-400 font-semibold">
|
||||
{" "}
|
||||
Customize your stack{" "}
|
||||
</span>
|
||||
while maintaining full type safety and integration.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-4 text-sm text-gray-400">
|
||||
<span className="px-3 py-1 bg-gray-800/50 rounded-full hover:bg-gray-800 transition-colors">
|
||||
Multiple Database Options
|
||||
</span>
|
||||
<span className="px-3 py-1 bg-gray-800/50 rounded-full hover:bg-gray-800 transition-colors">
|
||||
Flexible Authentication
|
||||
</span>
|
||||
<span className="px-3 py-1 bg-gray-800/50 rounded-full hover:bg-gray-800 transition-colors">
|
||||
Alternative ORMs
|
||||
</span>
|
||||
<span className="px-3 py-1 bg-gray-800/50 rounded-full hover:bg-gray-800 transition-colors">
|
||||
Framework Choices
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: 0.4 }}
|
||||
className="relative"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-500/10 via-purple-500/10 to-pink-500/10 blur-3xl" />
|
||||
<CustomizableStack />
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
177
apps/web/src/app/(home)/_components/CustomizableStack.tsx
Normal file
177
apps/web/src/app/(home)/_components/CustomizableStack.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Background,
|
||||
type Connection,
|
||||
type Edge,
|
||||
ReactFlow,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
} from "@xyflow/react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { TechSelector } from "./TechSelector";
|
||||
import "@xyflow/react/dist/style.css";
|
||||
import { initialNodes } from "@/lib/constant";
|
||||
import { TechNodeComponent } from "./TechNodeComponent";
|
||||
|
||||
const initialEdges = [
|
||||
{ id: "bun-hono", source: "bun", target: "hono", animated: true },
|
||||
{ id: "bun-tanstack", source: "bun", target: "tanstack", animated: true },
|
||||
{ id: "hono-libsql", source: "hono", target: "libsql", animated: true },
|
||||
{ id: "libsql-drizzle", source: "libsql", target: "drizzle", animated: true },
|
||||
{
|
||||
id: "hono-better-auth",
|
||||
source: "hono",
|
||||
target: "better-auth",
|
||||
animated: true,
|
||||
},
|
||||
{ id: "bun-tailwind", source: "bun", target: "tailwind", animated: true },
|
||||
{
|
||||
id: "tailwind-shadcn",
|
||||
source: "tailwind",
|
||||
target: "shadcn",
|
||||
animated: true,
|
||||
},
|
||||
];
|
||||
|
||||
const nodeTypes = {
|
||||
techNode: TechNodeComponent,
|
||||
};
|
||||
|
||||
const CustomizableStack = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const [activeNodes, setActiveNodes] = useState({
|
||||
backend: "hono",
|
||||
database: "libsql",
|
||||
orm: "drizzle",
|
||||
auth: "better-auth",
|
||||
});
|
||||
|
||||
const handleTechSelect = useCallback(
|
||||
(category: string, techId: string) => {
|
||||
setActiveNodes((prev) => ({ ...prev, [category]: techId }));
|
||||
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => ({
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
isActive: node.data.isStatic
|
||||
? true
|
||||
: node.data.category === category
|
||||
? node.id === techId
|
||||
: node.data.isActive,
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
const sourceNodes = nodes.filter(
|
||||
(n) => n.data.isActive && !n.data.isStatic,
|
||||
);
|
||||
const targetNode = nodes.find((n) => n.id === techId);
|
||||
|
||||
if (!targetNode) return;
|
||||
|
||||
setEdges((eds) =>
|
||||
eds.filter(
|
||||
(edge) =>
|
||||
!nodes.some(
|
||||
(n) =>
|
||||
n.data.category === category &&
|
||||
(edge.source === n.id || edge.target === n.id),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
setEdges((eds) => [
|
||||
...eds,
|
||||
...sourceNodes.map((source) => ({
|
||||
id: `${source.id}-${techId}`,
|
||||
source: source.id,
|
||||
target: techId,
|
||||
animated: true,
|
||||
})),
|
||||
]);
|
||||
},
|
||||
[nodes, setNodes, setEdges],
|
||||
);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
const sourceNode = nodes.find((n) => n.id === connection.source);
|
||||
const targetNode = nodes.find((n) => n.id === connection.target);
|
||||
|
||||
if (!sourceNode || !targetNode) return;
|
||||
|
||||
if (sourceNode.data.group === targetNode.data.group) {
|
||||
return;
|
||||
}
|
||||
|
||||
const edgesToRemove = edges.filter((edge) => {
|
||||
const edgeTarget = nodes.find((n) => n.id === edge.target);
|
||||
return edgeTarget?.data.group === targetNode.data.group;
|
||||
});
|
||||
|
||||
setEdges((eds) => {
|
||||
const newEdges = eds.filter((edge) => !edgesToRemove.includes(edge));
|
||||
return [...newEdges, { ...(connection as Edge), animated: true }];
|
||||
});
|
||||
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => ({
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
isActive:
|
||||
node.id === connection.source || node.id === connection.target
|
||||
? true
|
||||
: node.data.group !== targetNode.data.group
|
||||
? node.data.isActive
|
||||
: false,
|
||||
},
|
||||
})),
|
||||
);
|
||||
},
|
||||
[nodes, edges, setEdges, setNodes],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative w-full max-w-5xl mx-auto z-50">
|
||||
<TechSelector onSelect={handleTechSelect} activeNodes={activeNodes} />
|
||||
<div className="absolute top-4 right-4 z-50 max-w-xs bg-gray-950/10 p-4 rounded-xl border border-gray-800 backdrop-blur-3xl">
|
||||
<div className="text-sm text-gray-300">
|
||||
Select technologies from the left panel to customize your stack. The
|
||||
graph will automatically update connections.
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-0 backdrop-blur-3xl bg-gradient-to-r from-blue-500/10 via-purple-500/10 to-pink-500/10 rounded-xl" />
|
||||
<div className="h-[600px] relative backdrop-blur-sm bg-gray-950/50 rounded-xl overflow-hidden border border-gray-800">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
nodeTypes={nodeTypes}
|
||||
fitView
|
||||
minZoom={1}
|
||||
maxZoom={1}
|
||||
zoomOnScroll={false}
|
||||
zoomOnPinch={false}
|
||||
preventScrolling={false}
|
||||
nodesConnectable={true}
|
||||
>
|
||||
<Background
|
||||
className="bg-gray-950/5"
|
||||
color="#1e293b"
|
||||
gap={12}
|
||||
size={1}
|
||||
/>
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomizableStack;
|
||||
@@ -56,7 +56,7 @@ const Navbar = () => {
|
||||
|
||||
<div
|
||||
className={`flex items-center backdrop-blur-md bg-white/5 rounded-full border border-white/10 py-1 px-1.5 text-sm relative transition-all duration-500 ease-out ${
|
||||
scrolled ? "w-[475px]" : "w-[335px]"
|
||||
scrolled ? "w-[480px]" : "w-[335px]"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
@@ -126,7 +126,7 @@ const Navbar = () => {
|
||||
style={{
|
||||
transform: scrolled ? "translateY(0)" : "translateY(-8px)",
|
||||
}}
|
||||
className={`text-gray-300 hover:text-white transition-all duration-300 py-2 px-4 rounded-full ${
|
||||
className={`text-violet-700 hover:text-white transition-all duration-300 py-2 px-4 rounded-full ${
|
||||
scrolled
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 pointer-events-none"
|
||||
|
||||
@@ -2,14 +2,14 @@ const SideCircles = () => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="h-[40vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-50 fixed top-1/2 -translate-y-1/2 -left-[30%]" />
|
||||
<div className="h-[50vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-40 fixed top-1/2 -translate-y-1/2 -left-[25%]" />
|
||||
<div className="h-[60vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-30 fixed top-1/2 -translate-y-1/2 -left-[20%]" />
|
||||
<div className="h-[50vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-40 fixed top-1/2 -translate-y-1/2 -left-[30%]" />
|
||||
<div className="h-[40vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-50 fixed top-1/2 -translate-y-1/2 -left-[35%]" />
|
||||
<div className="h-[60vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-30 fixed top-1/2 -translate-y-1/2 -left-[25%]" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="h-[40vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-50 fixed top-1/2 -translate-y-1/2 -right-[30%]" />
|
||||
<div className="h-[50vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-40 fixed top-1/2 -translate-y-1/2 -right-[25%]" />
|
||||
<div className="h-[60vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-30 fixed top-1/2 -translate-y-1/2 -right-[20%]" />
|
||||
<div className="h-[40vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-50 fixed top-1/2 -translate-y-1/2 -right-[35%]" />
|
||||
<div className="h-[50vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-40 fixed top-1/2 -translate-y-1/2 -right-[30%]" />
|
||||
<div className="h-[60vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-30 fixed top-1/2 -translate-y-1/2 -right-[25%]" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -153,7 +153,7 @@ const TechConstellation = () => {
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative w-full h-[90vh] bg-gradient-to-b from-transparent via-gray-950 to-transparent overflow-hidden flex items-center justify-center"
|
||||
className="relative w-full h-[90vh] bg-gradient-to-b from-transparent mt-8 via-gray-950 to-transparent overflow-hidden flex items-center justify-center"
|
||||
>
|
||||
<div
|
||||
ref={centerRef}
|
||||
|
||||
63
apps/web/src/app/(home)/_components/TechNodeComponent.tsx
Normal file
63
apps/web/src/app/(home)/_components/TechNodeComponent.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Handle, Position } from "@xyflow/react";
|
||||
|
||||
const categoryIndicators = {
|
||||
core: "before:bg-amber-500",
|
||||
frontend: "before:bg-blue-500",
|
||||
backend: "before:bg-emerald-500",
|
||||
database: "before:bg-purple-500",
|
||||
auth: "before:bg-red-500",
|
||||
orm: "before:bg-orange-500",
|
||||
};
|
||||
|
||||
interface TechNodeData {
|
||||
category: keyof typeof categoryIndicators;
|
||||
isActive: boolean;
|
||||
label: string;
|
||||
description: string;
|
||||
isDefault?: boolean;
|
||||
isStatic?: boolean;
|
||||
}
|
||||
|
||||
export function TechNodeComponent({ data }: { data: TechNodeData }) {
|
||||
const baseStyles = `
|
||||
relative px-5 py-4 rounded-lg
|
||||
transition-all duration-300
|
||||
border border-white/20
|
||||
before:content-[''] before:absolute before:left-0 before:top-0 before:w-1.5 before:h-full
|
||||
before:rounded-l-xl ${categoryIndicators[data.category]}
|
||||
`;
|
||||
|
||||
const activeStyles = data.isActive
|
||||
? "opacity-100 bg-gradient-to-br from-indigo-900/10 to-violet-900/10"
|
||||
: "opacity-80 hover:opacity-95 bg-slate-800";
|
||||
|
||||
return (
|
||||
<div className="relative group">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
className="!w-1.5 !h-1.5 !bg-indigo-400/70"
|
||||
/>
|
||||
|
||||
<div className={`${baseStyles} ${activeStyles} backdrop-blur-3xl`}>
|
||||
<div className="text-white font-medium text-sm tracking-wide mb-1.5">
|
||||
{data.label}
|
||||
</div>
|
||||
<div className="text-[11px] leading-relaxed text-white/80">
|
||||
{data.description}
|
||||
</div>
|
||||
{!data.isDefault && !data.isStatic && (
|
||||
<div className="text-[10px] text-indigo-200/70 mt-2 italic">
|
||||
Alternative Option
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
className="!w-1.5 !h-1.5 !bg-indigo-400/70"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
81
apps/web/src/app/(home)/_components/TechSelector.tsx
Normal file
81
apps/web/src/app/(home)/_components/TechSelector.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
type TechOption = {
|
||||
id: string;
|
||||
label: string;
|
||||
category: string;
|
||||
};
|
||||
|
||||
const techOptions: Record<string, TechOption[]> = {
|
||||
backend: [
|
||||
{ id: "hono", label: "Hono", category: "backend" },
|
||||
{ id: "express", label: "Express", category: "backend" },
|
||||
],
|
||||
database: [
|
||||
{ id: "libsql", label: "libSQL", category: "database" },
|
||||
{ id: "postgres", label: "PostgreSQL", category: "database" },
|
||||
],
|
||||
orm: [
|
||||
{ id: "drizzle", label: "Drizzle", category: "orm" },
|
||||
{ id: "prisma", label: "Prisma", category: "orm" },
|
||||
],
|
||||
auth: [
|
||||
{ id: "better-auth", label: "Better-Auth", category: "auth" },
|
||||
{ id: "no-auth", label: "No Auth", category: "auth" },
|
||||
],
|
||||
};
|
||||
|
||||
interface TechSelectorProps {
|
||||
onSelect: (category: string, techId: string) => void;
|
||||
activeNodes: Record<string, string>;
|
||||
}
|
||||
|
||||
export function TechSelector({ onSelect, activeNodes }: TechSelectorProps) {
|
||||
return (
|
||||
<div className="absolute top-4 left-4 z-50 space-y-4 bg-gray-950/10 p-4 rounded-xl border border-gray-800 backdrop-blur-3xl">
|
||||
<div className="text-sm font-medium text-gray-200">Customize Stack</div>
|
||||
{Object.entries(techOptions).map(([category, options]) => (
|
||||
<div key={category} className="space-y-2">
|
||||
<div className="text-xs text-gray-400 capitalize">{category}</div>
|
||||
<div className="flex gap-2">
|
||||
{options.map((option) => (
|
||||
<Badge
|
||||
key={option.id}
|
||||
variant="secondary"
|
||||
className={`cursor-pointer hover:bg-gray-700 ${
|
||||
activeNodes[category] === option.id &&
|
||||
"bg-blue-600 text-white"
|
||||
}`}
|
||||
onClick={() => onSelect(category, option.id)}
|
||||
>
|
||||
{option.label}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Badge = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
variant: "primary" | "secondary";
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<span
|
||||
className={`
|
||||
px-2 rounded-full py-1 text-xs font-medium,
|
||||
${className}
|
||||
`}
|
||||
{...props}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,8 @@ import { Poppins } from "next/font/google";
|
||||
import React from "react";
|
||||
import BackgroundGradients from "./_components/BackgroundGradients";
|
||||
import CodeContainer from "./_components/CodeContainer";
|
||||
import Featured from "./_components/FeaturedSection";
|
||||
import CustomizableSection from "./_components/CustomizableSection";
|
||||
// import Featured from "./_components/FeaturedSection";
|
||||
import NpmPackage from "./_components/NpmPackage";
|
||||
import SideCircles from "./_components/SideCircles";
|
||||
import Spotlight from "./_components/Spotlight";
|
||||
@@ -62,6 +63,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
</div>
|
||||
<TerminalDisplay />
|
||||
|
||||
<div className="w-full max-w-6xl mx-auto space-y-12 mt-12">
|
||||
<div className="text-center space-y-6 relative z-10">
|
||||
<h2 className="text-4xl md:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-slate-700 via-gray-100 to-stone-600 pb-2">
|
||||
@@ -90,9 +92,9 @@ export default function HomePage() {
|
||||
</div>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-gray-900 via-gray-900/50 to-transparent -z-10 blur-xl" />
|
||||
</div>
|
||||
<TechConstellation />
|
||||
</div>
|
||||
<Featured />
|
||||
<TechConstellation />
|
||||
<CustomizableSection />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,3 +52,16 @@
|
||||
opacity: 0;
|
||||
animation: fadeInUp 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
.border-beam {
|
||||
animation: border-beam 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes border-beam {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ServerCog,
|
||||
Workflow,
|
||||
} from "lucide-react";
|
||||
import type { TechNode } from "./types";
|
||||
|
||||
export const technologies = [
|
||||
{
|
||||
@@ -103,3 +104,161 @@ export const technologies = [
|
||||
left: "left-[6rem]",
|
||||
},
|
||||
];
|
||||
|
||||
export const initialNodes: TechNode[] = [
|
||||
{
|
||||
id: "bun",
|
||||
type: "techNode",
|
||||
position: { x: 536, y: 204 },
|
||||
data: {
|
||||
label: "Bun",
|
||||
category: "core",
|
||||
description: "Fast all-in-one JavaScript runtime",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tanstack",
|
||||
type: "techNode",
|
||||
position: { x: 362, y: 296 },
|
||||
data: {
|
||||
label: "TanStack Router",
|
||||
category: "frontend",
|
||||
description: "Type-safe routing",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
group: "router",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tailwind",
|
||||
type: "techNode",
|
||||
position: { x: 494, y: 379 },
|
||||
data: {
|
||||
label: "Tailwind CSS",
|
||||
category: "frontend",
|
||||
description: "Utility-first CSS framework",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
isStatic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "shadcn",
|
||||
type: "techNode",
|
||||
position: { x: 358, y: 486 },
|
||||
data: {
|
||||
label: "shadcn/ui",
|
||||
category: "frontend",
|
||||
description: "Re-usable components",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
isStatic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "hono",
|
||||
type: "techNode",
|
||||
position: { x: 700, y: 325 },
|
||||
data: {
|
||||
label: "Hono",
|
||||
category: "backend",
|
||||
description: "Ultrafast web framework",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
group: "backend",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "express",
|
||||
type: "techNode",
|
||||
position: { x: 897, y: 389 },
|
||||
data: {
|
||||
label: "Express",
|
||||
category: "backend",
|
||||
description: "Fast, unopinionated web framework",
|
||||
isDefault: false,
|
||||
isActive: false,
|
||||
group: "backend",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "libsql",
|
||||
type: "techNode",
|
||||
position: { x: 544, y: 532 },
|
||||
data: {
|
||||
label: "libSQL",
|
||||
category: "database",
|
||||
description: "SQLite-compatible database",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
group: "database",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "postgres",
|
||||
type: "techNode",
|
||||
position: { x: 318, y: 579 },
|
||||
data: {
|
||||
label: "PostgreSQL",
|
||||
category: "database",
|
||||
description: "Advanced SQL database",
|
||||
isDefault: false,
|
||||
isActive: false,
|
||||
group: "database",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "drizzle",
|
||||
type: "techNode",
|
||||
position: { x: 559, y: 651 },
|
||||
data: {
|
||||
label: "Drizzle",
|
||||
category: "orm",
|
||||
description: "TypeScript ORM",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
group: "orm",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "prisma",
|
||||
type: "techNode",
|
||||
position: { x: 707, y: 675 },
|
||||
data: {
|
||||
label: "Prisma",
|
||||
category: "orm",
|
||||
description: "Next-generation ORM",
|
||||
isDefault: false,
|
||||
isActive: false,
|
||||
group: "orm",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "better-auth",
|
||||
type: "techNode",
|
||||
position: { x: 770, y: 502 },
|
||||
data: {
|
||||
label: "Better-Auth",
|
||||
category: "auth",
|
||||
description: "Modern authentication",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
group: "auth",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "no-auth",
|
||||
type: "techNode",
|
||||
position: { x: 950, y: 621 },
|
||||
data: {
|
||||
label: "No Auth",
|
||||
category: "auth",
|
||||
description: "No authentication needed",
|
||||
isDefault: false,
|
||||
isActive: false,
|
||||
group: "auth",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
32
apps/web/src/lib/types.ts
Normal file
32
apps/web/src/lib/types.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export type TechCategory =
|
||||
| "core"
|
||||
| "frontend"
|
||||
| "backend"
|
||||
| "database"
|
||||
| "auth"
|
||||
| "orm"
|
||||
| "router";
|
||||
|
||||
export interface TechNode {
|
||||
id: string;
|
||||
type: string;
|
||||
position: { x: number; y: number };
|
||||
data: {
|
||||
label: string;
|
||||
category: TechCategory;
|
||||
description: string;
|
||||
isDefault: boolean;
|
||||
alternatives?: string[];
|
||||
isActive: boolean;
|
||||
group?: TechCategory;
|
||||
isStatic?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TechEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
type?: string;
|
||||
animated?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user