From e77eda02202491906069c5b292566dedf3a1dd49 Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Tue, 10 Mar 2026 20:20:47 +0100 Subject: [PATCH] project page --- src/app/projects/page.tsx | 87 ++++++++++++++++++-- src/components/StackBadge.tsx | 149 ++++++++++++++++++++++++++++++++++ src/server/routers/project.ts | 10 ++- 3 files changed, 239 insertions(+), 7 deletions(-) create mode 100644 src/components/StackBadge.tsx diff --git a/src/app/projects/page.tsx b/src/app/projects/page.tsx index e5ed191..e42a7f0 100644 --- a/src/app/projects/page.tsx +++ b/src/app/projects/page.tsx @@ -1,12 +1,87 @@ 'use client' -import { usePathname } from "next/navigation" +import { trpc } from "~/app/_trpc/Client"; +import * as Card from "~/components/ui/card"; +import { Badge } from "~/components/ui/badge"; +import { StackBadge } from "~/components/StackBadge"; + +export default function ProjectsPage() { + const { data: projects, isLoading } = trpc.projectv2.listWithStack.useQuery(); + + if (isLoading) { + return ( +
+ Loading... +
+ ); + } + + if (!projects?.length) { + return ( +
+ No projects yet. +
+ ); + } -export default function Page() { - const pathName = usePathname() return ( -
- {pathName} +
+ {projects.map((project) => ( + + +
+ {project.title} +
+ {project.sourceType && ( + + {project.sourceType === "open" ? "Open Source" : "Closed Source"} + + )} + {project.releaseStatus && ( + + {project.releaseStatus === "released" ? "Released" : "Unreleased"} + + )} +
+
+
+ {(project.sourceLink || project.releaseLink || project.techStack?.stackItems?.length) && ( + + {(project.sourceLink || project.releaseLink) && ( +
+ {project.sourceLink && ( + + Source + + )} + {project.releaseLink && ( + + Live + + )} +
+ )} + {project.techStack?.stackItems && project.techStack.stackItems.length > 0 && ( +
+ {project.techStack.stackItems.map((item) => ( + + ))} +
+ )} +
+ )} +
+ ))}
- ) + ); } diff --git a/src/components/StackBadge.tsx b/src/components/StackBadge.tsx new file mode 100644 index 0000000..c0e143f --- /dev/null +++ b/src/components/StackBadge.tsx @@ -0,0 +1,149 @@ +import { Badge } from "~/components/ui/badge"; + +interface SvglIcon { + light: string; + dark: string; +} + +const STACK_META: Record = { + drizzle: { + label: "Drizzle ORM", + icon: { + light: "https://svgl.app/library/drizzle-orm_light.svg", + dark: "https://svgl.app/library/drizzle-orm_dark.svg", + }, + }, + postgres: { + label: "PostgreSQL", + icon: { + light: "https://svgl.app/library/postgresql.svg", + dark: "https://svgl.app/library/postgresql.svg", + }, + }, + nextjs: { + label: "Next.js", + icon: { + light: "https://svgl.app/library/nextjs_icon_dark.svg", + dark: "https://svgl.app/library/nextjs_icon_dark.svg", + }, + }, + react: { + label: "React", + icon: { + light: "https://svgl.app/library/react_light.svg", + dark: "https://svgl.app/library/react_dark.svg", + }, + }, + servercomponents: { label: "Server Components" }, + php: { + label: "PHP", + icon: { + light: "https://svgl.app/library/php.svg", + dark: "https://svgl.app/library/php_dark.svg", + }, + }, + laravel: { + label: "Laravel", + icon: { + light: "https://svgl.app/library/laravel.svg", + dark: "https://svgl.app/library/laravel.svg", + }, + }, + reactnative: { + label: "React Native", + icon: { + light: "https://svgl.app/library/react_light.svg", + dark: "https://svgl.app/library/react_dark.svg", + }, + }, + "react-native": { + label: "React Native", + icon: { + light: "https://svgl.app/library/react_light.svg", + dark: "https://svgl.app/library/react_dark.svg", + }, + }, + expo: { + label: "Expo", + icon: { + light: "https://svgl.app/library/expo.svg", + dark: "https://svgl.app/library/expo.svg", + }, + }, + mysql: { + label: "MySQL", + icon: { + light: "https://svgl.app/library/mysql-icon-light.svg", + dark: "https://svgl.app/library/mysql-icon-dark.svg", + }, + }, + nginx: { + label: "Nginx", + icon: { + light: "https://svgl.app/library/nginx.svg", + dark: "https://svgl.app/library/nginx.svg", + }, + }, + protobuf: { label: "Protobuf" }, + grpc: { label: "gRPC" }, + java: { + label: "Java", + icon: { + light: "https://svgl.app/library/java.svg", + dark: "https://svgl.app/library/java.svg", + }, + }, + graalvm: { label: "GraalVM" }, + spring: { + label: "Spring", + icon: { + light: "https://svgl.app/library/spring.svg", + dark: "https://svgl.app/library/spring.svg", + }, + }, + aws: { + label: "AWS", + icon: { + light: "https://svgl.app/library/aws_light.svg", + dark: "https://svgl.app/library/aws_dark.svg", + }, + }, + s3: { label: "Amazon S3" }, + linux: { + label: "Linux", + icon: { + light: "https://svgl.app/library/linux.svg", + dark: "https://svgl.app/library/linux.svg", + }, + }, + debian: { label: "Debian" }, + htmx: { label: "HTMX" }, +}; + +export function StackBadge({ item }: { item: string }) { + const meta = STACK_META[item] ?? { label: item }; + + return ( + + {meta.icon && ( + <> + + + + )} + {meta.label} + + ); +} diff --git a/src/server/routers/project.ts b/src/server/routers/project.ts index bd7fcc6..8f3604e 100644 --- a/src/server/routers/project.ts +++ b/src/server/routers/project.ts @@ -1,4 +1,12 @@ -import { router } from "~/server/trpc"; +import { publicProcedure, router } from "~/server/trpc"; +import { db } from "~/server/db"; export const projectRouter = router({ + listWithStack: publicProcedure.query(async () => { + return db.query.project.findMany({ + with: { + techStack: true, + }, + }); + }), });