AuthGate.jsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. "use client";
  2. import React from "react";
  3. import { RefreshCw } from "lucide-react";
  4. import { usePathname, useRouter } from "next/navigation";
  5. import { useAuth } from "@/components/auth/authContext";
  6. import {
  7. shouldRedirectToProfileForPasswordChange,
  8. buildMustChangePasswordRedirectUrl,
  9. resolveMustChangePasswordResumePath,
  10. } from "@/lib/frontend/auth/mustChangePasswordGate";
  11. import { Button } from "@/components/ui/button";
  12. import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
  13. import {
  14. Card,
  15. CardHeader,
  16. CardTitle,
  17. CardDescription,
  18. CardContent,
  19. CardFooter,
  20. } from "@/components/ui/card";
  21. export default function AuthGate({ children }) {
  22. const router = useRouter();
  23. const pathname = usePathname() || "/";
  24. const { status, user, error, retry } = useAuth();
  25. const canRetry = typeof retry === "function";
  26. const isAuthenticated = status === "authenticated" && user;
  27. const mustChangePassword = isAuthenticated && user.mustChangePassword === true;
  28. const currentSearch =
  29. typeof window !== "undefined" ? window.location.search || "" : "";
  30. const currentPathWithSearch = `${pathname}${currentSearch}`;
  31. const mustChangePasswordRedirectUrl = buildMustChangePasswordRedirectUrl(
  32. currentPathWithSearch,
  33. );
  34. const shouldForceProfileRedirect = isAuthenticated
  35. ? shouldRedirectToProfileForPasswordChange({
  36. pathname,
  37. mustChangePassword,
  38. })
  39. : false;
  40. const resumePathAfterPasswordChange = isAuthenticated
  41. ? resolveMustChangePasswordResumePath({
  42. pathname,
  43. searchParams:
  44. typeof window !== "undefined"
  45. ? new URLSearchParams(window.location.search || "")
  46. : null,
  47. mustChangePassword,
  48. })
  49. : null;
  50. React.useEffect(() => {
  51. if (!shouldForceProfileRedirect) return;
  52. router.replace(mustChangePasswordRedirectUrl);
  53. }, [shouldForceProfileRedirect, mustChangePasswordRedirectUrl, router]);
  54. React.useEffect(() => {
  55. if (!resumePathAfterPasswordChange) return;
  56. router.replace(resumePathAfterPasswordChange);
  57. }, [resumePathAfterPasswordChange, router]);
  58. if (status === "authenticated") {
  59. if (shouldForceProfileRedirect) return null;
  60. if (resumePathAfterPasswordChange) return null;
  61. return children;
  62. }
  63. if (status === "error") {
  64. return (
  65. <Card>
  66. <CardHeader>
  67. <CardTitle>Sitzungsprüfung fehlgeschlagen</CardTitle>
  68. <CardDescription>
  69. Die Sitzung konnte nicht geprüft werden.
  70. </CardDescription>
  71. </CardHeader>
  72. <CardContent className="space-y-3">
  73. <Alert variant="destructive">
  74. <AlertTitle>Fehler</AlertTitle>
  75. <AlertDescription>
  76. {error ||
  77. "Bitte prüfen Sie Ihre Verbindung und versuchen Sie es erneut."}
  78. </AlertDescription>
  79. </Alert>
  80. </CardContent>
  81. <CardFooter className="flex flex-col gap-2 sm:flex-row sm:justify-end">
  82. <Button
  83. type="button"
  84. variant="outline"
  85. onClick={() => {
  86. if (canRetry) retry();
  87. else window.location.reload();
  88. }}
  89. >
  90. <RefreshCw className="h-4 w-4" />
  91. Erneut versuchen
  92. </Button>
  93. </CardFooter>
  94. </Card>
  95. );
  96. }
  97. // "unauthenticated" -> redirect happens in AuthProvider.
  98. // Keeping this message is fine because TopNav indicator is not shown in this state.
  99. if (status === "unauthenticated") {
  100. return (
  101. <Card>
  102. <CardHeader>
  103. <CardTitle>Weiterleitung</CardTitle>
  104. <CardDescription>
  105. Sie werden zum Login weitergeleitet.
  106. </CardDescription>
  107. </CardHeader>
  108. <CardContent>
  109. <p className="text-sm text-muted-foreground">Bitte warten…</p>
  110. </CardContent>
  111. </Card>
  112. );
  113. }
  114. // Default: loading (or unknown)
  115. // RHL-032:
  116. // Do not render a second "session checking" UI here.
  117. // The TopNav SessionIndicator is the single source of feedback.
  118. return null;
  119. }