1
0
Fork 0
polaris-task-force/src/components/frontend/dashboard/MarkNotificationReadButton.tsx
Z8MB1E f0af778713 feat(dashboard): customizable dashboard with personal widgets
- 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
2026-09-10 20:03:27 -04:00

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>
);
}