protofolio / frontend /src /sections /Projects /ProjectsSection.tsx
moustafa7's picture
Fix: Project card skeleton matching new card design with logo circle
7a6a641
Raw History Blame Contribute Delete
4.93 kB
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import { projectsService } from "@/services/projects.service";
import { ProjectCard } from "@/components/cards/ProjectCard";
import { ProjectModal } from "@/components/modals/ProjectModal";
import { FadeIn } from "@/components/animations/FadeIn";
import { Skeleton } from "@/components/common/Skeleton";
import type { Project } from "@/types";
import { Search } from "lucide-react";
export function ProjectsSection() {
const { t } = useTranslation();
const [search, setSearch] = useState("");
const [category, setCategory] = useState("");
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
const { data, isLoading } = useQuery({
queryKey: ["projects", search, category],
queryFn: () => projectsService.getAll({ search, category }),
});
const projects: Project[] = data?.data?.data || [];
const categories = [...new Set(projects.map((p) => p.categoryEn))];
return (
<section id="projects" className="py-20">
<div className="max-w-7xl mx-auto px-4">
<FadeIn>
<h2 className="text-3xl font-bold text-center text-slate-900 dark:text-white mb-8">{t("projects.title")}</h2>
</FadeIn>
<FadeIn>
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-8">
<div className="relative max-w-md w-full mx-auto sm:mx-0">
<Search className="absolute left-3 top-3 w-4 h-4 text-slate-400" />
<input
type="text"
placeholder={t("projects.search")}
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full pl-10 pr-4 py-2 rounded-lg border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none"
/>
</div>
<div className="flex gap-2 justify-center flex-wrap">
<button
onClick={() => setCategory("")}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
!category ? "bg-blue-600 text-white" : "bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700"
}`}
>
{t("projects.all")}
</button>
{categories.map((cat) => (
<button
key={cat}
onClick={() => setCategory(cat)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
category === cat ? "bg-blue-600 text-white" : "bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700"
}`}
>
{cat}
</button>
))}
</div>
</div>
</FadeIn>
{isLoading ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="bg-white dark:bg-slate-900 rounded-2xl border border-slate-200 dark:border-slate-800 overflow-hidden">
<div className="relative h-56 bg-slate-200 dark:bg-slate-800 animate-pulse">
<div className="absolute top-4 right-4 w-12 h-12 rounded-full bg-slate-300 dark:bg-slate-700" />
</div>
<div className="p-5 space-y-3">
<div className="h-5 w-20 bg-slate-200 dark:bg-slate-800 rounded-full" />
<div className="h-5 w-3/4 bg-slate-200 dark:bg-slate-800 rounded" />
<div className="space-y-2">
<div className="h-3 w-full bg-slate-200 dark:bg-slate-800 rounded" />
<div className="h-3 w-2/3 bg-slate-200 dark:bg-slate-800 rounded" />
</div>
<div className="flex gap-2 pt-1">
<div className="h-6 w-16 bg-slate-200 dark:bg-slate-800 rounded" />
<div className="h-6 w-16 bg-slate-200 dark:bg-slate-800 rounded" />
</div>
</div>
</div>
))}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{projects.map((project, i) => (
<FadeIn key={project.id} delay={i * 0.1}>
<ProjectCard project={project} onClick={() => setSelectedProject(project)} />
</FadeIn>
))}
</div>
)}
<ProjectModal project={selectedProject} open={!!selectedProject} onClose={() => setSelectedProject(null)} />
</div>
</section>
);
}