fix(frontend): rewrite FiltersSortingPanel with shadcn, correct styling, useTransition

This commit is contained in:
2026-05-29 00:23:07 +02:00
parent 8229285a2f
commit 5a05968ae9

View File

@@ -1,6 +1,11 @@
"use client"; "use client";
import { useState, useTransition } from "react";
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { FeedSortOption } from "@/lib/api"; import { FeedSortOption } from "@/lib/api";
const SORT_OPTIONS: { value: FeedSortOption; label: string }[] = [ const SORT_OPTIONS: { value: FeedSortOption; label: string }[] = [
@@ -14,112 +19,148 @@ const SORT_OPTIONS: { value: FeedSortOption; label: string }[] = [
export function FiltersSortingPanel() { export function FiltersSortingPanel() {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [isPending, startTransition] = useTransition();
const currentSort = (searchParams.get("sort") ?? "newest") as FeedSortOption; const [sort, setSort] = useState<FeedSortOption>(
const originalsOnly = searchParams.get("originals_only") === "true"; (searchParams.get("sort") as FeedSortOption | null) ?? "newest"
const repliesOnly = searchParams.get("replies_only") === "true"; );
const localOnly = searchParams.get("local_only") === "true"; const [originalsOnly, setOriginalsOnly] = useState(
const hideSensitive = searchParams.get("hide_sensitive") === "true"; searchParams.get("originals_only") === "true"
);
const [repliesOnly, setRepliesOnly] = useState(
searchParams.get("replies_only") === "true"
);
const [localOnly, setLocalOnly] = useState(
searchParams.get("local_only") === "true"
);
const [hideSensitive, setHideSensitive] = useState(
searchParams.get("hide_sensitive") === "true"
);
function update(key: string, value: string | null) { function pushParams(updates: Record<string, string | null>) {
const params = new URLSearchParams(searchParams.toString()); const params = new URLSearchParams(searchParams.toString());
params.delete("page"); params.delete("page");
for (const [key, value] of Object.entries(updates)) {
if (value === null) { if (value === null) {
params.delete(key); params.delete(key);
} else { } else {
params.set(key, value); params.set(key, value);
} }
router.push(`/?${params.toString()}`); }
startTransition(() => router.replace(`/?${params.toString()}`));
} }
function setSort(value: FeedSortOption) { function handleSort(value: FeedSortOption) {
update("sort", value === "newest" ? null : value); setSort(value);
pushParams({ sort: value === "newest" ? null : value });
} }
function toggleFilter(key: string, current: boolean) { function handleOriginalsOnly(checked: boolean) {
update(key, current ? null : "true"); setOriginalsOnly(checked);
if (checked) setRepliesOnly(false);
const updates: Record<string, string | null> = {
originals_only: checked ? "true" : null,
};
if (checked) updates.replies_only = null;
pushParams(updates);
}
function handleRepliesOnly(checked: boolean) {
setRepliesOnly(checked);
if (checked) setOriginalsOnly(false);
const updates: Record<string, string | null> = {
replies_only: checked ? "true" : null,
};
if (checked) updates.originals_only = null;
pushParams(updates);
}
function handleLocalOnly(checked: boolean) {
setLocalOnly(checked);
pushParams({ local_only: checked ? "true" : null });
}
function handleHideSensitive(checked: boolean) {
setHideSensitive(checked);
pushParams({ hide_sensitive: checked ? "true" : null });
} }
return ( return (
<div className="space-y-4"> <div
className={`space-y-3 transition-opacity duration-150 ${
isPending ? "opacity-50 pointer-events-none" : ""
}`}
>
<div> <div>
<h3 className="text-sm font-semibold mb-2">Sort by</h3> <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-2">
<div className="space-y-1"> Sort by
</p>
<RadioGroup
value={sort}
onValueChange={(v) => handleSort(v as FeedSortOption)}
className="space-y-1"
>
{SORT_OPTIONS.map((opt) => ( {SORT_OPTIONS.map((opt) => (
<label key={opt.value} className="flex items-center gap-2 cursor-pointer"> <div key={opt.value} className="flex items-center gap-2">
<input <RadioGroupItem value={opt.value} id={`sort-${opt.value}`} />
type="radio" <Label
name="feed-sort" htmlFor={`sort-${opt.value}`}
value={opt.value} className="text-xs font-normal cursor-pointer"
checked={currentSort === opt.value} >
onChange={() => setSort(opt.value)} {opt.label}
className="accent-primary" </Label>
/>
<span className="text-sm">{opt.label}</span>
</label>
))}
</div> </div>
))}
</RadioGroup>
</div> </div>
<Separator />
<div> <div>
<h3 className="text-sm font-semibold mb-2">Filter</h3> <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-2">
<div className="space-y-1"> Filter
<label className="flex items-center gap-2 cursor-pointer"> </p>
<input <div className="space-y-2">
type="checkbox" <div className="flex items-center gap-2">
<Checkbox
id="originals-only"
checked={originalsOnly} checked={originalsOnly}
onChange={() => { onCheckedChange={(c) => handleOriginalsOnly(c === true)}
const params = new URLSearchParams(searchParams.toString());
params.delete("page");
if (!originalsOnly) {
params.delete("replies_only");
params.set("originals_only", "true");
} else {
params.delete("originals_only");
}
router.push(`/?${params.toString()}`);
}}
className="accent-primary"
/> />
<span className="text-sm">Originals only</span> <Label htmlFor="originals-only" className="text-xs font-normal cursor-pointer">
</label> Originals only
<label className="flex items-center gap-2 cursor-pointer"> </Label>
<input </div>
type="checkbox" <div className="flex items-center gap-2">
<Checkbox
id="replies-only"
checked={repliesOnly} checked={repliesOnly}
onChange={() => { onCheckedChange={(c) => handleRepliesOnly(c === true)}
const params = new URLSearchParams(searchParams.toString());
params.delete("page");
if (!repliesOnly) {
params.delete("originals_only");
params.set("replies_only", "true");
} else {
params.delete("replies_only");
}
router.push(`/?${params.toString()}`);
}}
className="accent-primary"
/> />
<span className="text-sm">Replies only</span> <Label htmlFor="replies-only" className="text-xs font-normal cursor-pointer">
</label> Replies only
<label className="flex items-center gap-2 cursor-pointer"> </Label>
<input </div>
type="checkbox" <div className="flex items-center gap-2">
<Checkbox
id="local-only"
checked={localOnly} checked={localOnly}
onChange={() => toggleFilter("local_only", localOnly)} onCheckedChange={(c) => handleLocalOnly(c === true)}
className="accent-primary"
/> />
<span className="text-sm">Local only</span> <Label htmlFor="local-only" className="text-xs font-normal cursor-pointer">
</label> Local only
<label className="flex items-center gap-2 cursor-pointer"> </Label>
<input </div>
type="checkbox" <div className="flex items-center gap-2">
<Checkbox
id="hide-sensitive"
checked={hideSensitive} checked={hideSensitive}
onChange={() => toggleFilter("hide_sensitive", hideSensitive)} onCheckedChange={(c) => handleHideSensitive(c === true)}
className="accent-primary"
/> />
<span className="text-sm">Hide sensitive</span> <Label htmlFor="hide-sensitive" className="text-xs font-normal cursor-pointer">
</label> Hide sensitive
</Label>
</div>
</div> </div>
</div> </div>
</div> </div>