diff --git a/app/components/Projects.tsx b/app/components/Projects.tsx index dec2ab4..f5b4585 100644 --- a/app/components/Projects.tsx +++ b/app/components/Projects.tsx @@ -114,11 +114,13 @@ const Projects = () => { className="object-cover transition-transform duration-1000 ease-out group-hover:scale-105" /> ) : ( -
-
- +
+
+ + {project.title.charAt(0)} +
-
+
)} diff --git a/app/projects/[slug]/page.tsx b/app/projects/[slug]/page.tsx index 1132ed0..d5b0930 100644 --- a/app/projects/[slug]/page.tsx +++ b/app/projects/[slug]/page.tsx @@ -1,11 +1,12 @@ "use client"; import { motion } from 'framer-motion'; -import { ExternalLink, Calendar, Tag, ArrowLeft, Github as GithubIcon } from 'lucide-react'; +import { ExternalLink, Calendar, Tag, ArrowLeft, Github as GithubIcon, Share2 } from 'lucide-react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { useState, useEffect } from 'react'; import ReactMarkdown from 'react-markdown'; +import Image from 'next/image'; interface Project { id: number; @@ -18,6 +19,7 @@ interface Project { date: string; github?: string; live?: string; + imageUrl?: string; } const ProjectDetail = () => { @@ -48,142 +50,182 @@ const ProjectDetail = () => { if (!project) { return ( -
+
-
-

Loading project...

+
+

