1- // src/pages/ProjectsPage.tsx
2-
31import { useState } from "react" ;
4- import { Search , PlusIcon , AlertCircle } from "lucide-react" ;
2+ import { Search , PlusIcon , AlertCircle , Filter } from "lucide-react" ;
53import { motion } from "framer-motion" ;
64import ProjectCard , { Project } from "../components/projectCard" ;
75import { useGitHubStars } from "../hooks/useGithubStars" ;
@@ -12,6 +10,7 @@ const ProjectsPage = () => {
1210 const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
1311 const [ selectedTag , setSelectedTag ] = useState ( "" ) ;
1412 const [ sortBy , setSortBy ] = useState ( "stars" ) ; // Options: none, stars, name
13+ const [ showFilters , setShowFilters ] = useState ( false ) ;
1514
1615 const { projects, isLoading, rateLimit, refreshStars } = useGitHubStars ( data . projects as Project [ ] ) ;
1716
@@ -46,12 +45,12 @@ const ProjectsPage = () => {
4645 initial = { { opacity : 0 } }
4746 animate = { { opacity : 1 } }
4847 transition = { { delay : 3 , duration : 0.8 } }
49- className = "min-h-screen m-10"
48+ className = "min-h-screen mx-4 md: m-10"
5049 >
5150 < div className = "max-w-7xl mx-auto" >
5251 < div >
53- < div className = "grid justify-center md:flex md:justify-between text-center" >
54- < h1 className = "text-4xl font-bold text-gray-900 mb-4 " >
52+ < div className = "grid justify-center md:flex md:justify-between text-center mb-4 " >
53+ < h1 className = "text-4xl font-bold text-gray-900 mb-2 md:mb-0 " >
5554 Breakpoint;
5655 </ h1 >
5756 < a
@@ -63,78 +62,79 @@ const ProjectsPage = () => {
6362 </ a >
6463 </ div >
6564
66- { /* Search, filter, and sort */ }
67- < div className = "flex flex-wrap justify-center gap-4 mb-8 pt-3" >
68- < div className = "relative flex-grow " >
65+ < div className = "flex flex-col md:flex-row md:flex-wrap md:justify-center md:gap-4 mb-4" >
66+ < div className = "relative flex-grow mb-2 md:mb-0" >
6967 < Search
7068 className = "absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
7169 size = { 20 }
7270 />
73- < input
74- type = "text"
75- placeholder = "Search projects..."
76- className = "pl-10 pr-4 py-2 w-full border rounded-lg"
77- value = { searchTerm }
78- onChange = { ( e ) => setSearchTerm ( e . target . value ) }
79- />
80- </ div >
81-
82- < select
83- className = "border rounded-lg px-4 py-2 bg-white"
84- value = { selectedTag }
85- onChange = { ( e ) => setSelectedTag ( e . target . value ) }
86- >
87- < option value = "" > All Tags</ option >
88- { allTags . map ( ( tag ) => (
89- < option
90- key = { tag }
91- value = { tag }
92- className = "text-red bg-white border border-black"
71+ < div className = "flex" >
72+ < input
73+ type = "text"
74+ placeholder = "Search projects..."
75+ className = "pl-10 pr-4 py-2 w-full border rounded-l-lg md:rounded-lg"
76+ value = { searchTerm }
77+ onChange = { ( e ) => setSearchTerm ( e . target . value ) }
78+ />
79+ < button
80+ className = "md:hidden px-3 bg-white border border-l-0 rounded-r-lg flex items-center"
81+ onClick = { ( ) => setShowFilters ( ! showFilters ) }
9382 >
94- { tag }
95- </ option >
96- ) ) }
97- </ select >
98-
99- < select
100- className = "border rounded-lg px-4 py-2 bg-white"
101- value = { sortBy }
102- onChange = { ( e ) => setSortBy ( e . target . value ) }
103- >
104- < option value = "stars" > Sort by: GitHub Stars ⭐</ option >
105- < option value = "none" > Sort by: Default</ option >
106- < option value = "name" > Sort by: Project Name</ option >
107- </ select >
108-
109- < button
110- onClick = { handleRefreshStars }
111- className = "border rounded-lg px-4 py-2 bg-white hover:bg-gray-100"
112- title = "Refresh GitHub stars (clears cache)"
113- >
114- Refresh Stars
115- </ button >
83+ < Filter size = { 18 } className = { showFilters ? "text-yellow-500" : "text-gray-500" } />
84+ </ button >
85+ </ div >
86+ </ div >
87+
88+ < div className = { `${ showFilters || window . innerWidth >= 768 ? 'flex' : 'hidden' } flex-wrap md:flex gap-2 items-center` } >
89+ < select
90+ className = "border rounded-lg px-3 py-2 bg-white flex-grow md:flex-grow-0 mb-2 md:mb-0"
91+ value = { selectedTag }
92+ onChange = { ( e ) => setSelectedTag ( e . target . value ) }
93+ >
94+ < option value = "" > All Tags</ option >
95+ { allTags . map ( ( tag ) => (
96+ < option key = { tag } value = { tag } > { tag } </ option >
97+ ) ) }
98+ </ select >
99+
100+ < select
101+ className = "border rounded-lg px-3 py-2 bg-white flex-grow md:flex-grow-0 mb-2 md:mb-0"
102+ value = { sortBy }
103+ onChange = { ( e ) => setSortBy ( e . target . value ) }
104+ >
105+ < option value = "stars" > Sort by: Stars ⭐</ option >
106+ < option value = "none" > Sort by: Default</ option >
107+ < option value = "name" > Sort by: Name</ option >
108+ </ select >
109+
110+ < button
111+ onClick = { handleRefreshStars }
112+ className = "border rounded-lg px-3 py-2 bg-white hover:bg-gray-100 flex-grow md:flex-grow-0"
113+ title = "Refresh GitHub stars (clears cache)"
114+ >
115+ Refresh Stars
116+ </ button >
117+ </ div >
116118 </ div >
117119
118120 { /* Rate limit warning */ }
119121 { rateLimit && rateLimit . remaining < 10 && (
120122 < div className = "bg-yellow-50 border border-yellow-200 text-yellow-800 p-3 rounded-md mb-4 flex items-start gap-2" >
121123 < AlertCircle className = "text-yellow-600 shrink-0 mt-1" size = { 20 } />
122- < p >
124+ < p className = "text-sm" >
123125 < strong > GitHub API rate limit warning:</ strong > { rateLimit . remaining } requests remaining.
124126 Rate limit will reset at { rateLimit . resetTime } .
125127 </ p >
126128 </ div >
127129 ) }
128130 </ div >
129131
130- { /* Loading indicator */ }
131132 { isLoading && (
132133 < div className = "flex justify-center items-center h-64" >
133134 < div className = "animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black" > </ div >
134135 </ div >
135136 ) }
136137
137- { /* Projects grid */ }
138138 { ! isLoading && (
139139 < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" >
140140 { sortedProjects . map ( ( project ) => (
0 commit comments