UserStatus.jsx 5.1 KB

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