aegida-console / components / auth / auth-gate.tsx
auth-gate.tsx
Raw
"use client";

import { useEffect, useState } from "react";

import { LoginScreen } from "@/components/auth/login-screen";
import { ChatApp } from "@/components/chat/chat-app";
import {
  authFetch,
  clearStoredToken,
  getStoredToken,
  storeToken,
} from "@/lib/auth/client";
import { parseAuthIdentity, type AuthIdentity } from "@/lib/auth/types";

type AuthState =
  | { status: "checking" }
  | { status: "anonymous" }
  | { status: "authenticated"; user: AuthIdentity };

type IdentityResponse = { user: AuthIdentity };

export function AuthGate() {
  const [state, setState] = useState<AuthState>({ status: "checking" });

  useEffect(() => {
    let active = true;

    async function validateToken() {
      await Promise.resolve();
      const token = getStoredToken();

      if (!token) {
        if (active) {
          setState({ status: "anonymous" });
        }
        return;
      }

      try {
        const response = await authFetch("/api/auth/me", { cache: "no-store" });

        if (response.status === 401) {
          clearStoredToken();
        }

        if (!response.ok) {
          if (active) {
            setState({ status: "anonymous" });
          }
          return;
        }

        const payload: unknown = await response.json();

        if (!isIdentityResponse(payload)) {
          if (active) {
            setState({ status: "anonymous" });
          }
          return;
        }

        if (active) {
          setState({ status: "authenticated", user: payload.user });
        }
      } catch {
        if (active) {
          setState({ status: "anonymous" });
        }
      }
    }

    void validateToken();

    return () => {
      active = false;
    };
  }, []);

  function handleAuthenticated(token: string, user: AuthIdentity) {
    storeToken(token);
    setState({ status: "authenticated", user });
  }

  function handleLogout() {
    clearStoredToken();
    setState({ status: "anonymous" });
  }

  if (state.status === "checking") {
    return (
      <main
        aria-label="Проверяем доступ"
        className="flex min-h-screen items-center justify-center bg-muted/30 p-6"
      >
        <p role="status" className="text-sm text-muted-foreground">
          Проверяем доступ
        </p>
      </main>
    );
  }

  if (state.status === "authenticated") {
    return <ChatApp key={state.user.id} onLogout={handleLogout} user={state.user} />;
  }

  return <LoginScreen onAuthenticated={handleAuthenticated} />;
}

function isIdentityResponse(value: unknown): value is IdentityResponse {
  if (typeof value !== "object" || value === null || !("user" in value)) {
    return false;
  }

  return parseAuthIdentity(value.user) !== null;
}