project page
This commit is contained in:
@@ -1,12 +1,87 @@
|
|||||||
'use client'
|
'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 Page() {
|
export default function ProjectsPage() {
|
||||||
const pathName = usePathname()
|
const { data: projects, isLoading } = trpc.projectv2.listWithStack.useQuery();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex justify-center items-center min-h-[200px] text-muted-foreground">
|
||||||
{pathName}
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!projects?.length) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-[200px] text-muted-foreground">
|
||||||
|
No projects yet.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-4xl mx-auto px-4 py-8 flex flex-col gap-4">
|
||||||
|
{projects.map((project) => (
|
||||||
|
<Card.Card key={project.id}>
|
||||||
|
<Card.CardHeader>
|
||||||
|
<div className="flex items-start justify-between gap-2 flex-wrap">
|
||||||
|
<Card.CardTitle>{project.title}</Card.CardTitle>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{project.sourceType && (
|
||||||
|
<Badge variant={project.sourceType === "open" ? "secondary" : "outline"}>
|
||||||
|
{project.sourceType === "open" ? "Open Source" : "Closed Source"}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{project.releaseStatus && (
|
||||||
|
<Badge variant={project.releaseStatus === "released" ? "default" : "outline"}>
|
||||||
|
{project.releaseStatus === "released" ? "Released" : "Unreleased"}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card.CardHeader>
|
||||||
|
{(project.sourceLink || project.releaseLink || project.techStack?.stackItems?.length) && (
|
||||||
|
<Card.CardContent className="flex flex-col gap-3">
|
||||||
|
{(project.sourceLink || project.releaseLink) && (
|
||||||
|
<div className="flex gap-3 flex-wrap">
|
||||||
|
{project.sourceLink && (
|
||||||
|
<a
|
||||||
|
href={project.sourceLink}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4 transition-colors"
|
||||||
|
>
|
||||||
|
Source
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{project.releaseLink && (
|
||||||
|
<a
|
||||||
|
href={project.releaseLink}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4 transition-colors"
|
||||||
|
>
|
||||||
|
Live
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{project.techStack?.stackItems && project.techStack.stackItems.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{project.techStack.stackItems.map((item) => (
|
||||||
|
<StackBadge key={item} item={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card.CardContent>
|
||||||
|
)}
|
||||||
|
</Card.Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
149
src/components/StackBadge.tsx
Normal file
149
src/components/StackBadge.tsx
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
|
||||||
|
interface SvglIcon {
|
||||||
|
light: string;
|
||||||
|
dark: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STACK_META: Record<string, { label: string; icon?: SvglIcon }> = {
|
||||||
|
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 (
|
||||||
|
<Badge variant="outline">
|
||||||
|
{meta.icon && (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
src={meta.icon.light}
|
||||||
|
alt=""
|
||||||
|
width={12}
|
||||||
|
height={12}
|
||||||
|
className="dark:hidden shrink-0"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={meta.icon.dark}
|
||||||
|
alt=""
|
||||||
|
width={12}
|
||||||
|
height={12}
|
||||||
|
className="hidden dark:block shrink-0"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{meta.label}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,12 @@
|
|||||||
import { router } from "~/server/trpc";
|
import { publicProcedure, router } from "~/server/trpc";
|
||||||
|
import { db } from "~/server/db";
|
||||||
|
|
||||||
export const projectRouter = router({
|
export const projectRouter = router({
|
||||||
|
listWithStack: publicProcedure.query(async () => {
|
||||||
|
return db.query.project.findMany({
|
||||||
|
with: {
|
||||||
|
techStack: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user