difficulty filters and clickable badges
This commit is contained in:
@@ -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>
|
||||||
<Badge
|
<Link href={`/questions?difficulty=${question.difficulty}`}>
|
||||||
variant={getDifficultyVariant(question.difficulty)}
|
<Badge
|
||||||
aria-label={getDifficultyLabel(question.difficulty)}
|
variant={getDifficultyVariant(question.difficulty)}
|
||||||
>
|
aria-label={getDifficultyLabel(question.difficulty)}
|
||||||
{capitalize(question.difficulty)}
|
className="cursor-pointer hover:opacity-80"
|
||||||
</Badge>
|
>
|
||||||
|
{capitalize(question.difficulty)}
|
||||||
|
</Badge>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2 mb-4">
|
<div className="flex flex-wrap gap-2 mb-4">
|
||||||
|
|||||||
@@ -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
|
||||||
}`}
|
</Badge>
|
||||||
>
|
|
||||||
All
|
|
||||||
</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)]"
|
>
|
||||||
}`}
|
{capitalize(d)}
|
||||||
>
|
</Badge>
|
||||||
{d}
|
|
||||||
</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)]">
|
||||||
|
|||||||
Reference in New Issue
Block a user