Skip to content

Commit f3be6b8

Browse files
davidagustinclaude
andcommitted
Add LeetCode-style UI/UX improvements
- Add persistent navbar with logo, navigation, progress stats, and theme toggle - Add ProgressProvider for tracking solved problems in localStorage - Add problem table view with status checkmarks and acceptance rate column - Add collapsible sidebar filters (status, difficulty, category) - Add streak tracking and progress indicators - Update home page with progress bar - Add solved badge on problem detail page - Auto-mark problems as solved when all tests pass New components: - Navbar.tsx - persistent top navigation - ProgressProvider.tsx - progress context with localStorage - ProblemTable.tsx - LeetCode-style table view - FilterSidebar.tsx - collapsible filter panel Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9cbae35 commit f3be6b8

14 files changed

Lines changed: 1736 additions & 344 deletions

.chromatic.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
projectToken: process.env.CHROMATIC_PROJECT_TOKEN,
3+
buildScriptName: 'chromatic:build',
4+
onlyChanged: true,
5+
exitZeroOnChanges: true,
6+
exitOnceUploaded: true,
7+
};

app/globals.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@
1313
}
1414

1515
body {
16-
@apply bg-white dark:bg-[#0a0a0a] text-gray-900 dark:text-gray-100 font-sans antialiased;
16+
@apply font-sans antialiased;
1717
font-family: var(--font-geist-sans), -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
18+
background-color: white;
19+
color: rgb(17, 24, 39);
20+
}
21+
22+
html.dark body {
23+
background-color: #0a0a0a;
24+
color: #ededed;
1825
}
1926

2027
button,

app/layout.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import './globals.css';
44
import ErrorHandler from '@/components/ErrorHandler';
55
import ErrorBoundary from '@/components/ErrorBoundary';
66
import { ThemeProvider } from '@/components/ThemeProvider';
7+
import { ProgressProvider } from '@/components/ProgressProvider';
8+
import Navbar from '@/components/Navbar';
79

810
const geistSans = Geist({
911
variable: '--font-geist-sans',
@@ -50,11 +52,14 @@ export default function RootLayout({
5052
}}
5153
/>
5254
</head>
53-
<body className={`${geistSans.variable} ${geistMono.variable} bg-white dark:bg-[#0a0a0a] text-gray-900 dark:text-gray-100 antialiased`}>
55+
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
5456
<ErrorBoundary>
5557
<ThemeProvider>
56-
<ErrorHandler />
57-
{children}
58+
<ProgressProvider>
59+
<ErrorHandler />
60+
<Navbar />
61+
<main>{children}</main>
62+
</ProgressProvider>
5863
</ThemeProvider>
5964
</ErrorBoundary>
6065
</body>

app/page.tsx

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import { useMemo } from 'react';
44
import Link from 'next/link';
55
import { problems } from '@/lib/problems';
6-
import ThemeToggle from '@/components/ThemeToggle';
6+
import { useProgress } from '@/components/ProgressProvider';
77

88
export default function Home() {
9+
const { solvedCount } = useProgress();
10+
911
// Memoize stats calculation to avoid recalculating on every render
1012
const stats = useMemo(
1113
() => ({
@@ -22,10 +24,7 @@ export default function Home() {
2224

2325
return (
2426
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
25-
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
26-
<div className="flex justify-end mb-8">
27-
<ThemeToggle />
28-
</div>
27+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
2928
<div className="text-center mb-16">
3029
<h1 className="text-5xl font-bold text-gray-900 dark:text-gray-100 mb-4">
3130
JavaScript & TypeScript Tricks
@@ -42,30 +41,51 @@ export default function Home() {
4241
</Link>
4342
</div>
4443

45-
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-16">
46-
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 text-center">
47-
<div className="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-2">
44+
{/* Progress Card */}
45+
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mb-8 max-w-md mx-auto">
46+
<div className="flex items-center justify-between mb-3">
47+
<span className="text-gray-600 dark:text-gray-400">Your Progress</span>
48+
<span className="text-2xl font-bold text-green-600 dark:text-green-400">
49+
{solvedCount}/{stats.total}
50+
</span>
51+
</div>
52+
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-3">
53+
<div
54+
className="bg-green-500 h-3 rounded-full transition-all duration-500"
55+
style={{ width: `${(solvedCount / stats.total) * 100}%` }}
56+
/>
57+
</div>
58+
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2 text-center">
59+
{solvedCount === 0
60+
? 'Start solving problems to track your progress!'
61+
: `${Math.round((solvedCount / stats.total) * 100)}% complete`}
62+
</p>
63+
</div>
64+
65+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-16">
66+
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-5 text-center">
67+
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400 mb-1">
4868
{stats.total}
4969
</div>
50-
<div className="text-gray-600 dark:text-gray-400">Total Problems</div>
70+
<div className="text-sm text-gray-600 dark:text-gray-400">Total</div>
5171
</div>
52-
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 text-center">
53-
<div className="text-3xl font-bold text-green-600 dark:text-green-400 mb-2">
72+
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-5 text-center">
73+
<div className="text-2xl font-bold text-green-600 dark:text-green-400 mb-1">
5474
{stats.easy}
5575
</div>
56-
<div className="text-gray-600 dark:text-gray-400">Easy</div>
76+
<div className="text-sm text-gray-600 dark:text-gray-400">Easy</div>
5777
</div>
58-
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 text-center">
59-
<div className="text-3xl font-bold text-yellow-600 dark:text-yellow-400 mb-2">
78+
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-5 text-center">
79+
<div className="text-2xl font-bold text-yellow-600 dark:text-yellow-400 mb-1">
6080
{stats.medium}
6181
</div>
62-
<div className="text-gray-600 dark:text-gray-400">Medium</div>
82+
<div className="text-sm text-gray-600 dark:text-gray-400">Medium</div>
6383
</div>
64-
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 text-center">
65-
<div className="text-3xl font-bold text-red-600 dark:text-red-400 mb-2">
84+
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-5 text-center">
85+
<div className="text-2xl font-bold text-red-600 dark:text-red-400 mb-1">
6686
{stats.hard}
6787
</div>
68-
<div className="text-gray-600 dark:text-gray-400">Hard</div>
88+
<div className="text-sm text-gray-600 dark:text-gray-400">Hard</div>
6989
</div>
7090
</div>
7191

app/problems/[id]/page.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ import { useEffect, useRef, useState } from 'react';
66
import CodeEditor from '@/components/CodeEditor';
77
import ProblemDescription from '@/components/ProblemDescription';
88
import TestResults from '@/components/TestResults';
9-
import ThemeToggle from '@/components/ThemeToggle';
9+
import { useProgress } from '@/components/ProgressProvider';
1010
import { getProblemById, problems } from '@/lib/problems';
1111
import { runTests, type TestRunnerResult } from '@/lib/test-runner';
1212

1313
export default function ProblemPage() {
1414
const params = useParams();
1515
const problemId = params.id as string;
16+
const { markSolved, isSolved } = useProgress();
1617

1718
const problem = getProblemById(problemId);
19+
const solved = isSolved(problemId);
1820
const [code, setCode] = useState('');
1921
const [userCode, setUserCode] = useState(''); // Store user's code separately
2022
const [testResults, setTestResults] = useState<TestRunnerResult | null>(null);
@@ -83,6 +85,11 @@ export default function ProblemPage() {
8385
);
8486

8587
setTestResults(results);
88+
89+
// Mark as solved if all tests passed
90+
if (results.allPassed) {
91+
markSolved(problemId);
92+
}
8693
} catch (error: unknown) {
8794
console.error('Unexpected test execution error:', error);
8895
const errorMessage =
@@ -137,15 +144,15 @@ export default function ProblemPage() {
137144
return (
138145
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
139146
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
140-
<div className="mb-6 flex items-center justify-between">
141-
<Link
142-
href="/problems"
143-
className="text-blue-600 dark:text-blue-400 hover:underline transition-colors duration-200 cursor-pointer inline-flex items-center gap-1 hover:text-blue-700 dark:hover:text-blue-300 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2 dark:focus:ring-offset-gray-800 rounded px-1"
144-
>
145-
← Back to Problems
146-
</Link>
147-
<ThemeToggle />
148-
</div>
147+
{/* Solved Badge */}
148+
{solved && (
149+
<div className="mb-4 inline-flex items-center gap-2 px-3 py-1.5 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 rounded-full text-sm font-medium">
150+
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
151+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
152+
</svg>
153+
Solved
154+
</div>
155+
)}
149156

150157
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
151158
{/* Left Column - Problem Description */}

0 commit comments

Comments
 (0)