UserStatus.jsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. "use client";
  2. import React from "react";
  3. import Link from "next/link";
  4. import { LifeBuoy, LogOut, 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. function formatRole(role) {
  18. if (role === "branch") return "Niederlassung";
  19. if (role === "admin") return "Admin";
  20. if (role === "dev") return "Entwicklung";
  21. return role ? String(role) : "Unbekannt";
  22. }
  23. /**
  24. * Build a mailto link using encodeURIComponent (NOT URLSearchParams).
  25. *
  26. * Reason:
  27. * - Some mail clients treat "+" literally in mailto query strings.
  28. * - encodeURIComponent produces "%20" for spaces, which is handled reliably.
  29. */
  30. function buildSupportMailto({ user, currentUrl }) {
  31. const to = "info@attus.de";
  32. const subject = "Support – RHL Lieferscheine";
  33. const roleLabel = user ? formatRole(user.role) : "Unbekannt";
  34. const userLabel = user?.branchId
  35. ? `${roleLabel} (${user.branchId})`
  36. : roleLabel;
  37. const urlLine = currentUrl ? `URL: ${currentUrl}` : "URL: (bitte einfügen)";
  38. const body = [
  39. "Hallo attus Support,",
  40. "",
  41. "bitte beschreibt hier kurz euer Anliegen:",
  42. "",
  43. "- Was wollten Sie tun?",
  44. "- Was ist passiert?",
  45. "- (Optional) Screenshot / Zeitpunkt",
  46. "",
  47. urlLine,
  48. `Benutzer: ${userLabel}`,
  49. "",
  50. "Vielen Dank.",
  51. ].join("\r\n");
  52. return `mailto:${to}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(
  53. body,
  54. )}`;
  55. }
  56. export default function UserStatus() {
  57. const { status, user } = useAuth();
  58. const isAuthenticated = status === "authenticated" && user;
  59. let text = "Nicht geladen";
  60. if (status === "loading") text = "Lädt…";
  61. if (isAuthenticated) {
  62. const roleLabel = formatRole(user.role);
  63. text = user.branchId ? `${roleLabel} (${user.branchId})` : roleLabel;
  64. }
  65. if (status === "unauthenticated") text = "Abgemeldet";
  66. if (status === "error") text = "Fehler";
  67. const currentUrl = typeof window !== "undefined" ? window.location.href : "";
  68. const supportMailto = buildSupportMailto({ user, currentUrl });
  69. async function handleLogout() {
  70. try {
  71. await logout();
  72. } catch (err) {
  73. // Logout is idempotent; we still redirect for predictable UX.
  74. console.error("[UserStatus] logout failed:", err);
  75. }
  76. const loginUrl = buildLoginUrl({ reason: LOGIN_REASONS.LOGGED_OUT });
  77. window.location.replace(loginUrl);
  78. }
  79. return (
  80. <DropdownMenu>
  81. <DropdownMenuTrigger asChild>
  82. <Button
  83. type="button"
  84. variant="ghost"
  85. size="sm"
  86. className="gap-2"
  87. aria-label="Benutzermenü öffnen"
  88. title="Benutzermenü"
  89. >
  90. <User className="h-4 w-4" aria-hidden="true" />
  91. <span className="hidden text-xs md:inline">{text}</span>
  92. </Button>
  93. </DropdownMenuTrigger>
  94. <DropdownMenuContent align="end" className="min-w-56">
  95. <DropdownMenuLabel>Benutzer</DropdownMenuLabel>
  96. <div className="px-2 pb-2 text-xs text-muted-foreground">
  97. {isAuthenticated
  98. ? `Angemeldet als: ${text}`
  99. : "Keine aktive Sitzung."}
  100. </div>
  101. <DropdownMenuSeparator />
  102. <DropdownMenuItem asChild disabled={!isAuthenticated}>
  103. <Link href="/profile" className="flex w-full items-center gap-2">
  104. <User className="h-4 w-4" aria-hidden="true" />
  105. Profil
  106. </Link>
  107. </DropdownMenuItem>
  108. <DropdownMenuItem asChild>
  109. <a
  110. href={supportMailto}
  111. className="flex w-full items-center gap-2"
  112. title="Support kontaktieren"
  113. >
  114. <LifeBuoy className="h-4 w-4" aria-hidden="true" />
  115. Support
  116. </a>
  117. </DropdownMenuItem>
  118. <DropdownMenuSeparator />
  119. <DropdownMenuItem
  120. variant="destructive"
  121. disabled={!isAuthenticated}
  122. onSelect={(e) => {
  123. e.preventDefault();
  124. handleLogout();
  125. }}
  126. >
  127. <LogOut className="h-4 w-4" aria-hidden="true" />
  128. Abmelden
  129. </DropdownMenuItem>
  130. </DropdownMenuContent>
  131. </DropdownMenu>
  132. );
  133. }