import { useEnsLookup } from "@/shared/hooks/useEnsLookup";
import { formatAddress } from "@/shared/lib/formatAddress";
import { ErrorMessage } from "@/shared/ui/ErrorMessage";
import { Loader } from "@/shared/ui/Loader";
import { PageContainer } from "@/shared/ui/PageContainer";
import { useQuery } from "@tanstack/react-query";
import { ExternalLink } from "lucide-react";
import { useParams } from "react-router";

interface UserProfile {
  address: string;
  ens: string | null;
  avatar_url: string;
  socials: Array<{ network: string; value: string }>;
  roles: string[];
}

const fetchUserProfile = async (address: string): Promise<UserProfile> => {
  const response = await fetch(`/api/users/${address}/profile`, {
    credentials: "include",
  });
  if (!response.ok) {
    throw new Error("Failed to fetch user profile");
  }
  return response.json();
};

const useUserProfile = (address: string | undefined) => {
  return useQuery({
    queryKey: ["user", "profile", address],
    queryFn: () => {
      if (!address) throw new Error("No address provided");
      return fetchUserProfile(address);
    },
    enabled: !!address,
  });
};

export const UserPage: React.FC = () => {
  const { address } = useParams<{ address: string }>();
  const { data: profile, error, isLoading } = useUserProfile(address);
  const ensName = useEnsLookup(address || "");

  if (isLoading) return <Loader />;
  if (error) return <ErrorMessage errors={error} />;
  if (!profile) return <ErrorMessage errors="User not found" />;

  const displayName =
    ensName || formatAddress(profile.address as `0x${string}`);

  return (
    <PageContainer name="User Profile">
      <div className="flex flex-col gap-6 p-4">
        {/* Avatar and main info */}
        <div className="flex flex-col items-center gap-4">
          <img
            src={`https://effigy.im/a/${profile.address}.svg`}
            alt="User avatar"
            className="w-24 h-24 rounded-full"
          />
          <div className="text-center">
            <h2 className="text-xl font-semibold">{displayName}</h2>
            <a
              href={`https://etherscan.io/address/${profile.address}`}
              target="_blank"
              rel="noopener noreferrer"
              className="inline-flex items-center gap-1 text-sm text-secondary hover:underline"
            >
              {formatAddress(profile.address as `0x${string}`)}
              <ExternalLink className="w-4 h-4" />
            </a>
          </div>
          {/* Roles */}
          {profile.roles.length > 0 && (
            <div className="flex flex-wrap gap-2 justify-center">
              {profile.roles.map((role) => (
                <span
                  key={role}
                  className="px-3 py-1 text-sm bg-secondary/20 rounded-full capitalize"
                >
                  {role.replace("_", " ")}
                </span>
              ))}
            </div>
          )}
        </div>

        {/* Socials */}
        {profile.socials && profile.socials.length > 0 && (
          <div className="border-t border-secondary/20 pt-4">
            <h3 className="text-lg font-semibold mb-3">Socials</h3>
            <div className="flex flex-col gap-2">
              {profile.socials.map((social) => (
                <div
                  key={social.network}
                  className="flex justify-between items-center py-2 px-3 bg-secondary/10 rounded"
                >
                  <span className="capitalize font-medium">
                    {social.network}
                  </span>
                  <span className="text-secondary">{social.value}</span>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>
    </PageContainer>
  );
};

Homonyms

cyberia/research/events/client/src/pages/txhash/index.tsx
cyberia/research/events/client/src/pages/place/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