|
@@ -2,8 +2,11 @@
|
|
|
|
|
|
|
|
import React from "react";
|
|
import React from "react";
|
|
|
import Link from "next/link";
|
|
import Link from "next/link";
|
|
|
|
|
+import { usePathname } from "next/navigation";
|
|
|
import { LifeBuoy, LogOut, User } from "lucide-react";
|
|
import { LifeBuoy, LogOut, User } from "lucide-react";
|
|
|
|
|
|
|
|
|
|
+import { cn } from "@/lib/utils";
|
|
|
|
|
+
|
|
|
import { useAuth } from "@/components/auth/authContext";
|
|
import { useAuth } from "@/components/auth/authContext";
|
|
|
import { logout } from "@/lib/frontend/apiClient";
|
|
import { logout } from "@/lib/frontend/apiClient";
|
|
|
import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
|
|
import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
|
|
@@ -17,6 +20,11 @@ import {
|
|
|
DropdownMenuSeparator,
|
|
DropdownMenuSeparator,
|
|
|
DropdownMenuTrigger,
|
|
DropdownMenuTrigger,
|
|
|
} from "@/components/ui/dropdown-menu";
|
|
} from "@/components/ui/dropdown-menu";
|
|
|
|
|
+import {
|
|
|
|
|
+ Tooltip,
|
|
|
|
|
+ TooltipContent,
|
|
|
|
|
+ TooltipTrigger,
|
|
|
|
|
+} from "@/components/ui/tooltip";
|
|
|
|
|
|
|
|
function formatRole(role) {
|
|
function formatRole(role) {
|
|
|
if (role === "branch") return "Niederlassung";
|
|
if (role === "branch") return "Niederlassung";
|
|
@@ -25,35 +33,54 @@ function formatRole(role) {
|
|
|
return role ? String(role) : "Unbekannt";
|
|
return role ? String(role) : "Unbekannt";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * Build a mailto link using encodeURIComponent (NOT URLSearchParams).
|
|
|
|
|
- *
|
|
|
|
|
- * Reason:
|
|
|
|
|
- * - Some mail clients treat "+" literally in mailto query strings.
|
|
|
|
|
- * - encodeURIComponent produces "%20" for spaces, which is handled reliably.
|
|
|
|
|
- */
|
|
|
|
|
-function buildSupportMailto({ user, currentUrl }) {
|
|
|
|
|
|
|
+function buildSupportMailto({ user, currentUrl, pathname, userAgent }) {
|
|
|
const to = "info@attus.de";
|
|
const to = "info@attus.de";
|
|
|
- const subject = "Support – RHL Lieferscheine";
|
|
|
|
|
|
|
|
|
|
const roleLabel = user ? formatRole(user.role) : "Unbekannt";
|
|
const roleLabel = user ? formatRole(user.role) : "Unbekannt";
|
|
|
const userLabel = user?.branchId
|
|
const userLabel = user?.branchId
|
|
|
? `${roleLabel} (${user.branchId})`
|
|
? `${roleLabel} (${user.branchId})`
|
|
|
: roleLabel;
|
|
: roleLabel;
|
|
|
|
|
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ const tz =
|
|
|
|
|
+ typeof Intl !== "undefined"
|
|
|
|
|
+ ? Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
|
|
|
+ : "";
|
|
|
|
|
+
|
|
|
|
|
+ const timestampLocal = now.toLocaleString("de-DE");
|
|
|
|
|
+ const timestampIso = now.toISOString();
|
|
|
|
|
+
|
|
|
|
|
+ const routeLine = pathname ? `Route: ${pathname}` : "Route: (unbekannt)";
|
|
|
const urlLine = currentUrl ? `URL: ${currentUrl}` : "URL: (bitte einfügen)";
|
|
const urlLine = currentUrl ? `URL: ${currentUrl}` : "URL: (bitte einfügen)";
|
|
|
|
|
+ const uaLine = userAgent
|
|
|
|
|
+ ? `User-Agent: ${userAgent}`
|
|
|
|
|
+ : "User-Agent: (unbekannt)";
|
|
|
|
|
+ const timeLine = tz
|
|
|
|
|
+ ? `Zeitpunkt: ${timestampLocal} (${tz})`
|
|
|
|
|
+ : `Zeitpunkt: ${timestampLocal}`;
|
|
|
|
|
+ const isoLine = `ISO: ${timestampIso}`;
|
|
|
|
|
+
|
|
|
|
|
+ const subject = user?.branchId
|
|
|
|
|
+ ? `Support – RHL Lieferscheine (${user.branchId})`
|
|
|
|
|
+ : "Support – RHL Lieferscheine";
|
|
|
|
|
|
|
|
const body = [
|
|
const body = [
|
|
|
"Hallo attus Support,",
|
|
"Hallo attus Support,",
|
|
|
"",
|
|
"",
|
|
|
- "bitte beschreibt hier kurz euer Anliegen:",
|
|
|
|
|
|
|
+ "bitte beschreibt hier kurz das Anliegen:",
|
|
|
"",
|
|
"",
|
|
|
"- Was wollten Sie tun?",
|
|
"- Was wollten Sie tun?",
|
|
|
"- Was ist passiert?",
|
|
"- Was ist passiert?",
|
|
|
"- (Optional) Screenshot / Zeitpunkt",
|
|
"- (Optional) Screenshot / Zeitpunkt",
|
|
|
"",
|
|
"",
|
|
|
- urlLine,
|
|
|
|
|
|
|
+ "--- Kontext (bitte drin lassen) ---",
|
|
|
`Benutzer: ${userLabel}`,
|
|
`Benutzer: ${userLabel}`,
|
|
|
|
|
+ routeLine,
|
|
|
|
|
+ urlLine,
|
|
|
|
|
+ timeLine,
|
|
|
|
|
+ isoLine,
|
|
|
|
|
+ uaLine,
|
|
|
|
|
+ "----------------------------------",
|
|
|
"",
|
|
"",
|
|
|
"Vielen Dank.",
|
|
"Vielen Dank.",
|
|
|
].join("\r\n");
|
|
].join("\r\n");
|
|
@@ -64,6 +91,7 @@ function buildSupportMailto({ user, currentUrl }) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export default function UserStatus() {
|
|
export default function UserStatus() {
|
|
|
|
|
+ const pathname = usePathname() || "/";
|
|
|
const { status, user } = useAuth();
|
|
const { status, user } = useAuth();
|
|
|
|
|
|
|
|
const isAuthenticated = status === "authenticated" && user;
|
|
const isAuthenticated = status === "authenticated" && user;
|
|
@@ -78,14 +106,22 @@ export default function UserStatus() {
|
|
|
if (status === "error") text = "Fehler";
|
|
if (status === "error") text = "Fehler";
|
|
|
|
|
|
|
|
const currentUrl = typeof window !== "undefined" ? window.location.href : "";
|
|
const currentUrl = typeof window !== "undefined" ? window.location.href : "";
|
|
|
|
|
+ const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
|
|
|
|
|
|
- const supportMailto = buildSupportMailto({ user, currentUrl });
|
|
|
|
|
|
|
+ const supportMailto = buildSupportMailto({
|
|
|
|
|
+ user,
|
|
|
|
|
+ currentUrl,
|
|
|
|
|
+ pathname,
|
|
|
|
|
+ userAgent,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const isProfileActive =
|
|
|
|
|
+ pathname === "/profile" || pathname.startsWith("/profile/");
|
|
|
|
|
|
|
|
async function handleLogout() {
|
|
async function handleLogout() {
|
|
|
try {
|
|
try {
|
|
|
await logout();
|
|
await logout();
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- // Logout is idempotent; we still redirect for predictable UX.
|
|
|
|
|
console.error("[UserStatus] logout failed:", err);
|
|
console.error("[UserStatus] logout failed:", err);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -95,19 +131,28 @@ export default function UserStatus() {
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<DropdownMenu>
|
|
<DropdownMenu>
|
|
|
- <DropdownMenuTrigger asChild>
|
|
|
|
|
- <Button
|
|
|
|
|
- type="button"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- className="gap-2"
|
|
|
|
|
- aria-label="Benutzermenü öffnen"
|
|
|
|
|
- title="Benutzermenü"
|
|
|
|
|
- >
|
|
|
|
|
- <User className="h-4 w-4" aria-hidden="true" />
|
|
|
|
|
- <span className="hidden text-xs md:inline">{text}</span>
|
|
|
|
|
- </Button>
|
|
|
|
|
- </DropdownMenuTrigger>
|
|
|
|
|
|
|
+ <Tooltip>
|
|
|
|
|
+ <TooltipTrigger asChild>
|
|
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ aria-label="Benutzermenü öffnen"
|
|
|
|
|
+ className={cn(
|
|
|
|
|
+ "gap-2",
|
|
|
|
|
+ "px-2 md:px-3",
|
|
|
|
|
+ isProfileActive ? "bg-accent" : "",
|
|
|
|
|
+ )}
|
|
|
|
|
+ >
|
|
|
|
|
+ <User className="h-4 w-4" aria-hidden="true" />
|
|
|
|
|
+ <span className="hidden text-xs md:inline">{text}</span>
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </DropdownMenuTrigger>
|
|
|
|
|
+ </TooltipTrigger>
|
|
|
|
|
+
|
|
|
|
|
+ <TooltipContent side="bottom">Benutzermenü</TooltipContent>
|
|
|
|
|
+ </Tooltip>
|
|
|
|
|
|
|
|
<DropdownMenuContent align="end" className="min-w-56">
|
|
<DropdownMenuContent align="end" className="min-w-56">
|
|
|
<DropdownMenuLabel>Benutzer</DropdownMenuLabel>
|
|
<DropdownMenuLabel>Benutzer</DropdownMenuLabel>
|
|
@@ -128,11 +173,7 @@ export default function UserStatus() {
|
|
|
</DropdownMenuItem>
|
|
</DropdownMenuItem>
|
|
|
|
|
|
|
|
<DropdownMenuItem asChild>
|
|
<DropdownMenuItem asChild>
|
|
|
- <a
|
|
|
|
|
- href={supportMailto}
|
|
|
|
|
- className="flex w-full items-center gap-2"
|
|
|
|
|
- title="Support kontaktieren"
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <a href={supportMailto} className="flex w-full items-center gap-2">
|
|
|
<LifeBuoy className="h-4 w-4" aria-hidden="true" />
|
|
<LifeBuoy className="h-4 w-4" aria-hidden="true" />
|
|
|
Support
|
|
Support
|
|
|
</a>
|
|
</a>
|