Loading project...

); } return ( -
-
- {/* Header */} +
+
+ {/* Navigation */} - - Back to Projects + + Back to Projects - -
-

- {project.title} -

- {project.featured && ( - - Featured - - )} -
- -

- {project.description} -

- - {/* Project Meta */} -
-
- - {project.date} -
-
- - {project.category} -
-
- - {/* Tags */} -
- {project.tags.map((tag) => ( - - {tag} - - ))} -
- - {/* Action Buttons */} - {((project.github && project.github.trim() && project.github !== "#") || (project.live && project.live.trim() && project.live !== "#")) && ( -
- {project.github && project.github.trim() && project.github !== "#" && ( - - - View Code - - )} - - {project.live && project.live.trim() && project.live !== "#" && ( - - - Live Demo - - )} -
- )}
- {/* Project Content */} + {/* Header & Meta */} -
-

{children}

, - h2: ({children}) =>

{children}

, - h3: ({children}) =>

{children}

, - p: ({children}) =>

{children}

, - ul: ({children}) =>
    {children}
, - ol: ({children}) =>
    {children}
, - li: ({children}) =>
  • {children}
  • , - a: ({href, children}) => ( - - {children} - - ), - code: ({children}) => {children}, - pre: ({children}) =>
    {children}
    , - blockquote: ({children}) =>
    {children}
    , - strong: ({children}) => {children}, - em: ({children}) => {children} - }} - > - {project.content} -
    +
    +

    + {project.title} +

    +
    + {project.featured && ( + + Featured + + )} + + {project.category} + +
    +
    + +

    + {project.description} +

    + +
    +
    + + {new Date(project.date).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })} +
    +
    +
    + {project.tags.map(tag => ( + #{tag} + ))} +
    + + {/* Featured Image / Fallback */} + + {project.imageUrl ? ( + {project.title} + ) : ( +
    + + {project.title.charAt(0)} + +
    + )} +
    + + + {/* Content & Sidebar Layout */} +
    + {/* Main Content */} + +
    +

    {children}

    , + h2: ({children}) =>

    {children}

    , + p: ({children}) =>

    {children}

    , + li: ({children}) =>
  • {children}
  • , + code: ({children}) => {children}, + pre: ({children}) =>
    {children}
    , + }} + > + {project.content} +
    +
    +
    + + {/* Sidebar / Actions */} + +
    +

    + + Project Links +

    +
    + {project.live && project.live.trim() && project.live !== "#" ? ( + + Live Demo + + + ) : ( +
    + Live demo not available +
    + )} + + {project.github && project.github.trim() && project.github !== "#" ? ( + + View Source + + + ) : null} +
    + +
    +

    Tech Stack

    +
    + {project.tags.map(tag => ( + + {tag} + + ))} +
    +
    +
    +
    +
    ); }; -export default ProjectDetail; +export default ProjectDetail; \ No newline at end of file diff --git a/app/projects/page.tsx b/app/projects/page.tsx index d0d1adc..1b0290e 100644 --- a/app/projects/page.tsx +++ b/app/projects/page.tsx @@ -1,9 +1,8 @@ "use client"; import { useState, useEffect } from "react"; - import { motion } from 'framer-motion'; -import { ExternalLink, Github, Calendar, ArrowLeft } from 'lucide-react'; +import { ExternalLink, Github, Calendar, ArrowLeft, Search } from 'lucide-react'; import Link from 'next/link'; interface Project { @@ -17,10 +16,16 @@ interface Project { date: string; github?: string; live?: string; + imageUrl?: string; } const ProjectsPage = () => { const [projects, setProjects] = useState([]); + const [filteredProjects, setFilteredProjects] = useState([]); + const [categories, setCategories] = useState(["All"]); + const [selectedCategory, setSelectedCategory] = useState("All"); + const [searchQuery, setSearchQuery] = useState(""); + const [mounted, setMounted] = useState(false); // Load projects from API useEffect(() => { @@ -29,7 +34,12 @@ const ProjectsPage = () => { const response = await fetch('/api/projects?published=true'); if (response.ok) { const data = await response.json(); - setProjects(data.projects || []); + const loadedProjects = data.projects || []; + setProjects(loadedProjects); + + // Extract unique categories + const uniqueCategories = ["All", ...Array.from(new Set(loadedProjects.map((p: Project) => p.category))) as string[]]; + setCategories(uniqueCategories); } } catch (error) { if (process.env.NODE_ENV === 'development') { @@ -39,31 +49,36 @@ const ProjectsPage = () => { }; loadProjects(); - }, []); - - const categories = ["All", "Web Development", "Full-Stack", "Web Application", "Mobile App"]; - const [selectedCategory, setSelectedCategory] = useState("All"); - const [mounted, setMounted] = useState(false); - - useEffect(() => { setMounted(true); }, []); - if (!mounted) { - return null; - } + // Filter projects + useEffect(() => { + let result = projects; - const filteredProjects = selectedCategory === "All" - ? projects - : projects.filter(project => project.category === selectedCategory); + if (selectedCategory !== "All") { + result = result.filter(project => project.category === selectedCategory); + } + + if (searchQuery) { + const query = searchQuery.toLowerCase(); + result = result.filter(project => + project.title.toLowerCase().includes(query) || + project.description.toLowerCase().includes(query) || + project.tags.some(tag => tag.toLowerCase().includes(query)) + ); + } + + setFilteredProjects(result); + }, [projects, selectedCategory, searchQuery]); if (!mounted) { return null; } return ( -
    -
    +
    +
    {/* Header */} { > - + Back to Home -

    +

    My Projects

    -

    +

    Explore my portfolio of projects, from web applications to mobile apps. Each project showcases different skills and technologies.

    - {/* Category Filter */} + {/* Filters & Search */} -
    + {/* Categories */} +
    {categories.map((category) => ( ))}
    + + {/* Search */} +
    + + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2 bg-white border border-stone-200 rounded-full text-stone-800 placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-stone-200 focus:border-stone-400 transition-all" + /> +
    {/* Projects Grid */} @@ -120,98 +148,102 @@ const ProjectsPage = () => { initial={{ opacity: 0, y: 30 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6, delay: index * 0.1 }} - whileHover={{ y: -10 }} - className="group relative overflow-hidden rounded-2xl glass-card card-hover" + whileHover={{ y: -8 }} + className="group flex flex-col bg-white/40 backdrop-blur-xl rounded-2xl overflow-hidden border border-white/60 shadow-[0_4px_20px_rgba(0,0,0,0.02)] hover:shadow-[0_20px_40px_rgba(0,0,0,0.06)] transition-all duration-500" > -
    -
    -
    -
    - - {project.title.split(' ').map(word => word[0]).join('').toUpperCase()} + {/* Image / Fallback */} +
    + {project.imageUrl ? ( + {project.title} + ) : ( +
    + + {project.title.charAt(0)}
    - - {project.title} - -
    + )} {project.featured && ( -
    +
    Featured
    )} - - {((project.github && project.github.trim() && project.github !== "#") || (project.live && project.live.trim() && project.live !== "#")) && ( -
    - {project.github && project.github.trim() && project.github !== "#" && ( - - - - )} - {project.live && project.live.trim() && project.live !== "#" && ( - - - - )} -
    - )}
    -
    +
    -

    +

    {project.title}

    -
    - - {project.date} +
    + + {new Date(project.date).getFullYear()}
    -

    +

    {project.description}

    -
    - {project.tags.map((tag) => ( +
    + {project.tags.slice(0, 4).map((tag) => ( {tag} ))} + {project.tags.length > 4 && ( + + {project.tags.length - 4} + )}
    - - View Project - - +
    +
    + {project.github && ( + + + + )} + {project.live && ( + + + + )} +
    + + + Read More + + +
    ))}
    + + {filteredProjects.length === 0 && ( +
    +

    No projects found matching your criteria.

    + +
    + )}
    ); }; -export default ProjectsPage; +export default ProjectsPage; \ No newline at end of file