Improve family invitations and absence management flows

This commit is contained in:
t.indorf
2026-10-09 22:59:36 +02:00
parent 4702bf5abb
commit 8915fe7df6
29 changed files with 2803 additions and 617 deletions
@@ -0,0 +1,73 @@
"use client";
import { useTransition } from "react";
import { Mail } from "lucide-react";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import type { FamilyInvitationView } from "@/lib/family-invitation-view";
import { resendFamilyInvitationAction } from "./actions";
const labels = {
active: "Konto aktiviert",
open: "Aktivierung offen",
sending: "Versand läuft",
sent: "Einladung versendet",
failed: "Versand fehlgeschlagen",
expired: "Einladung abgelaufen",
};
export function InvitationStatus({
userId,
name,
invitation,
blocked = false,
}: {
userId: string;
name: string;
invitation: FamilyInvitationView;
blocked?: boolean;
}) {
const [pending, startTransition] = useTransition();
return (
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs">
<Badge variant={invitation.status === "active" ? "success" : "warning"}>
{labels[invitation.status]}
</Badge>
{invitation.status !== "active" && (
<>
{invitation.expiresAt && (
<span className="text-[var(--ink-muted)]">
{invitation.status === "expired"
? "Gültig gewesen bis"
: "Link gültig bis"}{" "}
{new Intl.DateTimeFormat("de-DE", {
dateStyle: "short",
timeZone: "Europe/Berlin",
}).format(new Date(invitation.expiresAt))}
</span>
)}
<Button
type="button"
variant="outline"
size="sm"
className="h-7 text-xs"
disabled={blocked || pending || invitation.status === "sending"}
aria-label={`Einladung erneut senden an ${name}`}
onClick={() =>
startTransition(async () => {
const result = await resendFamilyInvitationAction(userId);
if (result.error) toast.error(result.error);
else if (result.warning) toast.warning(result.warning);
else toast.success("Einladung versendet.");
})
}
>
<Mail className="h-3 w-3" />
{pending ? "Wird versendet…" : "Einladung erneut senden"}
</Button>
</>
)}
</div>
);
}