import animationData from "@/lotties/vagina.json";
import { apiClient } from "@/shared/api";
import { PageContainer } from "@/shared/ui/PageContainer";
import { Button } from "@/shared/ui/button";
import Lottie from "lottie-react";
import { Loader } from "lucide-react";
import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router";

const POLLING_INTERVAL_MS = 2000;
const MAX_POLLING_ATTEMPTS = 60; // 2 minutes max
const SUCCESS_ANIMATION_DURATION_MS = 2500;

export const TxHashPlaceholderPage: React.FC = () => {
  const { txHash } = useParams<{ txHash: string }>();
  const navigate = useNavigate();
  const [attempts, setAttempts] = useState(0);
  const [error, setError] = useState<string | null>(null);
  const [isPolling, setIsPolling] = useState(true);
  const [foundEventId, setFoundEventId] = useState<number | null>(null);
  const [showSuccess, setShowSuccess] = useState(false);

  useEffect(() => {
    if (!txHash) {
      navigate("/", { replace: true });
      return;
    }

    // Normalize txHash to lowercase
    const normalizedTxHash = txHash.toLowerCase();

    const pollForEvent = async (): Promise<boolean> => {
      try {
        const response = await apiClient.GET(
          "/api/events/by-tx-hash/{tx_hash}/",
          {
            params: {
              path: {
                tx_hash: normalizedTxHash,
                // biome-ignore lint/suspicious/noExplicitAny: OpenAPI types use camelCase but runtime URL template uses snake_case
              } as any,
            },
          },
        );

        if (response.data?.id) {
          // Event found! Show success animation then navigate
          setFoundEventId(response.data.id);
          setShowSuccess(true);
          setIsPolling(false);
          // Navigate after animation duration with state to indicate we came from txhash
          setTimeout(() => {
            navigate(`/events/${response.data.id}`, {
              replace: true,
              state: { fromTxHash: true },
            });
          }, SUCCESS_ANIMATION_DURATION_MS);
          return true;
        }
      } catch (err) {
        // 404 is expected while event is being indexed
        // Other errors should be logged but polling continues
        console.log("Polling attempt failed:", err);
      }
      return false;
    };

    const intervalId = setInterval(async () => {
      if (!isPolling) {
        clearInterval(intervalId);
        return;
      }

      const found = await pollForEvent();
      if (found) {
        clearInterval(intervalId);
        return;
      }

      setAttempts((prev) => {
        const next = prev + 1;
        if (next >= MAX_POLLING_ATTEMPTS) {
          clearInterval(intervalId);
          setIsPolling(false);
          setError(
            "Event is taking longer than expected to sync. Please check your events list later.",
          );
        }
        return next;
      });
    }, POLLING_INTERVAL_MS);

    // Initial check
    pollForEvent();

    return () => {
      clearInterval(intervalId);
    };
  }, [txHash, navigate, isPolling]);

  const handleCancel = () => {
    setIsPolling(false);
    navigate("/", { replace: true });
  };

  const progress = Math.min(
    Math.round((attempts / MAX_POLLING_ATTEMPTS) * 100),
    99,
  );

  // Show success animation when event is found
  if (showSuccess && foundEventId) {
    return (
      <PageContainer hasBackIcon={false} name="Success!">
        <div className="flex flex-col items-center justify-center px-6 py-12">
          <div className="mb-4">
            <Lottie animationData={animationData} loop={false} />
          </div>

          <h1 className="text-2xl font-bold text-center mb-2 text-primary">
            Success!
          </h1>

          <h2 className="text-xl font-semibold text-center mb-4">
            Event created successfully
          </h2>

          <p className="text-muted-foreground text-center mb-6 max-w-md">
            Your event has been confirmed and is now available.
          </p>

          <div className="bg-muted rounded-lg p-4 mb-6 w-full max-w-md">
            <p className="text-xs text-muted-foreground mb-1">
              Transaction Hash
            </p>
            <p className="font-mono text-sm break-all">{txHash}</p>
          </div>

          <Button
            onClick={() =>
              navigate(`/events/${foundEventId}`, { replace: true })
            }
          >
            View Event
          </Button>
        </div>
      </PageContainer>
    );
  }

  return (
    <PageContainer hasBackIcon={false} name="Processing Event">
      <div className="flex flex-col items-center justify-center px-6 py-12">
        {!error ? (
          <>
            <div className="relative mb-8">
              <Loader className="h-16 w-16 animate-[spin_3s_linear_infinite] text-primary" />
              <div className="absolute inset-0 flex items-center justify-center">
                <span className="text-xs font-medium">{progress}%</span>
              </div>
            </div>

            <h2 className="text-xl font-semibold text-center mb-4">
              Confirming your event creation...
            </h2>

            <p className="text-muted-foreground text-center mb-6 max-w-md">
              Your transaction has been submitted and is being processed by the
              network. This usually takes a few seconds.
            </p>

            <div className="bg-muted rounded-lg p-4 mb-6 w-full max-w-md">
              <p className="text-xs text-muted-foreground mb-1">
                Transaction Hash
              </p>
              <p className="font-mono text-sm break-all">{txHash}</p>
            </div>

            <Button filling="outline" onClick={handleCancel}>
              Cancel and return home
            </Button>
          </>
        ) : (
          <>
            <div className="mb-8">
              <img
                src="/icons/price_1.svg"
                alt="Timeout"
                className="h-16 w-16 opacity-50"
              />
            </div>

            <h2 className="text-xl font-semibold text-center mb-4">
              Event still processing
            </h2>

            <p className="text-muted-foreground text-center mb-6 max-w-md">
              {error}
            </p>

            <div className="p-4 mb-6 w-full max-w-md">
              <p className="text-xs text-muted-foreground mb-1">
                Transaction Hash
              </p>
              <p className="font-mono text-sm break-all">{txHash}</p>
            </div>

            <div className="flex gap-3">
              <Button filling="outline" onClick={handleCancel}>
                Return home
              </Button>
              <Button onClick={() => window.location.reload()}>
                Continue waiting
              </Button>
            </div>
          </>
        )}
      </div>
    </PageContainer>
  );
};

