LogoutButton.jsx 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. "use client";
  2. import React from "react";
  3. import { logout } from "@/lib/frontend/apiClient";
  4. import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
  5. import { Button } from "@/components/ui/button";
  6. /**
  7. * LogoutButton (RHL-020)
  8. *
  9. * Responsibilities:
  10. * - Call apiClient.logout() to clear the HTTP-only session cookie.
  11. * - Then redirect to /login?reason=logged-out.
  12. *
  13. * Important test/runtime note:
  14. * - We intentionally avoid next/navigation hooks here.
  15. * - Some unit tests render AppShell via react-dom/server without Next.js runtime.
  16. * - Using window.location inside the click handler avoids needing router context
  17. * during server rendering (handler is not invoked in SSR tests).
  18. *
  19. * UX rule:
  20. * - All user-facing text must be German.
  21. */
  22. export default function LogoutButton() {
  23. const [isLoggingOut, setIsLoggingOut] = React.useState(false);
  24. async function handleLogout() {
  25. if (isLoggingOut) return;
  26. setIsLoggingOut(true);
  27. try {
  28. // Backend endpoint is idempotent; even if no cookie exists it returns ok.
  29. await logout();
  30. } catch (err) {
  31. // If logout fails due to network issues, we still redirect to login.
  32. // This keeps UX predictable; user can log in again if needed.
  33. console.error("[LogoutButton] logout failed:", err);
  34. }
  35. const loginUrl = buildLoginUrl({ reason: LOGIN_REASONS.LOGGED_OUT });
  36. // Replace so "Back" won't bring the user into a protected page.
  37. window.location.replace(loginUrl);
  38. }
  39. return (
  40. <Button
  41. variant="outline"
  42. size="sm"
  43. type="button"
  44. disabled={isLoggingOut}
  45. aria-disabled={isLoggingOut ? "true" : "false"}
  46. onClick={handleLogout}
  47. title={isLoggingOut ? "Abmeldung läuft…" : "Abmelden"}
  48. >
  49. {isLoggingOut ? "Abmeldung…" : "Abmelden"}
  50. </Button>
  51. );
  52. }