feat: implement projects section with category filtering and database integration
This commit is contained in:
@@ -5,8 +5,14 @@ import { ExperienceSection } from "@/features/experience/experience-section";
|
|||||||
import { TechStackSection } from "@/features/skills/tech-stack-section";
|
import { TechStackSection } from "@/features/skills/tech-stack-section";
|
||||||
import { ProjectsSection } from "@/features/projects/projects-section";
|
import { ProjectsSection } from "@/features/projects/projects-section";
|
||||||
import { ContactSection } from "@/features/messages/contact-section";
|
import { ContactSection } from "@/features/messages/contact-section";
|
||||||
|
import { prisma } from "@/core/db/prisma";
|
||||||
|
|
||||||
|
export default async function HomePage() {
|
||||||
|
const publishedProjects = await prisma.project.findMany({
|
||||||
|
where: { isPublished: true },
|
||||||
|
orderBy: { createdAt: "desc" }
|
||||||
|
});
|
||||||
|
|
||||||
export default function HomePage() {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
@@ -14,7 +20,7 @@ export default function HomePage() {
|
|||||||
<HeroSection />
|
<HeroSection />
|
||||||
<ExperienceSection />
|
<ExperienceSection />
|
||||||
<TechStackSection />
|
<TechStackSection />
|
||||||
<ProjectsSection />
|
<ProjectsSection initialProjects={publishedProjects} />
|
||||||
<ContactSection />
|
<ContactSection />
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|||||||
@@ -15,66 +15,18 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
export function ProjectsSection() {
|
export function ProjectsSection({ initialProjects }: { initialProjects?: any[] }) {
|
||||||
const t = useTranslations("Projects");
|
const t = useTranslations("Projects");
|
||||||
const [activeFilter, setActiveFilter] = useState("all");
|
const [activeFilter, setActiveFilter] = useState("all");
|
||||||
|
|
||||||
const projects = [
|
// Use initialProjects from DB or fallback to empty array
|
||||||
{
|
const projects = initialProjects || [];
|
||||||
id: "1",
|
|
||||||
title: t("items.apiGateway.title"),
|
|
||||||
description: t("items.apiGateway.description"),
|
|
||||||
category: "backend",
|
|
||||||
tags: ["Spring Boot", "Kafka", "Redis", "Docker"],
|
|
||||||
metrics: t.has("items.apiGateway.metrics") ? t("items.apiGateway.metrics") : undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
title: t("items.paymentEngine.title"),
|
|
||||||
description: t("items.paymentEngine.description"),
|
|
||||||
category: "backend",
|
|
||||||
tags: ["Java", "Kafka", "PostgreSQL", "gRPC"],
|
|
||||||
metrics: t.has("items.paymentEngine.metrics") ? t("items.paymentEngine.metrics") : undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
title: t("items.onboarding.title"),
|
|
||||||
description: t("items.onboarding.description"),
|
|
||||||
category: "frontend",
|
|
||||||
tags: ["React", "Next.js", "TypeScript", "Tailwind"],
|
|
||||||
repoUrl: "#",
|
|
||||||
liveUrl: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "4",
|
|
||||||
title: t("items.dashboard.title"),
|
|
||||||
description: t("items.dashboard.description"),
|
|
||||||
category: "frontend",
|
|
||||||
tags: ["React", "Chart.js", "WebSocket", "REST"],
|
|
||||||
repoUrl: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "5",
|
|
||||||
title: t("items.mobileApp.title"),
|
|
||||||
description: t("items.mobileApp.description"),
|
|
||||||
category: "mobile",
|
|
||||||
tags: ["React Native", "TypeScript", "Redux"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "6",
|
|
||||||
title: t("items.authService.title"),
|
|
||||||
description: t("items.authService.description"),
|
|
||||||
category: "backend",
|
|
||||||
tags: ["Spring Security", "OAuth2", "JWT", "Redis"],
|
|
||||||
metrics: t.has("items.authService.metrics") ? t("items.authService.metrics") : undefined,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const filters = [
|
const filters = [
|
||||||
{ value: "all", label: t("filters.all"), icon: <Layers size={16} /> },
|
{ value: "all", label: t("filters.all"), icon: <Layers size={16} /> },
|
||||||
{ value: "backend", label: t("filters.backend"), icon: <Server size={16} /> },
|
{ value: "Enterprise Backend", label: "Backend", icon: <Server size={16} /> },
|
||||||
{ value: "frontend", label: t("filters.frontend"), icon: <Globe size={16} /> },
|
{ value: "Frontend Development", label: "Frontend", icon: <Globe size={16} /> },
|
||||||
{ value: "mobile", label: t("filters.mobile"), icon: <Smartphone size={16} /> },
|
{ value: "Mobile Development", label: "Mobile", icon: <Smartphone size={16} /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
const filteredProjects =
|
const filteredProjects =
|
||||||
@@ -131,21 +83,21 @@ export function ProjectsSection() {
|
|||||||
{/* Category badge */}
|
{/* Category badge */}
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-[10px] font-mono font-semibold uppercase tracking-wider ${project.category === "backend"
|
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-[10px] font-mono font-semibold uppercase tracking-wider ${project.category === "Enterprise Backend"
|
||||||
? "bg-blue-500/10 text-blue-500 dark:text-blue-400"
|
? "bg-blue-500/10 text-blue-500 dark:text-blue-400"
|
||||||
: project.category === "frontend"
|
: project.category === "Frontend Development"
|
||||||
? "bg-violet-500/10 text-violet-500 dark:text-violet-400"
|
? "bg-violet-500/10 text-violet-500 dark:text-violet-400"
|
||||||
: "bg-orange-500/10 text-orange-500 dark:text-orange-400"
|
: "bg-orange-500/10 text-orange-500 dark:text-orange-400"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{project.category === "backend" ? (
|
{project.category === "Enterprise Backend" ? (
|
||||||
<Server size={12} />
|
<Server size={12} />
|
||||||
) : project.category === "frontend" ? (
|
) : project.category === "Frontend Development" ? (
|
||||||
<Globe size={12} />
|
<Globe size={12} />
|
||||||
) : (
|
) : (
|
||||||
<Smartphone size={12} />
|
<Smartphone size={12} />
|
||||||
)}
|
)}
|
||||||
{project.category}
|
{project.category.replace(" Development", "").replace("Enterprise ", "")}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
{project.repoUrl && (
|
{project.repoUrl && (
|
||||||
@@ -169,6 +121,17 @@ export function ProjectsSection() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Image banner */}
|
||||||
|
{project.imageUrl && (
|
||||||
|
<div className="w-full h-40 mb-4 rounded-xl overflow-hidden bg-muted/20 relative group-hover:shadow-md transition-shadow">
|
||||||
|
<img
|
||||||
|
src={project.imageUrl}
|
||||||
|
alt={project.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Title & description */}
|
{/* Title & description */}
|
||||||
<h3 className="text-lg font-bold mb-2 group-hover:text-accent transition-colors">
|
<h3 className="text-lg font-bold mb-2 group-hover:text-accent transition-colors">
|
||||||
{project.title}
|
{project.title}
|
||||||
@@ -187,9 +150,9 @@ export function ProjectsSection() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Tags */}
|
{/* Tags (Using generic category slice due to PRISMA missing tags yet) */}
|
||||||
<div className="flex flex-wrap gap-1.5 mt-auto">
|
<div className="flex flex-wrap gap-1.5 mt-auto">
|
||||||
{project.tags.map((tag) => (
|
{(project.tags || [project.category.split(" ")[0]]).map((tag: string) => (
|
||||||
<span
|
<span
|
||||||
key={tag}
|
key={tag}
|
||||||
className="px-2 py-1 rounded-md text-[10px] font-mono bg-muted/50 text-muted-foreground border border-border/30"
|
className="px-2 py-1 rounded-md text-[10px] font-mono bg-muted/50 text-muted-foreground border border-border/30"
|
||||||
|
|||||||
Reference in New Issue
Block a user