difficulty filters and clickable badges

This commit is contained in:
2025-05-21 18:52:44 +01:00
parent 02fc080bf8
commit c5cd007c8f
2 changed files with 59 additions and 79 deletions

View File

@@ -39,12 +39,15 @@ export default async function QuestionDetailPage({
<div> <div>
<div className="flex items-start justify-between gap-4 mb-4"> <div className="flex items-start justify-between gap-4 mb-4">
<h1 className="text-3xl font-bold">{question.title}</h1> <h1 className="text-3xl font-bold">{question.title}</h1>
<Link href={`/questions?difficulty=${question.difficulty}`}>
<Badge <Badge
variant={getDifficultyVariant(question.difficulty)} variant={getDifficultyVariant(question.difficulty)}
aria-label={getDifficultyLabel(question.difficulty)} aria-label={getDifficultyLabel(question.difficulty)}
className="cursor-pointer hover:opacity-80"
> >
{capitalize(question.difficulty)} {capitalize(question.difficulty)}
</Badge> </Badge>
</Link>
</div> </div>
<div className="flex flex-wrap gap-2 mb-4"> <div className="flex flex-wrap gap-2 mb-4">

View File

@@ -1,5 +1,9 @@
import { getQuestions, getCategories, getPatterns } from "@/lib/api"; import { getQuestions, getCategories, getPatterns } from "@/lib/api";
import type { CategoryListResponse, PatternListResponse, Difficulty } from "@/types";
import { QuestionCard } from "@/components/questions/question-card"; import { QuestionCard } from "@/components/questions/question-card";
import { QuestionFilters } from "@/components/questions/question-filters";
import { Badge } from "@/components/ui/badge";
import { getDifficultyVariant, capitalize } from "@/lib/utils";
import Link from "next/link"; import Link from "next/link";
interface SearchParams { interface SearchParams {
@@ -16,8 +20,8 @@ export default async function QuestionsPage({
searchParams: Promise<SearchParams>; searchParams: Promise<SearchParams>;
}) { }) {
const params = await searchParams; const params = await searchParams;
const [questionsResponse, categoriesResponse, patternsResponse] = const [questionsResult, categoriesResult, patternsResult] =
await Promise.all([ await Promise.allSettled([
getQuestions({ getQuestions({
difficulty: params.difficulty, difficulty: params.difficulty,
category: params.category, category: params.category,
@@ -29,7 +33,28 @@ export default async function QuestionsPage({
getPatterns(), getPatterns(),
]); ]);
const difficulties = ["easy", "medium", "hard"]; if (questionsResult.status === "rejected") {
return (
<div className="space-y-6">
<h1 className="text-3xl font-bold">Questions</h1>
<p className="text-[var(--muted-foreground)]">
Unable to load questions. Please try again later.
</p>
</div>
);
}
const questionsResponse = questionsResult.value;
const categoriesResponse: CategoryListResponse =
categoriesResult.status === "fulfilled"
? categoriesResult.value
: { items: [] };
const patternsResponse: PatternListResponse =
patternsResult.status === "fulfilled"
? patternsResult.value
: { items: [] };
const difficulties: Difficulty[] = ["easy", "medium", "hard"];
return ( return (
<div className="space-y-6"> <div className="space-y-6">
@@ -41,83 +66,35 @@ export default async function QuestionsPage({
Difficulty Difficulty
</label> </label>
<div className="flex gap-2"> <div className="flex gap-2">
<Link <Link href="/questions">
href="/questions" <Badge
className={`px-3 py-1 rounded text-sm ${ variant={!params.difficulty ? "default" : "outline"}
!params.difficulty className="cursor-pointer hover:opacity-80"
? "bg-[var(--primary)] text-[var(--primary-foreground)]"
: "bg-[var(--card)] hover:bg-[var(--muted)]"
}`}
> >
All All
</Badge>
</Link> </Link>
{difficulties.map((d) => ( {difficulties.map((d) => (
<Link <Link key={d} href={`/questions?difficulty=${d}`}>
key={d} <Badge
href={`/questions?difficulty=${d}`} variant={
className={`px-3 py-1 rounded text-sm capitalize ${ params.difficulty === d ? getDifficultyVariant(d) : "outline"
params.difficulty === d }
? "bg-[var(--primary)] text-[var(--primary-foreground)]" className="cursor-pointer hover:opacity-80"
: "bg-[var(--card)] hover:bg-[var(--muted)]"
}`}
> >
{d} {capitalize(d)}
</Badge>
</Link> </Link>
))} ))}
</div> </div>
</div> </div>
<div className="space-y-1"> <QuestionFilters
<label className="text-sm text-[var(--muted-foreground)]"> categories={categoriesResponse.items}
Category patterns={patternsResponse.items}
</label> currentCategory={params.category}
<select currentPattern={params.pattern}
defaultValue={params.category || ""} />
className="px-3 py-1 rounded text-sm bg-[var(--card)] border border-[var(--border)]"
onChange={(e) => {
const url = new URL(window.location.href);
if (e.target.value) {
url.searchParams.set("category", e.target.value);
} else {
url.searchParams.delete("category");
}
window.location.href = url.toString();
}}
>
<option value="">All Categories</option>
{categoriesResponse.items.map((cat) => (
<option key={cat.id} value={cat.slug}>
{cat.name} ({cat.question_count})
</option>
))}
</select>
</div>
<div className="space-y-1">
<label className="text-sm text-[var(--muted-foreground)]">
Pattern
</label>
<select
defaultValue={params.pattern || ""}
className="px-3 py-1 rounded text-sm bg-[var(--card)] border border-[var(--border)]"
onChange={(e) => {
const url = new URL(window.location.href);
if (e.target.value) {
url.searchParams.set("pattern", e.target.value);
} else {
url.searchParams.delete("pattern");
}
window.location.href = url.toString();
}}
>
<option value="">All Patterns</option>
{patternsResponse.items.map((pat) => (
<option key={pat.id} value={pat.slug}>
{pat.name} ({pat.question_count})
</option>
))}
</select>
</div>
</div> </div>
<div className="text-sm text-[var(--muted-foreground)]"> <div className="text-sm text-[var(--muted-foreground)]">