UserStatus.jsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. "use client";
  2. import React from "react";
  3. import Link from "next/link";
  4. import { usePathname } from "next/navigation";
  5. import { LifeBuoy, LogOut, User, Users } from "lucide-react";
  6. import { cn } from "@/lib/utils";
  7. import { useAuth } from "@/components/auth/authContext";
  8. import { logout } from "@/lib/frontend/apiClient";
  9. import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
  10. import { canManageUsers as canManageUsersRole } from "@/lib/frontend/auth/roles";
  11. import { Button } from "@/components/ui/button";
  12. import {
  13. DropdownMenu,
  14. DropdownMenuContent,
  15. DropdownMenuItem,
  16. DropdownMenuLabel,
  17. DropdownMenuSeparator,
  18. DropdownMenuTrigger,
  19. } from "@/components/ui/dropdown-menu";
  20. import {
  21. Tooltip,
  22. TooltipContent,
  23. TooltipTrigger,
  24. } from "@/components/ui/tooltip";
  25. function formatRole(role) {
  26. if (role === "branch") return "Niederlassung";
  27. if (role === "admin") return "Admin";
  28. if (role === "superadmin") return "Superadmin";
  29. if (role === "dev") return "Entwicklung";
  30. return role ? String(role) : "Unbekannt";
  31. }
  32. function buildSupportMailto({ user, currentUrl, pathname, userAgent }) {
  33. const to = "info@attus.de";
  34. const roleLabel = user ? formatRole(user.role) : "Unbekannt";
  35. const userLabel = user?.branchId
  36. ? `${roleLabel} (${user.branchId})`
  37. : roleLabel;
  38. const now = new Date();
  39. const tz =
  40. typeof Intl !== "undefined"
  41. ? Intl.DateTimeFormat().resolvedOptions().timeZone
  42. : "";
  43. const timestampLocal = now.toLocaleString("de-DE");
  44. const timestampIso = now.toISOString();
  45. const routeLine = pathname ? `Route: ${pathname}` : "Route: (unbekannt)";
  46. const urlLine = currentUrl ? `URL: ${currentUrl}` : "URL: (bitte einfügen)";
  47. const uaLine = userAgent
  48. ? `User-Agent: ${userAgent}`
  49. : "User-Agent: (unbekannt)";
  50. const timeLine = tz
  51. ? `Zeitpunkt: ${timestampLocal} (${tz})`
  52. : `Zeitpunkt: ${timestampLocal}`;
  53. const isoLine = `ISO: ${timestampIso}`;
  54. const subject = user?.branchId
  55. ? `Support – RHL Lieferscheine (${user.branchId})`
  56. : "Support – RHL Lieferscheine";
  57. const body = [
  58. "Hallo attus Support,",
  59. "",
  60. "bitte beschreibt hier kurz das Anliegen:",
  61. "",
  62. "- Was wollten Sie tun?",
  63. "- Was ist passiert?",
  64. "- (Optional) Screenshot / Zeitpunkt",
  65. "",
  66. "--- Kontext (bitte drin lassen) ---",
  67. `Benutzer: ${userLabel}`,
  68. routeLine,
  69. urlLine,
  70. timeLine,
  71. isoLine,
  72. uaLine,
  73. "----------------------------------",
  74. "",
  75. "Vielen Dank.",
  76. ].join("\r\n");
  77. return `mailto:${to}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(
  78. body,
  79. )}`;
  80. }
  81. export default function UserStatus() {
  82. const pathname = usePathname() || "/";
  83. const { status, user } = useAuth();
  84. const isAuthenticated = status === "authenticated" && user;
  85. const canManageUsers = isAuthenticated && canManageUsersRole(user.role);
  86. let text = "Nicht geladen";
  87. if (status === "loading") text = "Lädt…";
  88. if (isAuthenticated) {
  89. const roleLabel = formatRole(user.role);
  90. text = user.branchId ? `${roleLabel} (${user.branchId})` : roleLabel;
  91. }
  92. if (status === "unauthenticated") text = "Abgemeldet";
  93. if (status === "error") text = "Fehler";
  94. const currentUrl = typeof window !== "undefined" ? window.location.href : "";
  95. const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
  96. const supportMailto = buildSupportMailto({
  97. user,
  98. currentUrl,
  99. pathname,
  100. userAgent,
  101. });
  102. const isProfileActive =
  103. pathname === "/profile" || pathname.startsWith("/profile/");
  104. async function handleLogout() {
  105. try {
  106. await logout();
  107. } catch (err) {
  108. console.error("[UserStatus] logout failed:", err);
  109. }
  110. const loginUrl = buildLoginUrl({ reason: LOGIN_REASONS.LOGGED_OUT });
  111. window.location.replace(loginUrl);
  112. }
  113. return (
  114. <DropdownMenu>
  115. <Tooltip>
  116. <TooltipTrigger asChild>
  117. <DropdownMenuTrigger asChild>
  118. <Button
  119. type="button"
  120. variant="ghost"
  121. size="sm"
  122. aria-label="Benutzermenü öffnen"
  123. className={cn(
  124. "gap-2",
  125. "px-2 md:px-3",
  126. isProfileActive ? "bg-accent" : "",
  127. )}
  128. >
  129. <User className="h-4 w-4" aria-hidden="true" />
  130. <span className="hidden text-xs md:inline">{text}</span>
  131. </Button>
  132. </DropdownMenuTrigger>
  133. </TooltipTrigger>
  134. <TooltipContent side="bottom">Benutzermenü</TooltipContent>
  135. </Tooltip>
  136. <DropdownMenuContent align="end" className="min-w-56">
  137. <DropdownMenuLabel>Benutzer</DropdownMenuLabel>
  138. <div className="px-2 pb-2 text-xs text-muted-foreground">
  139. {isAuthenticated
  140. ? `Angemeldet als: ${text}`
  141. : "Keine aktive Sitzung."}
  142. </div>
  143. <DropdownMenuSeparator />
  144. <DropdownMenuItem asChild disabled={!isAuthenticated}>
  145. <Link href="/profile" className="flex w-full items-center gap-2">
  146. <User className="h-4 w-4" aria-hidden="true" />
  147. Profil
  148. </Link>
  149. </DropdownMenuItem>
  150. {canManageUsers ? (
  151. <DropdownMenuItem asChild>
  152. <Link
  153. href="/admin/users"
  154. className="flex w-full items-center gap-2"
  155. >
  156. <Users className="h-4 w-4" aria-hidden="true" />
  157. Benutzerverwaltung
  158. </Link>
  159. </DropdownMenuItem>
  160. ) : null}
  161. <DropdownMenuItem asChild>
  162. <a href={supportMailto} className="flex w-full items-center gap-2">
  163. <LifeBuoy className="h-4 w-4" aria-hidden="true" />
  164. Support
  165. </a>
  166. </DropdownMenuItem>
  167. <DropdownMenuSeparator />
  168. <DropdownMenuItem
  169. variant="destructive"
  170. disabled={!isAuthenticated}
  171. onSelect={(e) => {
  172. e.preventDefault();
  173. handleLogout();
  174. }}
  175. >
  176. <LogOut className="h-4 w-4" aria-hidden="true" />
  177. Abmelden
  178. </DropdownMenuItem>
  179. </DropdownMenuContent>
  180. </DropdownMenu>
  181. );
  182. }