@@ -3,6 +3,72 @@ import { ClientOnly } from 'remix-utils/client-only';
33import { BaseChat } from '~/components/chat/BaseChat' ;
44import { Chat } from '~/components/chat/Chat.client' ;
55import { Header } from '~/components/header/Header' ;
6+ import React , { useState } from 'react' ;
7+ import { ChallengeCard } from '~/components/challenge/ChallengeCard' ;
8+ import { useNavigate } from '@remix-run/react' ;
9+
10+ type Challenge = {
11+ id : string ;
12+ title : string ;
13+ image : string ;
14+ difficulty : 'Easy' | 'Medium' | 'Hard' ;
15+ averageAccuracy : number ;
16+ description ?: string ;
17+ } ;
18+
19+ const challenges : Challenge [ ] = [
20+ {
21+ id : '1' ,
22+ title : 'Sales Dashboard' ,
23+ image : '/sales-dashboard.png' ,
24+ difficulty : 'Hard' ,
25+ averageAccuracy : 62 ,
26+ } ,
27+ {
28+ id : '2' ,
29+ title : 'Login Box' ,
30+ image : '/login.png' ,
31+ difficulty : 'Easy' ,
32+ averageAccuracy : 91 ,
33+ } ,
34+ {
35+ id : '3' ,
36+ title : 'Google Drive' ,
37+ image : '/Folders.png' ,
38+ difficulty : 'Easy' ,
39+ averageAccuracy : 87 ,
40+ } ,
41+ {
42+ id : '4' ,
43+ title : 'Profile Page' ,
44+ image : '/profile.jpg' ,
45+ difficulty : 'Medium' ,
46+ averageAccuracy : 74 ,
47+ description : 'Determine whether an integer is a palindrome.' ,
48+ } ,
49+ {
50+ id : '5' ,
51+ title : 'Merge Intervals' ,
52+ image : '/project-visibility.jpg' ,
53+ difficulty : 'Medium' ,
54+ averageAccuracy : 68 ,
55+ description : 'Merge all overlapping intervals in a list of intervals.' ,
56+ } ,
57+ {
58+ id : '6' ,
59+ title : 'N-Queens' ,
60+ image : '/social_preview_index.jpg' ,
61+ difficulty : 'Hard' ,
62+ averageAccuracy : 41 ,
63+ description : 'Place N queens on an N×N chessboard so that no two queens threaten each other.' ,
64+ } ,
65+ ] as const ;
66+
67+ const difficultyOptions = [ 'All' , 'Easy' , 'Medium' , 'Hard' ] as const ;
68+ const sortOptions = [
69+ { value : 'title' , label : 'Title' } ,
70+ { value : 'difficulty' , label : 'Difficulty' } ,
71+ ] ;
672
773export const meta : MetaFunction = ( ) => {
874 return [ { title : 'Bolt' } , { name : 'description' , content : 'Talk with Bolt, an AI assistant from StackBlitz' } ] ;
@@ -11,10 +77,109 @@ export const meta: MetaFunction = () => {
1177export const loader = ( ) => json ( { } ) ;
1278
1379export default function Index ( ) {
80+ const navigate = useNavigate ( ) ;
81+ const [ difficulty , setDifficulty ] = useState < 'All' | 'Easy' | 'Medium' | 'Hard' > ( 'All' ) ;
82+ const [ sort , setSort ] = useState < 'title' | 'difficulty' > ( 'title' ) ;
83+ const [ search , setSearch ] = useState ( '' ) ;
84+
85+ const filtered = challenges . filter (
86+ ( c ) =>
87+ ( difficulty === 'All' || c . difficulty === difficulty ) &&
88+ ( c . title . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ||
89+ ( c . description && c . description . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ) ) ,
90+ ) ;
91+ const sorted = [ ...filtered ] . sort ( ( a , b ) => {
92+ if ( sort === 'title' ) {
93+ return a . title . localeCompare ( b . title ) ;
94+ }
95+
96+ if ( sort === 'difficulty' ) {
97+ return a . difficulty . localeCompare ( b . difficulty ) ;
98+ }
99+
100+ return 0 ;
101+ } ) ;
102+
14103 return (
15104 < div className = "flex flex-col h-full w-full" >
16105 < Header />
17- < ClientOnly fallback = { < BaseChat /> } > { ( ) => < Chat /> } </ ClientOnly >
106+ < div className = "min-h-screen bg-bolt-elements-background-depth-1 py-10 px-4" >
107+ < div className = "max-w-6xl mx-auto" >
108+ < div className = "w-full px-0 md:px-0" >
109+ < div
110+ className = "flex flex-col md:flex-row md:items-end md:justify-between mb-10 gap-4 w-full bg-gradient-to-r from-purple-700 via-fuchsia-600 to-purple-400 rounded-lg shadow-lg border-0 p-4 md:p-6 transition-all duration-200"
111+ style = { {
112+ minHeight : '90px' ,
113+ width : '100vw' ,
114+ left : '50%' ,
115+ right : '50%' ,
116+ marginLeft : '-50vw' ,
117+ marginRight : '-50vw' ,
118+ position : 'relative' ,
119+ } }
120+ >
121+ < div className = "flex-1 min-w-0" >
122+ < h1 className = "text-4xl font-extrabold text-white tracking-tight drop-shadow-lg mb-1 leading-tight" >
123+ Solve Challenges
124+ </ h1 >
125+ < p className = "text-base text-white/80 font-medium mt-0 drop-shadow-sm" >
126+ Browse and solve interactive UI challenges to sharpen your frontend skills.
127+ </ p >
128+ </ div >
129+ </ div >
130+ </ div >
131+ < div className = "flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-8 bg-bolt-elements-background-depth-2 rounded-xl border border-bolt-elements-borderColor shadow-lg p-6 w-full max-w-4xl mx-auto transition-all duration-200" >
132+ < input
133+ type = "text"
134+ placeholder = "Search challenges..."
135+ value = { search }
136+ onChange = { ( e ) => setSearch ( e . target . value ) }
137+ className = "flex-1 rounded-lg px-4 py-2 border-0 bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-accent/60 transition shadow-md text-base font-medium placeholder:text-bolt-elements-textSecondary"
138+ />
139+ < div className = "flex flex-col sm:flex-row gap-3 items-stretch sm:items-center mt-2 md:mt-0" >
140+ < div className = "flex gap-2 items-center bg-bolt-elements-background-depth-1 rounded-lg px-3 py-2 border border-bolt-elements-borderColor shadow-sm" >
141+ < label htmlFor = "difficulty" className = "text-bolt-elements-textSecondary font-semibold text-sm mr-1" >
142+ Difficulty:
143+ </ label >
144+ < select
145+ id = "difficulty"
146+ value = { difficulty }
147+ onChange = { ( e ) => setDifficulty ( e . target . value as any ) }
148+ className = "rounded px-2 py-1 border border-bolt-elements-borderColor bg-transparent text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-accent/60 shadow-sm font-medium"
149+ >
150+ { difficultyOptions . map ( ( opt ) => (
151+ < option key = { opt } value = { opt } >
152+ { opt }
153+ </ option >
154+ ) ) }
155+ </ select >
156+ </ div >
157+ < div className = "flex gap-2 items-center bg-bolt-elements-background-depth-1 rounded-lg px-3 py-2 border border-bolt-elements-borderColor shadow-sm" >
158+ < label htmlFor = "sort" className = "text-bolt-elements-textSecondary font-semibold text-sm mr-1" >
159+ Sort by:
160+ </ label >
161+ < select
162+ id = "sort"
163+ value = { sort }
164+ onChange = { ( e ) => setSort ( e . target . value as any ) }
165+ className = "rounded px-2 py-1 border border-bolt-elements-borderColor bg-transparent text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-accent/60 shadow-sm font-medium"
166+ >
167+ { sortOptions . map ( ( opt ) => (
168+ < option key = { opt . value } value = { opt . value } >
169+ { opt . label }
170+ </ option >
171+ ) ) }
172+ </ select >
173+ </ div >
174+ </ div >
175+ </ div >
176+ < div className = "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6" >
177+ { sorted . map ( ( challenge ) => (
178+ < ChallengeCard key = { challenge . id } { ...challenge } onClick = { ( ) => navigate ( `/challenge/${ challenge . id } ` ) } />
179+ ) ) }
180+ </ div >
181+ </ div >
182+ </ div >
18183 </ div >
19184 ) ;
20185}
0 commit comments