Skip to content

Commit 7fe4402

Browse files
committed
solve page
1 parent c33d37a commit 7fe4402

File tree

8 files changed

+304
-91
lines changed

8 files changed

+304
-91
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from 'react';
2+
3+
export type ChallengeCardProps = {
4+
id: string;
5+
title: string;
6+
image: string;
7+
difficulty: 'Easy' | 'Medium' | 'Hard';
8+
averageAccuracy?: number; // percentage, optional for backward compatibility
9+
onClick?: () => void;
10+
};
11+
12+
export function ChallengeCard({ id, title, image, difficulty, averageAccuracy, onClick }: ChallengeCardProps) {
13+
const difficultyColor =
14+
difficulty === 'Easy' ? 'text-green-500' : difficulty === 'Medium' ? 'text-yellow-500' : 'text-red-500';
15+
return (
16+
<div
17+
className="cursor-pointer bg-bolt-elements-background-depth-2 rounded-lg shadow border border-bolt-elements-borderColor hover:shadow-lg transition flex flex-col overflow-hidden group w-full max-w-xs mx-auto"
18+
onClick={onClick}
19+
tabIndex={0}
20+
role="button"
21+
aria-label={`Open challenge ${title}`}
22+
style={{ minHeight: 220 }}
23+
>
24+
<div className="h-40 w-full bg-gray-100 flex items-center justify-center overflow-hidden">
25+
<img
26+
src={image}
27+
alt={title}
28+
className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-200"
29+
/>
30+
</div>
31+
<div className="p-3 flex-1 flex flex-col">
32+
<div className="flex items-center justify-between mb-1">
33+
<h2 className="text-base font-bold text-bolt-elements-textPrimary truncate" title={title}>
34+
{title}
35+
</h2>
36+
<div className="flex items-center gap-2">
37+
{typeof averageAccuracy === 'number' && (
38+
<span className="text-xs font-semibold text-blue-500 flex items-center">
39+
{averageAccuracy}%
40+
<span className="mx-2 h-4 border-l border-bolt-elements-borderColor" />
41+
</span>
42+
)}
43+
<span className={`text-xs font-semibold ${difficultyColor}`}>{difficulty}</span>
44+
</div>
45+
</div>
46+
</div>
47+
</div>
48+
);
49+
}

app/routes/_index.tsx

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,72 @@ import { ClientOnly } from 'remix-utils/client-only';
33
import { BaseChat } from '~/components/chat/BaseChat';
44
import { Chat } from '~/components/chat/Chat.client';
55
import { 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

773
export 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 = () => {
1177
export const loader = () => json({});
1278

1379
export 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
}

app/routes/profile.tsx

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ export default function ProfilePage() {
4848
<Header />
4949
<main className="flex-1 flex items-center justify-center">
5050
<section className="w-full max-w-4xl mx-auto bg-bolt-elements-background-depth-2 rounded-2xl border border-bolt-elements-borderColor shadow-lg overflow-hidden">
51-
{/* Profile header */}
5251
<div className="flex flex-col md:flex-row items-center md:items-end gap-8 md:gap-16 px-10 pt-12 pb-8 border-b border-bolt-elements-borderColor bg-bolt-elements-background-depth-2">
5352
<div className="flex flex-col items-center md:items-start">
5453
<div className="w-32 h-32 rounded-full bg-gradient-to-br from-green-400 via-yellow-400 to-red-400 flex items-center justify-center mb-4 shadow-xl border-4 border-white">
@@ -96,17 +95,17 @@ export default function ProfilePage() {
9695
</div>
9796
</div>
9897
{/* Progress section */}
99-
<div className="px-10 py-10 bg-bolt-elements-background-depth-1">
98+
<div className="px-10 py-10 bg-bolt-elements-background-depth-1 flex flex-col items-center justify-center">
10099
{/* Add more detail: stats table */}
101-
<div className="mt-10">
100+
<div className="mt-10 w-full flex flex-col items-center">
102101
<button
103-
className="text-white bg-bolt-elements-background-depth-2 px-4 py-2 rounded font-bold mb-4 flex items-center gap-2 hover:bg-bolt-elements-background-depth-1 transition"
102+
className="text-lg text-white bg-bolt-elements-background-depth-2 px-6 py-3 rounded font-bold mb-4 flex items-center gap-2 hover:bg-bolt-elements-background-depth-1 transition shadow-lg"
104103
onClick={() => setShowStats((s) => !s)}
105104
aria-expanded={showStats}
106105
>
107-
<span>Statistics</span>
106+
<span className="text-xl">Statistics</span>
108107
<svg
109-
className={`w-4 h-4 transition-transform ${showStats ? 'rotate-180' : ''}`}
108+
className={`w-5 h-5 transition-transform ${showStats ? 'rotate-180' : ''}`}
110109
fill="none"
111110
stroke="currentColor"
112111
strokeWidth="2"
@@ -116,30 +115,30 @@ export default function ProfilePage() {
116115
</svg>
117116
</button>
118117
{showStats && (
119-
<div className="overflow-x-auto">
120-
<table className="min-w-full text-sm text-left border-collapse">
118+
<div className="overflow-x-auto w-full max-w-md mx-auto">
119+
<table className="min-w-full text-lg text-left border-collapse">
121120
<thead>
122121
<tr className="bg-bolt-elements-background-depth-2">
123-
<th className="px-4 py-2 font-semibold text-white">Category</th>
124-
<th className="px-4 py-2 font-semibold text-white">Value</th>
122+
<th className="px-4 py-2 font-semibold text-white text-lg">Category</th>
123+
<th className="px-4 py-2 font-semibold text-white text-lg">Value</th>
125124
</tr>
126125
</thead>
127126
<tbody>
128127
<tr>
129-
<td className="px-4 py-2 text-white">Average Acceptance Rate</td>
130-
<td className="px-4 py-2 text-white">92%</td>
128+
<td className="px-4 py-2 text-white text-base">Average Acceptance Rate</td>
129+
<td className="px-4 py-2 text-white text-base">92%</td>
131130
</tr>
132131
<tr className="bg-bolt-elements-background-depth-2/50">
133-
<td className="px-4 py-2 text-white">Longest Streak</td>
134-
<td className="px-4 py-2 text-white">12 days</td>
132+
<td className="px-4 py-2 text-white text-base">Longest Streak</td>
133+
<td className="px-4 py-2 text-white text-base">12 days</td>
135134
</tr>
136135
<tr>
137-
<td className="px-4 py-2 text-white">Last Submission</td>
138-
<td className="px-4 py-2 text-white">2 days ago</td>
136+
<td className="px-4 py-2 text-white text-base">Last Submission</td>
137+
<td className="px-4 py-2 text-white text-base">2 days ago</td>
139138
</tr>
140139
<tr className="bg-bolt-elements-background-depth-2/50">
141-
<td className="px-4 py-2 text-white">Total Submissions</td>
142-
<td className="px-4 py-2 text-white">128</td>
140+
<td className="px-4 py-2 text-white text-base">Total Submissions</td>
141+
<td className="px-4 py-2 text-white text-base">128</td>
143142
</tr>
144143
</tbody>
145144
</table>

0 commit comments

Comments
 (0)