Homonyms

cyberia/research/events/client/src/pages/place/index.tsx
cyberia/research/events/client/src/pages/user/index.tsx
cyberia/research/events/client/src/pages/purchase/index.tsx
cyberia/research/events/client/src/pages/account/index.tsx
cyberia/research/events/client/src/pages/manage/index.tsx
cyberia/research/events/client/src/pages/notifications/index.tsx
cyberia/research/events/client/src/pages/shaman-verification/index.tsx
cyberia/research/events/client/src/pages/404/index.tsx
cyberia/research/events/client/src/pages/map/index.tsx
cyberia/research/events/client/src/pages/events/index.tsx
cyberia/research/events/client/src/pages/socials/index.tsx
cyberia/research/events/client/src/pages/home/index.tsx
cyberia/research/events/client/src/pages/events/attendees/index.tsx
cyberia/research/events/client/src/pages/events/create/index.tsx
cyberia/research/events/client/src/pages/events/details/index.tsx
cyberia/research/events/client/src/pages/manage/verifiedshamans/index.tsx
cyberia/research/events/client/src/pages/manage/staff/index.tsx
cyberia/research/events/client/src/pages/manage/verification-stats/index.tsx
cyberia/research/events/client/src/pages/manage/place/index.tsx
cyberia/research/events/client/src/pages/events/edit/index.tsx
cyberia/research/events/client/src/pages/manage/localprovider/index.tsx
cyberia/research/events/client/src/pages/manage/place/update/index.tsx
cyberia/research/events/client/src/pages/manage/place/create/index.tsx
cyberia/research/events/client/src/pages/account/distribution-profiles/create/index.tsx
cyberia/research/events/client/src/pages/manage/place/request/index.tsx

Graph