UserStatus.jsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. "use client";
  2. import React from "react";
  3. import Link from "next/link";
  4. import { KeyRound, LogOut, Settings, User } from "lucide-react";
  5. import { useAuth } from "@/components/auth/authContext";
  6. import { logout } from "@/lib/frontend/apiClient";
  7. import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
  8. import { Button } from "@/components/ui/button";
  9. import {
  10. DropdownMenu,
  11. DropdownMenuContent,
  12. DropdownMenuItem,
  13. DropdownMenuLabel,
  14. DropdownMenuSeparator,
  15. DropdownMenuTrigger,
  16. } from "@/components/ui/dropdown-menu";
  17. /**
  18. * UserStatus (RHL-020)
  19. *
  20. * Updated responsibilities (RHL-032):
  21. * - Display minimal session info in the TopNav.
  22. * - Act as a user action menu trigger (settings/logout).
  23. *
  24. * UX rule:
  25. * - All user-facing strings must be German.
  26. */
  27. export default function UserStatus() {
  28. const { status, user } = useAuth();
  29. function formatRole(role) {
  30. if (role === "branch") return "Niederlassung";
  31. if (role === "admin") return "Admin";
  32. if (role === "dev") return "Entwicklung";
  33. return role ? String(role) : "Unbekannt";
  34. }
  35. const isAuthenticated = status === "authenticated" && user;
  36. let text = "Nicht geladen";
  37. if (status === "loading") text = "Lädt…";
  38. if (isAuthenticated) {
  39. const roleLabel = formatRole(user.role);
  40. text = user.branchId ? `${roleLabel} (${user.branchId})` : roleLabel;
  41. }
  42. if (status === "unauthenticated") text = "Abgemeldet";
  43. if (status === "error") text = "Fehler";
  44. async function handleLogout() {
  45. try {
  46. await logout();
  47. } catch (err) {
  48. // Logout is idempotent; we still redirect to login for predictable UX.
  49. console.error("[UserStatus] logout failed:", err);
  50. }
  51. const loginUrl = buildLoginUrl({ reason: LOGIN_REASONS.LOGGED_OUT });
  52. window.location.replace(loginUrl);
  53. }
  54. return (
  55. <DropdownMenu>
  56. <DropdownMenuTrigger asChild>
  57. <Button
  58. type="button"
  59. variant="ghost"
  60. size="sm"
  61. className="gap-2"
  62. aria-label="Benutzermenü öffnen"
  63. title="Benutzermenü"
  64. >
  65. <User className="h-4 w-4" aria-hidden="true" />
  66. {/* Keep the label visible on md+ like before, but keep menu accessible on mobile */}
  67. <span className="hidden text-xs md:inline">{text}</span>
  68. </Button>
  69. </DropdownMenuTrigger>
  70. <DropdownMenuContent align="end" className="min-w-56">
  71. <DropdownMenuLabel>Benutzer</DropdownMenuLabel>
  72. <div className="px-2 pb-2 text-xs text-muted-foreground">
  73. {isAuthenticated
  74. ? `Angemeldet als: ${text}`
  75. : "Keine aktive Sitzung."}
  76. </div>
  77. <DropdownMenuSeparator />
  78. <DropdownMenuItem asChild disabled={!isAuthenticated}>
  79. <Link href="/settings" className="flex w-full items-center gap-2">
  80. <Settings className="h-4 w-4" aria-hidden="true" />
  81. Einstellungen
  82. </Link>
  83. </DropdownMenuItem>
  84. <DropdownMenuItem disabled title="Kommt später">
  85. <KeyRound className="h-4 w-4" aria-hidden="true" />
  86. Passwort ändern
  87. </DropdownMenuItem>
  88. <DropdownMenuSeparator />
  89. <DropdownMenuItem
  90. variant="destructive"
  91. disabled={!isAuthenticated}
  92. onSelect={(e) => {
  93. e.preventDefault();
  94. handleLogout();
  95. }}
  96. >
  97. <LogOut className="h-4 w-4" aria-hidden="true" />
  98. Abmelden
  99. </DropdownMenuItem>
  100. </DropdownMenuContent>
  101. </DropdownMenu>
  102. );
  103. }