- Drag-and-drop widget arrangement (main two-column zone + sidebar) with per-widget span resize, persisted to Users.preferences.dashboard.layout via saveDashboardLayout (migration included) - New personal widgets: Wallet, Open Negotiations, My Tickets (View all), Recent Notifications (per-row mark as read) - Widgets that are empty or not visible to the viewer are omitted so they never render as empty drag targets - Registers both pending migrations and regenerates payload types/schema
55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { CheckIcon } from "lucide-react";
|
|
import { toast } from "sonner";
|
|
import { markNotificationsRead } from "@/app/(frontend)/notifications/actions";
|
|
|
|
interface MarkNotificationReadButtonProps {
|
|
readonly notificationId: number;
|
|
readonly title: string;
|
|
}
|
|
|
|
/**
|
|
* Inline "mark as read" affordance for dashboard notification rows. Calls the
|
|
* shared mark-read server action (scoped to the signed-in user's own
|
|
* notifications) and refreshes the server-rendered widget afterwards so the
|
|
* unread badge and row styling update.
|
|
*/
|
|
export function MarkNotificationReadButton({
|
|
notificationId,
|
|
title,
|
|
}: MarkNotificationReadButtonProps) {
|
|
const router = useRouter();
|
|
const [pending, setPending] = useState(false);
|
|
|
|
async function markRead() {
|
|
if (pending) return;
|
|
setPending(true);
|
|
try {
|
|
const result = await markNotificationsRead([notificationId]);
|
|
if (!result.success) {
|
|
toast.error(result.error ?? "Failed to update notifications.");
|
|
}
|
|
} catch (error) {
|
|
toast.error(error instanceof Error ? error.message : "Failed to update notifications.");
|
|
} finally {
|
|
setPending(false);
|
|
router.refresh();
|
|
}
|
|
}
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
title="Mark as read"
|
|
aria-label={`Mark "${title}" as read`}
|
|
disabled={pending}
|
|
onClick={() => void markRead()}
|
|
className="mt-1 shrink-0 rounded-sm p-0.5 text-muted-foreground/60 transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
>
|
|
<CheckIcon className="size-3.5" />
|
|
</button>
|
|
);
|
|
}
|