Skip to content

Commit 71843d2

Browse files
committed
added problem set page
1 parent 807853b commit 71843d2

File tree

4 files changed

+372
-0
lines changed

4 files changed

+372
-0
lines changed

client-test/public/sitemap.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
<changefreq>daily</changefreq>
2525
<priority>0.7</priority>
2626
</url>
27+
<url>
28+
<loc>https://codequest.srkrcodingclub.com/problemset</loc>
29+
<lastmod>2025-10-20</lastmod>
30+
<changefreq>weekly</changefreq>
31+
<priority>0.8</priority>
32+
</url>
2733
<url>
2834
<loc>https://codequest.srkrcodingclub.com/register</loc>
2935
<lastmod>2025-10-20</lastmod>

client-test/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import Dashboard from './components/Admin/Dashboard.tsx';
2424
import { useAdminStore } from './context/AdminContext.tsx';
2525
import AdminChallenges from './components/Admin/AdminChallenges.tsx';
2626
import RouteSEO from './components/RouteSEO';
27+
import ProblemSet from './components/ProblemSet';
28+
import CategoryProblems from './components/ProblemSet/CategoryProblems';
2729

2830
function UserApp() {
2931
return (
@@ -45,6 +47,8 @@ function UserApp() {
4547
<Route path="/reset-password/:token" element={<ResetPassword />} />
4648
<Route path="/forgot-password" element={<ForgotPassword />} />
4749
<Route path="/challenges/solution/:slug" element={<SolutionPage />} />
50+
<Route path="/problemset" element={<ProblemSet />} />
51+
<Route path="/problemset/category/:categoryName" element={<CategoryProblems />} />
4852
<Route path="*" element={<NotFoundPage />} />
4953
</Routes>
5054
<Footer />
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
import { useEffect, useState } from 'react'
2+
import { useParams, useSearchParams, Link } from 'react-router-dom'
3+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
4+
import { Badge } from '@/components/ui/badge'
5+
import { Button } from '@/components/ui/button'
6+
import { ChevronLeft, ChevronRight, ExternalLink, Loader2, ArrowLeft } from 'lucide-react'
7+
import SEO from '../SEO/SEO'
8+
9+
interface Problem {
10+
_id: string
11+
title: string
12+
difficulty: 'Easy' | 'Medium' | 'Hard'
13+
platform: 'LeetCode' | 'GFG' | 'CodeChef' | 'Codeforces'
14+
problemLink?: string
15+
}
16+
17+
interface PaginationInfo {
18+
currentPage: number
19+
totalPages: number
20+
totalProblems: number
21+
hasNext: boolean
22+
hasPrev: boolean
23+
}
24+
25+
export default function CategoryProblems() {
26+
const { categoryName } = useParams()
27+
const [searchParams, setSearchParams] = useSearchParams()
28+
const [problems, setProblems] = useState<Problem[]>([])
29+
const [loading, setLoading] = useState(true)
30+
const [actualCategoryName, setActualCategoryName] = useState<string>('')
31+
const [pagination, setPagination] = useState<PaginationInfo>({
32+
currentPage: 1,
33+
totalPages: 1,
34+
totalProblems: 0,
35+
hasNext: false,
36+
hasPrev: false
37+
})
38+
39+
const currentPage = parseInt(searchParams.get('page') || '1')
40+
const categoryNameFromURL = searchParams.get('name') || ''
41+
42+
useEffect(() => {
43+
const fetchProblems = async () => {
44+
if (!categoryName) return
45+
46+
setLoading(true)
47+
try {
48+
// Use the category name passed from URL parameter
49+
const actualCategory = categoryNameFromURL || formatCategoryName(categoryName)
50+
setActualCategoryName(actualCategory)
51+
52+
const response = await fetch(
53+
`${import.meta.env.VITE_API_BASE_URL}/api/challenges?page=${currentPage}&limit=10&category=${actualCategory}`
54+
)
55+
56+
if (!response.ok) throw new Error('Failed to fetch problems')
57+
58+
const data = await response.json()
59+
60+
setProblems(data.challenges || [])
61+
setPagination({
62+
currentPage: data.currentPage || 1,
63+
totalPages: data.totalPages || 1,
64+
totalProblems: data.totalChallenges || 0,
65+
hasNext: data.hasNextPage || false,
66+
hasPrev: data.hasPreviousPage || false
67+
})
68+
} catch (error) {
69+
console.error('Error fetching problems:', error)
70+
setProblems([])
71+
} finally {
72+
setLoading(false)
73+
}
74+
}
75+
76+
fetchProblems()
77+
}, [categoryName, currentPage, categoryNameFromURL])
78+
79+
const handlePageChange = (newPage: number) => {
80+
if (newPage >= 1 && newPage <= pagination.totalPages) {
81+
setSearchParams({ page: newPage.toString() })
82+
}
83+
}
84+
85+
const getDifficultyColor = (difficulty: string) => {
86+
switch (difficulty) {
87+
case 'Easy': return 'bg-green-100 text-green-800 hover:bg-green-200'
88+
case 'Medium': return 'bg-yellow-100 text-yellow-800 hover:bg-yellow-200'
89+
case 'Hard': return 'bg-red-100 text-red-800 hover:bg-red-200'
90+
default: return 'bg-gray-100 text-gray-800 hover:bg-gray-200'
91+
}
92+
}
93+
94+
const getPlatformColor = (platform: string) => {
95+
switch (platform) {
96+
case 'LeetCode': return 'bg-orange-100 text-orange-800'
97+
case 'GFG': return 'bg-green-100 text-green-800'
98+
case 'CodeChef': return 'bg-brown-100 text-brown-800'
99+
case 'Codeforces': return 'bg-blue-100 text-blue-800'
100+
default: return 'bg-gray-100 text-gray-800'
101+
}
102+
}
103+
104+
const formatCategoryName = (slug?: string) => {
105+
if (!slug) return ''
106+
return slug.split('-').map(word =>
107+
word.charAt(0).toUpperCase() + word.slice(1)
108+
).join(' ')
109+
}
110+
111+
if (loading) {
112+
return (
113+
<div className="min-h-screen flex items-center justify-center">
114+
<Loader2 className="h-8 w-8 animate-spin" />
115+
</div>
116+
)
117+
}
118+
119+
return (
120+
<>
121+
<SEO
122+
title={`${actualCategoryName || formatCategoryName(categoryName)} Problems | CodeQuest`}
123+
description={`Solve ${actualCategoryName || formatCategoryName(categoryName)} coding problems and challenges. Practice algorithms and data structures with problems from LeetCode, GeeksforGeeks, and more.`}
124+
keywords={`${actualCategoryName || categoryName} problems, ${actualCategoryName || categoryName} algorithms, coding challenges, programming practice`}
125+
/>
126+
<div className="container mx-auto px-4 py-8 min-h-screen">
127+
{/* Header with back navigation */}
128+
<div className="mb-8">
129+
<Link
130+
to="/problemset"
131+
className="inline-flex items-center text-sm text-muted-foreground hover:text-primary mb-4"
132+
>
133+
<ArrowLeft className="h-4 w-4 mr-2" />
134+
Back to Problem Set
135+
</Link>
136+
137+
<div className="flex items-center justify-between">
138+
<div>
139+
<h1 className="text-3xl font-bold">{actualCategoryName || formatCategoryName(categoryName)} Problems</h1>
140+
<p className="text-muted-foreground mt-2">
141+
{pagination.totalProblems > 0
142+
? `${pagination.totalProblems} problems available`
143+
: 'No problems found'}
144+
</p>
145+
</div>
146+
</div>
147+
</div>
148+
149+
{/* Problems List */}
150+
{problems.length > 0 ? (
151+
<Card>
152+
<CardHeader>
153+
<CardTitle className="text-lg">Problems</CardTitle>
154+
</CardHeader>
155+
<CardContent className="p-0">
156+
<div className="divide-y">
157+
{problems.map((problem, index) => (
158+
<a
159+
key={problem._id}
160+
href={problem.problemLink}
161+
target="_blank"
162+
rel="noopener noreferrer"
163+
className="flex items-center justify-between p-4 hover:bg-muted/50 transition-colors cursor-pointer"
164+
>
165+
<div className="flex items-center space-x-4 flex-1">
166+
<span className="text-sm text-muted-foreground w-8">
167+
{(pagination.currentPage - 1) * 10 + index + 1}
168+
</span>
169+
<div className="flex-1">
170+
<h3 className="font-medium">{problem.title}</h3>
171+
</div>
172+
</div>
173+
174+
<div className="flex items-center space-x-3">
175+
<Badge
176+
variant="secondary"
177+
className={getDifficultyColor(problem.difficulty)}
178+
>
179+
{problem.difficulty}
180+
</Badge>
181+
182+
<Badge
183+
variant="outline"
184+
className={getPlatformColor(problem.platform)}
185+
>
186+
{problem.platform}
187+
</Badge>
188+
189+
<ExternalLink className="h-4 w-4 text-muted-foreground" />
190+
</div>
191+
</a>
192+
))}
193+
</div>
194+
</CardContent>
195+
</Card>
196+
) : (
197+
<Card>
198+
<CardContent className="text-center py-12">
199+
<p className="text-muted-foreground">No problems found for this category.</p>
200+
</CardContent>
201+
</Card>
202+
)}
203+
204+
{/* Pagination */}
205+
{pagination.totalPages > 1 && (
206+
<div className="flex items-center justify-center space-x-2 mt-8">
207+
<Button
208+
variant="outline"
209+
size="sm"
210+
onClick={() => handlePageChange(currentPage - 1)}
211+
disabled={!pagination.hasPrev}
212+
>
213+
<ChevronLeft className="h-4 w-4" />
214+
Previous
215+
</Button>
216+
217+
<div className="flex items-center space-x-1">
218+
{Array.from({ length: pagination.totalPages }, (_, i) => i + 1)
219+
.filter(page => {
220+
const distance = Math.abs(page - currentPage)
221+
return distance <= 2 || page === 1 || page === pagination.totalPages
222+
})
223+
.map((page, index, array) => (
224+
<div key={page} className="flex items-center">
225+
{index > 0 && array[index - 1] !== page - 1 && (
226+
<span className="px-2 text-muted-foreground">...</span>
227+
)}
228+
<Button
229+
variant={page === currentPage ? "default" : "outline"}
230+
size="sm"
231+
onClick={() => handlePageChange(page)}
232+
className="min-w-[40px]"
233+
>
234+
{page}
235+
</Button>
236+
</div>
237+
))}
238+
</div>
239+
240+
<Button
241+
variant="outline"
242+
size="sm"
243+
onClick={() => handlePageChange(currentPage + 1)}
244+
disabled={!pagination.hasNext}
245+
>
246+
Next
247+
<ChevronRight className="h-4 w-4" />
248+
</Button>
249+
</div>
250+
)}
251+
252+
{/* Results info */}
253+
{pagination.totalProblems > 0 && (
254+
<div className="text-center text-sm text-muted-foreground mt-4">
255+
Showing {(pagination.currentPage - 1) * 10 + 1} - {Math.min(pagination.currentPage * 10, pagination.totalProblems)} of {pagination.totalProblems} problems
256+
</div>
257+
)}
258+
</div>
259+
</>
260+
)
261+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { useEffect, useState } from 'react'
2+
import { Link } from 'react-router-dom'
3+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
4+
import { Badge } from '@/components/ui/badge'
5+
import { Code2, Loader2 } from 'lucide-react'
6+
import SEO from '../SEO/SEO'
7+
8+
interface Category {
9+
name: string
10+
slug: string
11+
}
12+
13+
export default function ProblemSet() {
14+
const [categories, setCategories] = useState<Category[]>([])
15+
const [loading, setLoading] = useState(true)
16+
17+
useEffect(() => {
18+
const fetchCategories = async () => {
19+
try {
20+
const response = await fetch(
21+
`${import.meta.env.VITE_API_BASE_URL}/api/challenges/filter-options`
22+
)
23+
24+
if (!response.ok) throw new Error('Failed to fetch filter options')
25+
26+
const data = await response.json()
27+
28+
if (data.categories && Array.isArray(data.categories)) {
29+
const categoryData = data.categories.map((cat: string) => ({
30+
name: cat,
31+
slug: cat.toLowerCase().replace(/\s+/g, '-')
32+
}))
33+
34+
setCategories(categoryData)
35+
}
36+
37+
setLoading(false)
38+
} catch (error) {
39+
console.error('Failed to fetch categories:', error)
40+
setLoading(false)
41+
}
42+
}
43+
44+
fetchCategories()
45+
}, [])
46+
47+
if (loading) {
48+
return (
49+
<div className="min-h-screen flex items-center justify-center">
50+
<Loader2 className="h-8 w-8 animate-spin" />
51+
</div>
52+
)
53+
}
54+
55+
return (
56+
<>
57+
<SEO
58+
title="Problem Set | Browse Coding Problems by Category"
59+
description="Explore coding problems organized by categories. Find algorithms, data structures, and programming challenges from LeetCode, GeeksforGeeks, and more."
60+
keywords="coding problems, algorithms, data structures, programming categories, leetcode, geeksforgeeks"
61+
/>
62+
<div className="container mx-auto px-4 py-8 min-h-screen">
63+
<div className="text-center mb-8">
64+
<h1 className="text-4xl font-bold mb-4">Problem Set</h1>
65+
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
66+
Browse coding problems organized by categories. Choose a topic to explore related challenges.
67+
</p>
68+
</div>
69+
70+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
71+
{categories.map((category) => (
72+
<Link
73+
key={category.slug}
74+
to={`/problemset/category/${category.slug}?name=${encodeURIComponent(category.name)}`}
75+
className="group"
76+
>
77+
<Card className="h-full transition-all duration-300 hover:shadow-lg hover:-translate-y-1 group-hover:border-primary/50">
78+
<CardHeader className="pb-3">
79+
<div className="flex items-center justify-between">
80+
<Code2 className="h-8 w-8 text-primary" />
81+
<Badge variant="secondary" className="ml-2">
82+
Topic
83+
</Badge>
84+
</div>
85+
</CardHeader>
86+
<CardContent>
87+
<CardTitle className="text-lg group-hover:text-primary transition-colors">
88+
{category.name}
89+
</CardTitle>
90+
<p className="text-sm text-muted-foreground mt-2">
91+
Explore {category.name.toLowerCase()} problems
92+
</p>
93+
</CardContent>
94+
</Card>
95+
</Link>
96+
))}
97+
</div>
98+
</div>
99+
</>
100+
)
101+
}

0 commit comments

Comments
 (0)