- Scope all reading progress (books, series, stats) by user via Option<Extension<AuthUser>> — admin sees aggregate, read token sees own data - Fix duplicate book rows when admin views lists (IS NOT NULL guard on JOIN) - Add X-As-User header support: admin can impersonate any user from backoffice - UserSwitcher dropdown in nav header (persisted via as_user_id cookie) - Per-user filter pills on "Currently reading" and "Recently read" dashboard sections - Inline username editing (UsernameEdit component with optimistic update) - PATCH /admin/users/:id endpoint to rename a user - Unassigned read tokens row in users table - Komga sync now requires a user_id — reading progress attributed to selected user - Migration 0051: add user_id column to komga_sync_reports - Nav breakpoints: icons-only from md, labels from xl, hamburger until md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
39 lines
1.3 KiB
TypeScript
39 lines
1.3 KiB
TypeScript
"use client";
|
|
|
|
import { useOptimistic, useTransition } from "react";
|
|
|
|
interface TokenUserSelectProps {
|
|
tokenId: string;
|
|
currentUserId?: string;
|
|
users: { id: string; username: string }[];
|
|
action: (formData: FormData) => Promise<void>;
|
|
noUserLabel: string;
|
|
}
|
|
|
|
export function TokenUserSelect({ tokenId, currentUserId, users, action, noUserLabel }: TokenUserSelectProps) {
|
|
const [optimisticValue, setOptimisticValue] = useOptimistic(currentUserId ?? "");
|
|
const [, startTransition] = useTransition();
|
|
|
|
return (
|
|
<select
|
|
value={optimisticValue}
|
|
onChange={(e) => {
|
|
const newValue = e.target.value;
|
|
startTransition(async () => {
|
|
setOptimisticValue(newValue);
|
|
const fd = new FormData();
|
|
fd.append("id", tokenId);
|
|
fd.append("user_id", newValue);
|
|
await action(fd);
|
|
});
|
|
}}
|
|
className="flex h-8 rounded-md border border-input bg-background px-2 py-0 text-xs shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
>
|
|
<option value="">{noUserLabel}</option>
|
|
{users.map((u) => (
|
|
<option key={u.id} value={u.id}>{u.username}</option>
|
|
))}
|
|
</select>
|
|
);
|
|
}
|