Spaces:
Sleeping
Sleeping
Download frontend/src/sections/Projects/ProjectsSection.tsx from moustafa7/protofolio: direct link, hf CLI and curl.
- Browser
- Download file 4.93 kB
-
https://huggingface.co/spaces/moustafa7/protofolio/resolve/main/frontend/src/sections/Projects/ProjectsSection.tsx
- Command line
-
hf download hf://spaces/moustafa7/protofolio/frontend/src/sections/Projects/ProjectsSection.tsx
-
curl -L -o ProjectsSection.tsx https://huggingface.co/spaces/moustafa7/protofolio/resolve/main/frontend/src/sections/Projects/ProjectsSection.tsx
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> | |
| ); | |
| } |