@@ -24,6 +24,17 @@ import {
2424 TooltipProvider ,
2525 TooltipTrigger ,
2626} from "@/components/ui/tooltip" ;
27+ import {
28+ Select ,
29+ SelectContent ,
30+ SelectItem ,
31+ SelectTrigger ,
32+ SelectValue ,
33+ } from "@/components/ui/select"
34+
35+
36+
37+
2738
2839const DiscordIcon = ( { className = "" } ) => (
2940 < svg
@@ -81,7 +92,7 @@ const languageColors: { [key: string]: string } = {
8192
8293const LanguageBar = ( { languages } : { languages : Language } ) => {
8394 const totalBytes = Object . values ( languages ) . reduce ( ( sum , value ) => sum + value , 0 ) ;
84-
95+
8596 const percentages = Object . entries ( languages ) . map ( ( [ name , bytes ] ) => ( {
8697 name,
8798 percentage : ( bytes / totalBytes ) * 100
@@ -90,7 +101,7 @@ const LanguageBar = ({ languages }: { languages: Language }) => {
90101 return (
91102 < div className = "space-y-4" >
92103 < h2 className = "text-lg font-semibold text-white" > Languages</ h2 >
93-
104+
94105 < TooltipProvider >
95106 < div className = "h-2 w-full flex rounded-full overflow-hidden" >
96107 { percentages . map ( ( { name, percentage } ) => (
@@ -104,12 +115,12 @@ const LanguageBar = ({ languages }: { languages: Language }) => {
104115 className = "transition-opacity hover:opacity-80"
105116 />
106117 </ TooltipTrigger >
107- < TooltipContent
118+ < TooltipContent
108119 className = "bg-slate-800 border-slate-700 text-white"
109120 side = "top"
110121 >
111122 < div className = "flex items-center gap-2" >
112- < div
123+ < div
113124 className = "w-3 h-3 rounded-full"
114125 style = { { backgroundColor : languageColors [ name ] || '#ededed' } }
115126 />
@@ -120,10 +131,10 @@ const LanguageBar = ({ languages }: { languages: Language }) => {
120131 ) ) }
121132 </ div >
122133 </ TooltipProvider >
123-
134+
124135 < div className = "flex flex-wrap gap-4" >
125136 { percentages
126- . filter ( ( { percentage } ) => percentage >= 2 )
137+ . filter ( ( { percentage } ) => percentage >= 2 )
127138 . map ( ( { name, percentage } ) => (
128139 < div key = { name } className = "flex items-center gap-2" >
129140 < span
@@ -172,6 +183,9 @@ export default function GithubProjectsPage() {
172183 description : '' ,
173184 reason : ''
174185 } ) ;
186+ const [ sortBy , setSortBy ] = useState < SortOption > ( 'none' ) ;
187+
188+
175189 const projectsPerPage = 9 ;
176190
177191 useEffect ( ( ) => {
@@ -189,14 +203,36 @@ export default function GithubProjectsPage() {
189203 } ;
190204 fetchProjects ( ) ;
191205 } , [ ] ) ;
206+ type SortOption = 'none' | 'stars' | 'forks' ;
207+
208+ const sortProjects = ( projects : Project [ ] , sortBy : SortOption ) => {
209+ if ( sortBy === 'none' ) return projects ;
210+
211+ return [ ...projects ] . sort ( ( a , b ) => {
192212
193- const filteredProjects = projects . filter ( ( project ) =>
194- project . name . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
195- project . description . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
196- project . tags . some ( ( tag ) => tag . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ) ||
197- Object . keys ( project . languages ) . some ( ( lang ) =>
198- lang . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
199- )
213+ const aValue = parseFloat ( a [ sortBy ] . toLowerCase ( ) . replace ( / [ , k ] / g, '' ) ) *
214+ ( a [ sortBy ] . toLowerCase ( ) . includes ( 'k' ) ? 1000 : 1 ) ;
215+ const bValue = parseFloat ( b [ sortBy ] . toLowerCase ( ) . replace ( / [ , k ] / g, '' ) ) *
216+ ( b [ sortBy ] . toLowerCase ( ) . includes ( 'k' ) ? 1000 : 1 ) ;
217+
218+
219+ if ( isNaN ( aValue ) ) return 1 ;
220+ if ( isNaN ( bValue ) ) return - 1 ;
221+ if ( isNaN ( aValue ) && isNaN ( bValue ) ) return 0 ;
222+
223+ return bValue - aValue ;
224+ } ) ;
225+ } ;
226+ const filteredProjects = sortProjects (
227+ projects . filter ( ( project ) =>
228+ project . name . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
229+ project . description . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
230+ project . tags . some ( ( tag ) => tag . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ) ||
231+ Object . keys ( project . languages ) . some ( ( lang ) =>
232+ lang . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
233+ )
234+ ) ,
235+ sortBy
200236 ) ;
201237
202238 const indexOfLastProject = currentPage * projectsPerPage ;
@@ -293,73 +329,85 @@ export default function GithubProjectsPage() {
293329 onChange = { ( e ) => setSearchTerm ( e . target . value ) }
294330 />
295331 </ div >
332+ < div className = "flex justify-end max-w-6xl mx-auto mb-8" >
333+ < Select value = { sortBy } onValueChange = { ( value : SortOption ) => setSortBy ( value ) } >
334+ < SelectTrigger className = "w-[180px] bg-slate-800/50 border-slate-700 text-white" >
335+ < SelectValue placeholder = "Sort by..." />
336+ </ SelectTrigger >
337+ < SelectContent className = "bg-slate-800 border-slate-700" >
338+ < SelectItem value = "none" className = "text-white hover:bg-slate-700" > No sorting</ SelectItem >
339+ < SelectItem value = "stars" className = "text-white hover:bg-slate-700" > Most Stars</ SelectItem >
340+ < SelectItem value = "forks" className = "text-white hover:bg-slate-700" > Most Forks</ SelectItem >
341+ </ SelectContent >
342+ </ Select >
343+ </ div >
296344 </ div >
297345
298346 < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-8" >
299- { currentProjects . map ( ( project ) => {
300- const isSaved = savedProjects . includes ( project . name ) ;
301- return (
302- < div
303- key = { project . name }
304- className = "group relative cursor-pointer"
305- onClick = { ( ) => handleCardClick ( project . url ) }
306- >
307- < div className = { `absolute inset-0 ${ project . color } rounded-xl blur-md opacity-20 group-hover:opacity-30 transition-all duration-500` } > </ div >
308- < Card className = "relative h-full bg-slate-800/50 border-slate-700 hover:border-slate-600 transition-all duration-500 backdrop-blur-sm transform-gpu hover:-translate-y-2 hover:scale-105" >
309- < CardContent className = "p-6" >
310- < div className = "flex justify-between items-start mb-4" >
311- < h2 className = "text-2xl font-bold text-white group-hover:text-transparent group-hover:bg-gradient-to-r group-hover:from-blue-400 group-hover:via-purple-400 group-hover:to-pink-400 group-hover:bg-clip-text transition-all duration-300" >
312- { project . name }
313- </ h2 >
314- < Button
315- variant = "ghost"
316- size = "sm"
317- className = { `text-gray-400 ${ isSaved ? 'text-green-500' : 'hover:text-white' } ` }
318- onClick = { ( e ) => {
319- e . stopPropagation ( ) ;
320- if ( isSaved ) {
321- removeProject ( project . name ) ;
322- } else {
323- addProject ( project . name ) ;
324- }
325- } }
326- >
327- { isSaved ? 'Unsave' : 'Save' }
328- </ Button >
329- </ div >
330- < p className = "text-gray-300 mb-6" > { project . description } </ p >
331- < div className = "flex flex-wrap gap-2 mb-6" >
332- { project . tags . map ( ( tag , tagIndex ) => (
347+ { currentProjects . map ( ( project ) => {
348+ const isSaved = savedProjects . includes ( project . name ) ;
349+ return (
333350 < div
334- key = { tagIndex }
335- onClick = { ( e ) => {
336- e . stopPropagation ( ) ;
337- setSearchTerm ( tag ) ;
338- } }
339- className = "bg-slate-700/50 text-gray-300 text-sm px-3 py-1 rounded-full flex items-center transform transition-all duration-300 hover:scale-105 hover:bg-slate-600/50"
351+ key = { project . name }
352+ className = "group relative cursor-pointer"
353+ onClick = { ( ) => handleCardClick ( project . url ) }
340354 >
341- < Tag className = "h-3 w-3 mr-1.5" />
342- { tag }
355+ < div className = { `absolute inset-0 ${ project . color } rounded-xl blur-md opacity-20 group-hover:opacity-30 transition-all duration-500` } > </ div >
356+ < Card className = "relative h-full bg-slate-800/50 border-slate-700 hover:border-slate-600 transition-all duration-500 backdrop-blur-sm transform-gpu hover:-translate-y-2 hover:scale-105" >
357+ < CardContent className = "p-6" >
358+ < div className = "flex justify-between items-start mb-4" >
359+ < h2 className = "text-2xl font-bold text-white group-hover:text-transparent group-hover:bg-gradient-to-r group-hover:from-blue-400 group-hover:via-purple-400 group-hover:to-pink-400 group-hover:bg-clip-text transition-all duration-300" >
360+ { project . name }
361+ </ h2 >
362+ < Button
363+ variant = "ghost"
364+ size = "sm"
365+ className = { `text-gray-400 ${ isSaved ? 'text-green-500' : 'hover:text-white' } ` }
366+ onClick = { ( e ) => {
367+ e . stopPropagation ( ) ;
368+ if ( isSaved ) {
369+ removeProject ( project . name ) ;
370+ } else {
371+ addProject ( project . name ) ;
372+ }
373+ } }
374+ >
375+ { isSaved ? 'Unsave' : 'Save' }
376+ </ Button >
377+ </ div >
378+ < p className = "text-gray-300 mb-6" > { project . description } </ p >
379+ < div className = "flex flex-wrap gap-2 mb-6" >
380+ { project . tags . map ( ( tag , tagIndex ) => (
381+ < div
382+ key = { tagIndex }
383+ onClick = { ( e ) => {
384+ e . stopPropagation ( ) ;
385+ setSearchTerm ( tag ) ;
386+ } }
387+ className = "bg-slate-700/50 text-gray-300 text-sm px-3 py-1 rounded-full flex items-center transform transition-all duration-300 hover:scale-105 hover:bg-slate-600/50"
388+ >
389+ < Tag className = "h-3 w-3 mr-1.5" />
390+ { tag }
391+ </ div >
392+ ) ) }
393+ </ div >
394+ < LanguageBar languages = { project . languages } />
395+ < div className = "flex justify-start gap-6 text-sm text-gray-400 mt-6" >
396+ < span className = "flex items-center" >
397+ < Star className = "h-4 w-4 mr-1.5 text-yellow-500" />
398+ { project . stars }
399+ </ span >
400+ < span className = "flex items-center" >
401+ < GitFork className = "h-4 w-4 mr-1.5" />
402+ { project . forks }
403+ </ span >
404+ </ div >
405+ </ CardContent >
406+ </ Card >
343407 </ div >
344- ) ) }
345- </ div >
346- < LanguageBar languages = { project . languages } />
347- < div className = "flex justify-start gap-6 text-sm text-gray-400 mt-6" >
348- < span className = "flex items-center" >
349- < Star className = "h-4 w-4 mr-1.5 text-yellow-500" />
350- { project . stars }
351- </ span >
352- < span className = "flex items-center" >
353- < GitFork className = "h-4 w-4 mr-1.5" />
354- { project . forks }
355- </ span >
356- </ div >
357- </ CardContent >
358- </ Card >
359- </ div >
360- ) ;
361- } ) }
362- </ div >
408+ ) ;
409+ } ) }
410+ </ div >
363411
364412 < div className = "flex justify-center gap-4 mb-16" >
365413 < Button
@@ -427,8 +475,8 @@ export default function GithubProjectsPage() {
427475
428476 { submissionStatus . status && (
429477 < Alert className = { `${ submissionStatus . status === 'success' ? 'bg-green-500/20 border-green-500' :
430- submissionStatus . status === 'error' ? 'bg-red-500/20 border-red-500' :
431- 'bg-blue-500/20 border-blue-500'
478+ submissionStatus . status === 'error' ? 'bg-red-500/20 border-red-500' :
479+ 'bg-blue-500/20 border-blue-500'
432480 } text-white border`} >
433481 < AlertCircle className = "h-4 w-4" />
434482 < AlertTitle >
@@ -556,4 +604,5 @@ export default function GithubProjectsPage() {
556604 </ AlertDialogContent >
557605 </ AlertDialog >
558606 </ div >
559- ) } ;
607+ )
608+ } ;
0 commit comments