-
+
Meta Links
@@ -19,7 +27,7 @@ export default function Home() {
-
@@ -28,21 +36,3 @@ export default function Home() {
>
);
}
-
-export function ClaimYourProfileButton() {
- const router = useRouter();
- return (
-
- {
- router.push("https://enter.metagame.wtf/");
- }}
- >
- Claim your profile
-
-
- );
-}
diff --git a/app/providers/supabase.tsx b/app/providers/supabase.tsx
new file mode 100644
index 0000000..20e11c2
--- /dev/null
+++ b/app/providers/supabase.tsx
@@ -0,0 +1,58 @@
+"use client";
+
+import React, { createContext, useContext, useState, useEffect } from "react";
+import { createClient } from "@supabase/supabase-js";
+import type { SupabaseClient } from "@supabase/supabase-js";
+import type { Database } from "@/types/supabase";
+
+type SupabaseContextType = {
+ setToken: React.Dispatch
>;
+ supabase: SupabaseClient;
+};
+
+// Create a context
+const SupabaseContext = createContext(undefined);
+
+const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL as string;
+const SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string;
+
+function SupabaseProvider({ children }: { children: React.ReactNode }) {
+ const supabaseNew = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
+ const [token, setToken] = useState(null);
+ const [supabase, setSupabase] = useState(supabaseNew);
+
+ useEffect(() => {
+ if (!token) {
+ return;
+ }
+ const newSupabase = createClient(
+ SUPABASE_URL,
+ SUPABASE_ANON_KEY,
+ {
+ global: {
+ headers: {
+ Authorization: token ? `Bearer ${token}` : "",
+ },
+ },
+ }
+ );
+ newSupabase.realtime.accessToken = token;
+ setSupabase(newSupabase);
+ }, [token]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export const useSupabase = () => {
+ const context = useContext(SupabaseContext);
+ if (context === undefined) {
+ throw new Error("useSupabase must be used within a SupabaseProvider");
+ }
+ return context;
+};
+
+export default SupabaseProvider;
diff --git a/app/search/page.tsx b/app/search/page.tsx
index 49e395b..7e76b24 100644
--- a/app/search/page.tsx
+++ b/app/search/page.tsx
@@ -2,33 +2,19 @@
import { useSearchParams } from "next/navigation";
import SearchProfilesComponent from "@/components/SearchProfile";
-import { useQuery } from "@apollo/client";
-import { searchProfiles } from "@/services/apollo";
import { HoverEffect } from "@/components/card-hover-effect";
import { BackgroundBeams } from "@/components/background-beams";
-import { toHTTP } from "@/utils/ipfs";
+import { Suspense } from "react";
+import { useSearchByUsernameOrAddress } from "@/lib/hooks/useGetUserDetailsFromDatabase";
-const Page: React.FC = () => {
- const searchParams = useSearchParams();
- const searchQuery = searchParams.get("query") ?? undefined;
+import { ThreeDotsLoaderComponent } from "@/components/LoadingComponents";
- const { loading, error, data } = useQuery(searchProfiles, {
- variables: { search: `%${searchQuery}%` },
- });
+const SearchComponent = () => {
+ const searchParams = useSearchParams();
+ const searchQuery = searchParams.get("query") ?? "";
+ const { data, isLoading } = useSearchByUsernameOrAddress(searchQuery);
- const players = data?.player ?? [];
- const formattedData = players.map((player) => {
- const { profile } = player;
- return {
- name: profile.name,
- description: profile.description,
- username: profile.username,
- imageUrl: toHTTP(profile?.profileImageURL ?? ""),
- ethereumAddress: player.ethereumAddress,
- href: `/${player.ethereumAddress}`,
- };
- });
- console.log("searchParams", players);
+ if (isLoading || !data) return ;
return (
@@ -36,11 +22,24 @@ const Page: React.FC = () => {
-
+ {!data.length && (
+
+ No Results Found
+
+ )}
+
);
};
+const Page: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
export default Page;
diff --git a/codegen.ts b/codegen.ts
new file mode 100644
index 0000000..2ced09d
--- /dev/null
+++ b/codegen.ts
@@ -0,0 +1,20 @@
+import type { CodegenConfig } from "@graphql-codegen/cli";
+
+const config: CodegenConfig = {
+ overwrite: true,
+ schema: "https://api.airstack.xyz/gql",
+ documents: "services/**/*.ts",
+ generates: {
+ // Output type file
+ "graphql/types.ts": {
+ // add to plugins: @graphql-codegen/typescript and @graphql-codegen/typescript-operations
+ plugins: ["typescript", "typescript-operations"],
+ config: {
+ avoidOptionals: true,
+ skipTypename: true,
+ },
+ },
+ },
+};
+
+export default config;
\ No newline at end of file
diff --git a/components/Attestations.tsx b/components/Attestations.tsx
new file mode 100644
index 0000000..feac63a
--- /dev/null
+++ b/components/Attestations.tsx
@@ -0,0 +1,145 @@
+"use client";
+
+import { getTimeDifference } from "@/lib/get-time-diiference";
+import React, { useState, useEffect } from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { useEAS } from "@/lib/hooks/useEAS";
+import { Button } from "@/components/ui/button";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from "@/components/ui/form";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import type { TAttestations } from "@/lib/zod-utils";
+import Loader from "@/components/ui/loader";
+import { z } from "zod";
+
+const attestationFormSchema = z.object({
+ attestation: z.string().min(2, {
+ message: "Attestation must be at least 2 characters.",
+ }),
+});
+
+const AttestationItem = ({ timeCreated, attestationVal, attestor }: any) => {
+ const timeDifference = getTimeDifference(timeCreated);
+ return (
+
+
{attestationVal}
+
By {attestor}
+
{timeDifference}
+
+ );
+};
+
+export const Attestations = ({ address }: { address: string }) => {
+ const { attest, getAttestationsForRecipient, isLoading } = useEAS();
+ const [attestation, setAttestion] = useState
("");
+ const [attestations, setAttestations] = useState([]);
+ const [isAttesting, setIsAttesting] = useState(false);
+
+ useEffect(() => {
+ const getAttestationData = async () => {
+ const attestationData = await getAttestationsForRecipient(address);
+ if (attestationData) {
+ setAttestations(attestationData);
+ }
+ };
+ getAttestationData();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [address, isAttesting]);
+
+ const form = useForm>({
+ resolver: zodResolver(attestationFormSchema),
+ defaultValues: {
+ attestation: "",
+ },
+ });
+
+ async function handleAttest(data: z.infer) {
+ try {
+ const { attestation } = data;
+ setIsAttesting(true);
+ await attest(attestation, address);
+ form.reset();
+ } catch (err) {
+ } finally {
+ setIsAttesting(false);
+ }
+ }
+
+ return isLoading ? (
+ Loading...
+ ) : (
+
+
+
+ {attestations?.map((att, i) => {
+ const attestor = att[3].value;
+ const timeCreated = att[1].value.value;
+
+ const attestationVal = att[0].value;
+ return (
+
+ );
+ })}
+
+
+ );
+};
diff --git a/components/ClaimProfile.tsx b/components/ClaimProfile.tsx
new file mode 100644
index 0000000..549946d
--- /dev/null
+++ b/components/ClaimProfile.tsx
@@ -0,0 +1,22 @@
+import { useRouter } from "next/navigation";
+import { HoverBorderGradient } from "@/components/hover-border-gradient";
+
+const ClaimYourProfileButton = () => {
+ const router = useRouter();
+ return (
+
+ {
+ router.push("/create-profile");
+ }}
+ >
+ Claim your profile
+
+
+ );
+};
+
+export default ClaimYourProfileButton;
diff --git a/components/DonateCrypto.tsx b/components/DonateCrypto.tsx
index 3fc86c4..ab12e31 100644
--- a/components/DonateCrypto.tsx
+++ b/components/DonateCrypto.tsx
@@ -16,7 +16,7 @@ import {
import { Input } from "@/components/ui/input";
const formSchema = z.object({
- amount: z.number(),
+ amount: z.string(),
});
const DonateCrypto = ({
@@ -29,13 +29,14 @@ const DonateCrypto = ({
const form = useForm>({
resolver: zodResolver(formSchema),
defaultValues: {
- amount: 0,
+ amount: "0",
},
});
function onSubmit(values: z.infer) {
try {
const { amount } = values;
+ console.log(typeof amount);
sendTransaction({
to: ethereumAddress,
diff --git a/components/LoadingComponents.tsx b/components/LoadingComponents.tsx
new file mode 100644
index 0000000..9dbc1f8
--- /dev/null
+++ b/components/LoadingComponents.tsx
@@ -0,0 +1,14 @@
+import { cn } from "@/lib/utils";
+import ThreeDotsLoader from "./ThreeDotsLoader";
+
+type TLoader = {
+ className?: string;
+};
+
+export const ThreeDotsLoaderComponent = ({ className }: TLoader) => (
+
+
+
+);
diff --git a/components/MoreOptionsDropdown.tsx b/components/MoreOptionsDropdown.tsx
new file mode 100644
index 0000000..821ba14
--- /dev/null
+++ b/components/MoreOptionsDropdown.tsx
@@ -0,0 +1,130 @@
+import {
+ HamburgerMenuIcon,
+ ClipboardCopyIcon,
+ PlusIcon,
+ Pencil1Icon,
+} from "@radix-ui/react-icons";
+
+import { useRouter } from "next/navigation";
+
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuPortal,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { useState, useEffect } from "react";
+
+export function useCopy() {
+ const [isCopied, setIsCopied] = useState(false);
+
+ const copyToClipboard = (text: string) => {
+ if (typeof navigator !== "undefined" && navigator.clipboard) {
+ navigator.clipboard
+ .writeText(text)
+ .then(() => setIsCopied(true))
+ .catch((error) => console.error("Copy to clipboard failed:", error));
+ }
+ };
+
+ const resetCopyStatus = () => {
+ setIsCopied(false);
+ };
+
+ useEffect(() => {
+ if (isCopied) {
+ const timer = setTimeout(resetCopyStatus, 3000); // Reset copy status after 3 seconds
+ return () => clearTimeout(timer);
+ }
+ }, [isCopied]);
+
+ return { isCopied, copyToClipboard, resetCopyStatus };
+}
+
+function MoreOptionsDropdownMenu({
+ username,
+ address,
+ enableProfileEditing = true,
+}: {
+ username?: string | null;
+ address?: string | null;
+ enableProfileEditing?: boolean;
+}) {
+ const router = useRouter();
+
+ const { copyToClipboard, isCopied } = useCopy();
+
+ return (
+
+
+
+
+
+ {
+ copyToClipboard(window.location.href);
+ }}
+ className="cursor-pointer"
+ >
+ Copy to clipboard
+
+
+
+
+
+
+ {enableProfileEditing && (
+ {
+ router.push(
+ address ? `/${address}/edit-profile` : `/${address}`
+ );
+ }}
+ className="cursor-pointer"
+ >
+ Edit Profile
+
+
+
+
+ )}
+ {
+ router.push("https://enter.metagame.wtf/");
+ }}
+ className="cursor-pointer"
+ >
+ Create Profile
+
+
+
+
+
+
+ {
+ router.push("https://chat.metagame.wtf/");
+ }}
+ className="cursor-pointer"
+ >
+ Join Discord
+
+
+
+ );
+}
+
+export default MoreOptionsDropdownMenu;
diff --git a/components/ProfileCreationForm/GradiantComponents.tsx b/components/ProfileCreationForm/GradiantComponents.tsx
new file mode 100644
index 0000000..f3e6a43
--- /dev/null
+++ b/components/ProfileCreationForm/GradiantComponents.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+
+export const GradiantSeparatorLine = ({
+ className,
+}: {
+ className?: string;
+}) => (
+
+);
+
+export const BottomGradient = () => {
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/components/ProfileCreationForm/LabelInputContainer.tsx b/components/ProfileCreationForm/LabelInputContainer.tsx
new file mode 100644
index 0000000..06b7324
--- /dev/null
+++ b/components/ProfileCreationForm/LabelInputContainer.tsx
@@ -0,0 +1,19 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+
+const LabelInputContainer = ({
+ children,
+ className,
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default LabelInputContainer;
diff --git a/components/ProfileCreationForm/ProfileCreationForm.tsx b/components/ProfileCreationForm/ProfileCreationForm.tsx
new file mode 100644
index 0000000..911fd75
--- /dev/null
+++ b/components/ProfileCreationForm/ProfileCreationForm.tsx
@@ -0,0 +1,284 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+
+import SubmitLinksSection from "./SubmitLinksSection";
+import UserMetaDetailsSection from "./UserMetaDetailsSection";
+import { BottomGradient } from "./GradiantComponents";
+import { cn } from "@/lib/utils";
+import { Form } from "@/components/ui/form";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useLogin } from "@/lib/hooks/useLogin";
+import { useSupabase } from "@/app/providers/supabase";
+import { useW3upClient } from "@/lib/useW3upClient";
+import { useToast } from "@/components/ui/use-toast";
+import { useRouter } from "next/navigation";
+import { useGetUserProfile } from "@/lib/hooks/useGetUserProfile";
+import { useAccount } from "wagmi";
+
+const formSchema = z.object({
+ username: z.string().min(4, "username should be at least 4 characters"),
+ bio: z.string().optional().default(""),
+ profileImage: z.custom(
+ (v) => v instanceof File || typeof v === "string",
+ {
+ message: "Profile Image is required",
+ }
+ ),
+ backgroundImage: z
+ .custom(
+ (v) => v instanceof File || typeof v === "string"
+ )
+ .optional()
+ .nullable(),
+ links: z.array(
+ z.object({
+ icon: z
+ .custom((v) => v instanceof File)
+ .optional()
+ .nullable(),
+ name: z.string(),
+ url: z.string(),
+ })
+ ),
+});
+
+type TFormSchema = z.infer;
+
+type TLink = {
+ name: string;
+ url: string;
+ icon?: string;
+};
+
+type TuploadImagesRes = {
+ profileImageIPFS: string;
+ backgroundImageIPFS?: string;
+ links: Array;
+};
+
+type TuploadImagesInput = {
+ profileImage: TFormSchema["profileImage"];
+ backgroundImage?: TFormSchema["backgroundImage"];
+ links: TFormSchema["links"];
+};
+
+const ProfileCreationForm = ({
+ enableEditing = false,
+}: {
+ enableEditing?: boolean;
+}) => {
+ const [isLoading, setIsLoading] = useState(false);
+
+ const { address } = useAccount();
+
+ const { toast } = useToast();
+ const router = useRouter();
+
+ const w3storage = useW3upClient();
+
+ const { userProfile, isProfileLoading } = useGetUserProfile({
+ address: enableEditing && address ? address : null,
+ });
+
+ const [errorMsg, setErrorMsg] = useState();
+ const { supabase } = useSupabase();
+ const { login, logout, isLoggedIn, checkLoggedIn, isLoggingIn } = useLogin();
+
+ const [step, setStep] = useState<"userDetails" | "submitLinks">(
+ "userDetails"
+ );
+
+ const fillDefaultValues =
+ address && enableEditing && userProfile && !isProfileLoading;
+
+ const form = useForm({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ username: "",
+ bio: "",
+ profileImage: null,
+ backgroundImage: null,
+ links: [
+ {
+ icon: null,
+ name: "",
+ url: "",
+ },
+ ],
+ },
+ });
+
+ useEffect(() => {
+ if (!fillDefaultValues) {
+ return;
+ }
+ form.setValue("username", userProfile?.username ?? "");
+ form.setValue("bio", userProfile?.bio ?? "");
+ form.setValue("profileImage", userProfile?.profileImageIPFS ?? null);
+ form.setValue("backgroundImage", userProfile?.backgroundImageIPFS ?? null);
+ form.setValue("links", userProfile?.links ?? []);
+ }, [fillDefaultValues]);
+
+ const formValues = form.getValues();
+
+ const uploadImageToIPFS = async ({
+ profileImage,
+ backgroundImage,
+ links,
+ }: TuploadImagesInput): Promise => {
+ const response: TuploadImagesRes = {
+ profileImageIPFS: "",
+ backgroundImageIPFS: undefined,
+ links: [],
+ };
+
+ if (profileImage instanceof File) {
+ const profileImageCID = await w3storage?.uploadFile(profileImage);
+ response.profileImageIPFS = `ipfs://${profileImageCID}`;
+ } else if (typeof profileImage === "string") {
+ response.profileImageIPFS = profileImage;
+ }
+
+ if (backgroundImage instanceof File) {
+ const backgroundImageCID = await w3storage?.uploadFile(backgroundImage);
+ response.backgroundImageIPFS = `ipfs://${backgroundImageCID}`;
+ } else if (typeof backgroundImage === "string") {
+ response.backgroundImageIPFS = backgroundImage;
+ }
+
+ if (!!links) {
+ const results: TLink[] = [];
+
+ for (const link of links) {
+ if (link.icon instanceof File) {
+ try {
+ const cid = await w3storage?.uploadFile(link.icon);
+ results.push({
+ name: link.name,
+ url: link.url,
+ icon: cid ? `ipfs://${cid}` : undefined,
+ });
+ } catch (err) {
+ results.push({ ...link, icon: undefined });
+ }
+ } else {
+ results.push({ ...link, icon: link.icon ?? undefined });
+ }
+ }
+ response.links = results;
+ }
+
+ return response;
+ };
+
+ const handleFormSubmit = async (values: z.infer) => {
+ try {
+ setIsLoading(true);
+ const loggedIn = await checkLoggedIn();
+
+ let userAddress;
+ if (!loggedIn) {
+ userAddress = await login();
+ }
+
+ const { backgroundImage, profileImage, links, username, bio } = values;
+
+ const {
+ profileImageIPFS,
+ backgroundImageIPFS,
+ links: linksArr,
+ } = enableEditing
+ ? {
+ profileImageIPFS:
+ typeof profileImage === "string" ? profileImage : "",
+ backgroundImageIPFS:
+ typeof backgroundImage === "string" ? backgroundImage : "",
+ links: links.map((link) => ({
+ name: link.name,
+ url: link.url,
+ icon: typeof link.icon === "string" ? link.icon : undefined,
+ })),
+ }
+ : await uploadImageToIPFS({ backgroundImage, profileImage, links });
+
+ const updatedUser = await supabase
+ .from("users")
+ .update({
+ username,
+ bio,
+ profileImageIPFS,
+ backgroundImageIPFS,
+ links: linksArr,
+ })
+ .eq("address", address ?? (userAddress as string))
+ .select();
+
+ toast({
+ title: "Success",
+ description: "User Profile Created ",
+ });
+
+ form.reset();
+ router.push(`/${address}`);
+ setIsLoading(false);
+ } catch (error) {
+ console.log("error", error);
+ setErrorMsg("Failed to create profile. Please try again.");
+ setIsLoading(false);
+ }
+ };
+
+ // Function to handle Next button click
+ const handleNextButtonClick = () => {
+ if (!formValues.profileImage || !formValues.username) {
+ return;
+ }
+ setStep("submitLinks");
+ };
+
+ return (
+
+
+
+
+ );
+};
+
+export default ProfileCreationForm;
diff --git a/components/ProfileCreationForm/SubmitLinksSection.tsx b/components/ProfileCreationForm/SubmitLinksSection.tsx
new file mode 100644
index 0000000..af016f5
--- /dev/null
+++ b/components/ProfileCreationForm/SubmitLinksSection.tsx
@@ -0,0 +1,187 @@
+"use client";
+// TODO: maybe use useFieldArray for links
+
+import { Label } from "@radix-ui/react-label";
+import { GradiantSeparatorLine } from "./GradiantComponents";
+
+import { Input } from "../ui/input";
+import { IconCirclePlus, IconTrash, IconIcons } from "@tabler/icons-react";
+import Image from "next/image";
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { useFormContext } from "react-hook-form";
+
+export type TLink = {
+ icon: any;
+ name: string;
+ url: string;
+};
+
+const SubmitLinksSection = ({
+ onClickPrevBtn,
+}: {
+ onClickPrevBtn: () => void;
+}) => {
+ const form = useFormContext();
+
+ return (
+ <>
+
+
+
+
{
+ return (
+
+ {field.value.map((linkData: TLink, index: number) => (
+
+ {/* Icon Upload */}
+
+
+ {linkData?.icon ? (
+
+ ) : (
+
+ )}
+
+
+ {
+ linkData.icon = e.target.files && e.target.files[0];
+ field.onChange([
+ ...field.value.map((link: TLink, i: number) =>
+ i === index ? linkData : link
+ ),
+ ]);
+ }}
+ accept={["jpeg", "jpg", "png"].join(", ")}
+ />
+
+
+
+
+
+
+ Name
+
+
+ {
+ linkData.name = e.target.value;
+ field.onChange([
+ ...field.value.map((link: TLink, i: number) =>
+ i === index ? linkData : link
+ ),
+ ]);
+ }}
+ />
+
+
+
+
+ URL
+
+
+ {
+ linkData.url = e.target.value;
+ field.onChange([
+ ...field.value.map((link: TLink, i: number) =>
+ i === index ? linkData : link
+ ),
+ ]);
+ }}
+ />
+
+
+
+
+ {
+ field.onChange([
+ ...field.value.filter(
+ (_: any, i: number) => i !== index
+ ),
+ ]);
+ }}
+ />
+
+
+ ))}
+
+ {/* Add more links button */}
+
+
+
+
+ );
+ }}
+ />
+
+ {/* Previous button */}
+
+
+
+ >
+ );
+};
+
+export default SubmitLinksSection;
diff --git a/components/ProfileCreationForm/UserMetaDetailsSection.tsx b/components/ProfileCreationForm/UserMetaDetailsSection.tsx
new file mode 100644
index 0000000..1ba91b8
--- /dev/null
+++ b/components/ProfileCreationForm/UserMetaDetailsSection.tsx
@@ -0,0 +1,193 @@
+import React from "react";
+import Image from "next/image";
+import { Input } from "../ui/input";
+
+import LabelInputContainer from "./LabelInputContainer";
+import { GradiantSeparatorLine } from "./GradiantComponents";
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { useFormContext } from "react-hook-form";
+import { useMediaUploadErrorHandler } from "@/lib/hooks/useMediaErrorHandler";
+import { toHTTP } from "@/utils/ipfs";
+
+export type TUserMetaDetails = {
+ username: string;
+ bio: string;
+ profileImage: any;
+ backgroundImage: any;
+};
+
+const UserMetaDetailsSection = ({
+ onClickNextBtn,
+ editProfile = false,
+}: {
+ onClickNextBtn: () => void;
+ editProfile?: boolean;
+}) => {
+ const form = useFormContext();
+ const { mediaUploadErrorHandler } = useMediaUploadErrorHandler();
+ return (
+ <>
+
+
+
+ (
+
+
+ Username
+
+
+
+
+
+
+ )}
+ />
+
+ {/* Bio */}
+ (
+
+
+
+ Bio
+
+
+
+
+
+
+
+ )}
+ />
+
+
+ {/* Next Button */}
+
+
+
+ >
+ );
+};
+
+export default UserMetaDetailsSection;
diff --git a/components/SearchProfile.tsx b/components/SearchProfile.tsx
index c177525..c16154c 100644
--- a/components/SearchProfile.tsx
+++ b/components/SearchProfile.tsx
@@ -1,10 +1,21 @@
"use client";
+
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useState } from "react";
import IconSearch from "@/components/IconSearch";
import { useRouter } from "next/navigation";
import { cn } from "@/lib/utils";
+import ThreeDotsLoader from "./ThreeDotsLoader";
+import { useForm } from "react-hook-form";
+import { Form, FormField } from "./ui/form";
+import { useSupabase } from "@/app/providers/supabase";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+
+const schema = z.object({
+ searchQuery: z.string(),
+});
const SearchProfile = ({
val,
@@ -13,36 +24,80 @@ const SearchProfile = ({
val?: string;
classname?: string;
}) => {
- const [searchQuery, setSearchQuery] = useState(val ?? "");
+ const [submitting, setSubmitting] = useState(false);
+ const [userInputError, setUserInputError] = useState("");
const router = useRouter();
+ const form = useForm>({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ searchQuery: val ?? "",
+ },
+ });
+
+ const handleSearch = async (values: z.infer) => {
+ setSubmitting(true);
+ const { searchQuery } = values;
+
+ router.push(`/search?query=${searchQuery}`);
+
+ setSubmitting(false);
+ };
+
return (
- {
- setSearchQuery(e.target.value);
- }}
- />
-
+
+
+
);
};
+const Error = ({ message }: { message: string }) => {
+ if (!message) return null;
+ return {message}
;
+};
+
export default SearchProfile;
diff --git a/components/ThreeDotsLoader.tsx b/components/ThreeDotsLoader.tsx
new file mode 100644
index 0000000..e0c9387
--- /dev/null
+++ b/components/ThreeDotsLoader.tsx
@@ -0,0 +1,53 @@
+import { cn } from "@/lib/utils";
+import React from "react";
+
+export default function ThreeDotsLoader({
+ className,
+ color = "currentColor",
+}: {
+ className?: string;
+ color?: string;
+}) {
+ return (
+
+ );
+}
diff --git a/components/Wrapper.tsx b/components/Wrapper.tsx
index 7ee636c..a234cd3 100644
--- a/components/Wrapper.tsx
+++ b/components/Wrapper.tsx
@@ -1,10 +1,13 @@
+import { cn } from "@/lib/utils";
+
interface WrapperProps {
children: React.ReactNode;
+ className?: string;
}
function Wrapper({ children, ...props }: WrapperProps) {
return (
-
+
{children}
);
diff --git a/components/background-beams.tsx b/components/background-beams.tsx
index 51d1b70..7218bf7 100644
--- a/components/background-beams.tsx
+++ b/components/background-beams.tsx
@@ -60,7 +60,7 @@ export const BackgroundBeams = React.memo(
return (
diff --git a/components/card-hover-effect.tsx b/components/card-hover-effect.tsx
index d0dfba6..5252917 100644
--- a/components/card-hover-effect.tsx
+++ b/components/card-hover-effect.tsx
@@ -1,5 +1,6 @@
"use client";
+import { User } from "@/lib/hooks/useGetUserDetailsFromDatabase";
import { cn } from "@/lib/utils";
import { AnimatePresence, motion } from "framer-motion";
import Link from "next/link";
@@ -9,14 +10,7 @@ export const HoverEffect = ({
items,
className,
}: {
- items: {
- name: string;
- description: string;
- href: string;
- username: string;
- imageUrl: string;
- ethereumAddress: string;
- }[];
+ items: User[];
className?: string;
}) => {
let [hoveredIndex, setHoveredIndex] = useState
(null);
@@ -30,8 +24,8 @@ export const HoverEffect = ({
>
{items.map((item, idx) => (
setHoveredIndex(idx)}
onMouseLeave={() => setHoveredIndex(null)}
@@ -59,7 +53,7 @@ export const HoverEffect = ({
diff --git a/components/createClientComponentClient.tsx b/components/createClientComponentClient.tsx
new file mode 100644
index 0000000..121f50e
--- /dev/null
+++ b/components/createClientComponentClient.tsx
@@ -0,0 +1,9 @@
+"use client"
+
+import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"
+import { useState, useEffect } from "react"
+
+export const ClientComponent = () => {
+
+ return This is a client component
+}
\ No newline at end of file
diff --git a/components/hover-border-gradient.tsx b/components/hover-border-gradient.tsx
index 35ea7c2..af9fa9b 100644
--- a/components/hover-border-gradient.tsx
+++ b/components/hover-border-gradient.tsx
@@ -55,6 +55,7 @@ export function HoverBorderGradient({
return () => clearInterval(interval);
}
}, [hovered]);
+
return (
) => {
@@ -69,7 +70,7 @@ export function HoverBorderGradient({
>
diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx
new file mode 100644
index 0000000..95b0d38
--- /dev/null
+++ b/components/ui/dialog.tsx
@@ -0,0 +1,122 @@
+"use client"
+
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { Cross2Icon } from "@radix-ui/react-icons"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogTrigger,
+ DialogClose,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/components/ui/drawer.tsx b/components/ui/drawer.tsx
new file mode 100644
index 0000000..6a0ef53
--- /dev/null
+++ b/components/ui/drawer.tsx
@@ -0,0 +1,118 @@
+"use client"
+
+import * as React from "react"
+import { Drawer as DrawerPrimitive } from "vaul"
+
+import { cn } from "@/lib/utils"
+
+const Drawer = ({
+ shouldScaleBackground = true,
+ ...props
+}: React.ComponentProps) => (
+
+)
+Drawer.displayName = "Drawer"
+
+const DrawerTrigger = DrawerPrimitive.Trigger
+
+const DrawerPortal = DrawerPrimitive.Portal
+
+const DrawerClose = DrawerPrimitive.Close
+
+const DrawerOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
+
+const DrawerContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+ {children}
+
+
+))
+DrawerContent.displayName = "DrawerContent"
+
+const DrawerHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DrawerHeader.displayName = "DrawerHeader"
+
+const DrawerFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DrawerFooter.displayName = "DrawerFooter"
+
+const DrawerTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DrawerTitle.displayName = DrawerPrimitive.Title.displayName
+
+const DrawerDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DrawerDescription.displayName = DrawerPrimitive.Description.displayName
+
+export {
+ Drawer,
+ DrawerPortal,
+ DrawerOverlay,
+ DrawerTrigger,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerFooter,
+ DrawerTitle,
+ DrawerDescription,
+}
diff --git a/components/ui/loader.tsx b/components/ui/loader.tsx
new file mode 100644
index 0000000..22413c9
--- /dev/null
+++ b/components/ui/loader.tsx
@@ -0,0 +1,25 @@
+const Loader = () => {
+ return (
+
+ );
+};
+
+export default Loader;
diff --git a/components/ui/popover.tsx b/components/ui/popover.tsx
new file mode 100644
index 0000000..29c7bd2
--- /dev/null
+++ b/components/ui/popover.tsx
@@ -0,0 +1,33 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverAnchor = PopoverPrimitive.Anchor
+
+const PopoverContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx
new file mode 100644
index 0000000..cc4e0ab
--- /dev/null
+++ b/components/ui/toast.tsx
@@ -0,0 +1,129 @@
+"use client"
+
+import * as React from "react"
+import { Cross2Icon } from "@radix-ui/react-icons"
+import * as ToastPrimitives from "@radix-ui/react-toast"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const ToastProvider = ToastPrimitives.Provider
+
+const ToastViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+
+const toastVariants = cva(
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+ {
+ variants: {
+ variant: {
+ default: "border bg-background text-foreground",
+ destructive:
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Toast = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, ...props }, ref) => {
+ return (
+
+ )
+})
+Toast.displayName = ToastPrimitives.Root.displayName
+
+const ToastAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastAction.displayName = ToastPrimitives.Action.displayName
+
+const ToastClose = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+ToastClose.displayName = ToastPrimitives.Close.displayName
+
+const ToastTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastTitle.displayName = ToastPrimitives.Title.displayName
+
+const ToastDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastDescription.displayName = ToastPrimitives.Description.displayName
+
+type ToastProps = React.ComponentPropsWithoutRef
+
+type ToastActionElement = React.ReactElement
+
+export {
+ type ToastProps,
+ type ToastActionElement,
+ ToastProvider,
+ ToastViewport,
+ Toast,
+ ToastTitle,
+ ToastDescription,
+ ToastClose,
+ ToastAction,
+}
diff --git a/components/ui/toaster.tsx b/components/ui/toaster.tsx
new file mode 100644
index 0000000..e223385
--- /dev/null
+++ b/components/ui/toaster.tsx
@@ -0,0 +1,35 @@
+"use client"
+
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from "@/components/ui/toast"
+import { useToast } from "@/components/ui/use-toast"
+
+export function Toaster() {
+ const { toasts } = useToast()
+
+ return (
+
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ return (
+
+
+ {title && {title}}
+ {description && (
+ {description}
+ )}
+
+ {action}
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/components/ui/use-toast.ts b/components/ui/use-toast.ts
new file mode 100644
index 0000000..0b6fa1e
--- /dev/null
+++ b/components/ui/use-toast.ts
@@ -0,0 +1,193 @@
+"use client"
+
+import * as React from "react"
+
+import type {
+ ToastActionElement,
+ ToastProps,
+} from "@/components/ui/toast"
+
+const TOAST_LIMIT = 1
+const TOAST_REMOVE_DELAY = 1000000
+
+type ToasterToast = ToastProps & {
+ id: string
+ title?: React.ReactNode
+ description?: React.ReactNode
+ action?: ToastActionElement
+}
+
+const actionTypes = {
+ ADD_TOAST: "ADD_TOAST",
+ UPDATE_TOAST: "UPDATE_TOAST",
+ DISMISS_TOAST: "DISMISS_TOAST",
+ REMOVE_TOAST: "REMOVE_TOAST",
+} as const
+
+let count = 0
+
+function genId() {
+ count = (count + 1) % Number.MAX_SAFE_INTEGER
+ return count.toString()
+}
+
+type ActionType = typeof actionTypes
+
+type Action =
+ | {
+ type: ActionType["ADD_TOAST"]
+ toast: ToasterToast
+ }
+ | {
+ type: ActionType["UPDATE_TOAST"]
+ toast: Partial
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+
+interface State {
+ toasts: ToasterToast[]
+}
+
+const toastTimeouts = new Map>()
+
+const addToRemoveQueue = (toastId: string) => {
+ if (toastTimeouts.has(toastId)) {
+ return
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId)
+ dispatch({
+ type: "REMOVE_TOAST",
+ toastId: toastId,
+ })
+ }, TOAST_REMOVE_DELAY)
+
+ toastTimeouts.set(toastId, timeout)
+}
+
+export const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ case "ADD_TOAST":
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ }
+
+ case "UPDATE_TOAST":
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t
+ ),
+ }
+
+ case "DISMISS_TOAST": {
+ const { toastId } = action
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId)
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id)
+ })
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t
+ ),
+ }
+ }
+ case "REMOVE_TOAST":
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ }
+ }
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ }
+ }
+}
+
+const listeners: Array<(state: State) => void> = []
+
+let memoryState: State = { toasts: [] }
+
+function dispatch(action: Action) {
+ memoryState = reducer(memoryState, action)
+ listeners.forEach((listener) => {
+ listener(memoryState)
+ })
+}
+
+type Toast = Omit
+
+function toast({ ...props }: Toast) {
+ const id = genId()
+
+ const update = (props: ToasterToast) =>
+ dispatch({
+ type: "UPDATE_TOAST",
+ toast: { ...props, id },
+ })
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
+
+ dispatch({
+ type: "ADD_TOAST",
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open) => {
+ if (!open) dismiss()
+ },
+ },
+ })
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ }
+}
+
+function useToast() {
+ const [state, setState] = React.useState(memoryState)
+
+ React.useEffect(() => {
+ listeners.push(setState)
+ return () => {
+ const index = listeners.indexOf(setState)
+ if (index > -1) {
+ listeners.splice(index, 1)
+ }
+ }
+ }, [state])
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
+ }
+}
+
+export { useToast, toast }
diff --git a/graphql/types.ts b/graphql/types.ts
new file mode 100644
index 0000000..9782a01
--- /dev/null
+++ b/graphql/types.ts
@@ -0,0 +1,2247 @@
+export type Maybe = T | null;
+export type InputMaybe = Maybe;
+export type Exact = { [K in keyof T]: T[K] };
+export type MakeOptional = Omit & { [SubKey in K]?: Maybe };
+export type MakeMaybe = Omit & { [SubKey in K]: Maybe };
+export type MakeEmpty = { [_ in K]?: never };
+export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
+/** All built-in and custom scalars, mapped to their actual values */
+export type Scalars = {
+ ID: { input: string; output: string; }
+ String: { input: string; output: string; }
+ Boolean: { input: boolean; output: boolean; }
+ Int: { input: number; output: number; }
+ Float: { input: number; output: number; }
+ Address: { input: any; output: any; }
+ Any: { input: any; output: any; }
+ DateRange: { input: any; output: any; }
+ Identity: { input: any; output: any; }
+ IntString: { input: any; output: any; }
+ Map: { input: any; output: any; }
+ Range: { input: any; output: any; }
+ Time: { input: any; output: any; }
+ TimeRange: { input: any; output: any; }
+};
+
+/** Represents on-chain smart contract account */
+export type Account = {
+ /** Nested query - on-chain wallet related information, including address, domains, social profile, other token balances, and transfer history */
+ address: Wallet;
+ /** Blockchain where account is created */
+ blockchain: Maybe;
+ /** Block number of the account creation transaction */
+ createdAtBlockNumber: Maybe;
+ /** Block timestamp of the account creation transaction */
+ createdAtBlockTimestamp: Maybe;
+ /** Transaction Hash of the account creation transaction */
+ creationTransactionHash: Maybe;
+ /** Address of deployer */
+ deployer: Maybe;
+ /** Airstack unique identifier for the account */
+ id: Scalars['ID']['output'];
+ /** ERC6551 standard : Implementation address of on chain smart contract account */
+ implementation: Maybe;
+ /** Token NFT associated with erc-6551 */
+ nft: Maybe;
+ /** ERC6551 standard : Registry used to deploy smart contract wallet */
+ registry: Maybe;
+ /** ERC6551 standard salt for account creation */
+ salt: Maybe;
+ /** Standard of account- ERC6551, Safe etc */
+ standard: AccountStandard;
+ /** ERC6551 standard: Address of ERC721 token */
+ tokenAddress: Maybe;
+ /** ERC6551 standard: tokenId of ERC721 token */
+ tokenId: Maybe;
+ /** Block number of the account updation transaction */
+ updatedAtBlockNumber: Maybe;
+ /** Block timestamp of the account updation transaction */
+ updatedAtBlockTimestamp: Maybe;
+};
+
+export type AccountFilter = {
+ address: InputMaybe;
+ createdAtBlockTimestamp: InputMaybe;
+ implementation: InputMaybe;
+ registry: InputMaybe;
+ salt: InputMaybe;
+ standard: InputMaybe;
+ tokenAddress: InputMaybe;
+ tokenId: InputMaybe;
+};
+
+export type AccountOrderBy = {
+ createdAtBlockTimestamp: InputMaybe;
+};
+
+export enum AccountStandard {
+ Erc6551 = 'ERC6551'
+}
+
+export type AccountStandard_Comparator_Exp = {
+ _eq: InputMaybe;
+ _in: InputMaybe>;
+};
+
+export type AccountsInput = {
+ blockchain: TokenBlockchain;
+ cursor: InputMaybe;
+ filter: InputMaybe;
+ limit: InputMaybe;
+ order: InputMaybe>;
+};
+
+export type AccountsNestedInput = {
+ blockchain: InputMaybe;
+ filter: InputMaybe;
+ limit: InputMaybe;
+ order: InputMaybe>>;
+ showOptimisticAddress: InputMaybe;
+};
+
+export type AccountsOutput = {
+ Account: Maybe>;
+ pageInfo: Maybe;
+};
+
+export type Address_Comparator_Exp = {
+ _eq: InputMaybe;
+ _in: InputMaybe>;
+ _ne: InputMaybe;
+ _nin: InputMaybe>;
+};
+
+export type AnimationUrlVariants = {
+ original: Maybe;
+};
+
+export enum Audience {
+ All = 'all',
+ Farcaster = 'farcaster'
+}
+
+export type AudioVariants = {
+ original: Maybe;
+};
+
+export enum Blockchain {
+ Ethereum = 'ethereum'
+}
+
+export type Boolean_Comparator_Exp = {
+ _eq: InputMaybe;
+};
+
+export type ConnectedAddress = {
+ address: Maybe;
+ blockchain: Maybe;
+ chainId: Maybe;
+ timestamp: Maybe;
+};
+
+export type ContractMetadata = {
+ /** Description of the token, mirrored from the smart contract */
+ description: Maybe;
+ externalLink: Maybe;
+ /** Royalties recipient address, mirrored from the smart contract */
+ feeRecipient: Maybe;
+ image: Maybe;
+ /** Name of the token, mirrored from the smart contract */
+ name: Maybe;
+ sellerFeeBasisPoints: Maybe;
+};
+
+export type Date_Range_Comparator_Exp = {
+ _eq: InputMaybe;
+};
+
+export type Domain = {
+ /** Avatar of the domain */
+ avatar: Maybe;
+ /** Blockchain where the NFT sale took place */
+ blockchain: Blockchain;
+ /** Unique identifier for the blockchain */
+ chainId: Maybe;
+ /** Block number when the domain was created */
+ createdAtBlockNumber: Maybe;
+ /** Timestamp when the domain was created */
+ createdAtBlockTimestamp: Maybe;
+ /** DApp name associated with the domain (e.g. ENS) */
+ dappName: Maybe;
+ /** DApp slug (contract version) associated with the domain */
+ dappSlug: Maybe;
+ /** Timestamp when the domain registration expires */
+ expiryTimestamp: Maybe;
+ /** Domain registration cost in decimals */
+ formattedRegistrationCost: Maybe;
+ /** Domain registration cost in native blockchain token in decimals */
+ formattedRegistrationCostInNativeToken: Maybe;
+ /** Domain registration cost in USDC in decimals */
+ formattedRegistrationCostInUSDC: Maybe;
+ /** Airstack unique identifier for the data point */
+ id: Maybe;
+ /** Domain is name wrapped or not */
+ isNameWrapped: Maybe;
+ /** Indicates if the domain is set to be primary - true or false */
+ isPrimary: Maybe;
+ /** Airstack unique domain hash */
+ labelHash: Maybe;
+ /** Domain name without the domain ending, e.g. vitalik instead of vitalik.eth */
+ labelName: Maybe;
+ /** Block number when the domain was last updated */
+ lastUpdatedBlockNumber: Maybe;
+ /** Timestamp when the domain was last updated */
+ lastUpdatedBlockTimestamp: Maybe;
+ /** Manager of Domain */
+ manager: Scalars['Address']['output'];
+ /** Manager wallet related information, including address, domains, social profile, other token balances, and transfer history */
+ managerDetails: Maybe;
+ /** Multichain associated with the domain */
+ multiChainAddresses: Maybe>;
+ /** Full domain name, e.g. vitalik.eth */
+ name: Maybe;
+ /** Owner of token associated with the domain */
+ owner: Scalars['Address']['output'];
+ /** Owner wallet related information, including address, domains, social profile, other token balances, and transfer history */
+ ownerDetails: Maybe;
+ /** Parent domain name, if the entity is a subdomain */
+ parent: Maybe;
+ /** Nested query - can retrieve payment token data (name, symbol, etc.) */
+ paymentToken: Maybe;
+ /** payment amount in blockchain native token for the domain */
+ paymentTokenCostInNativeToken: Maybe;
+ /** payment amount in USDC for the domain */
+ paymentTokenCostInUSDC: Maybe;
+ /** Domain registration cost */
+ registrationCost: Maybe;
+ /** Domain registration cost in blockchain native token */
+ registrationCostInNativeToken: Maybe;
+ /** Domain registration cost in USDC */
+ registrationCostInUSDC: Maybe;
+ /** Blockchain address to which the domain is resolved */
+ resolvedAddress: Maybe;
+ /** Nested query - on-chain resolvedAddress wallet related information, including address, domains, social profile, other token balances, and transfer history */
+ resolvedAddressDetails: Maybe;
+ /** Resolver address associated with Domain */
+ resolverAddress: Maybe;
+ /** Count of subdomains linked to the domain */
+ subDomainCount: Maybe;
+ /** Nested query allowing to retrieve subdomain information associated with the domain */
+ subDomains: Maybe>>;
+ /** Texts associated with the domain */
+ texts: Maybe>;
+ /** Token Address associated with the domain, if applicable */
+ tokenAddress: Scalars['Address']['output'];
+ /** Domain Token ID associated with the domain, if applicable */
+ tokenId: Maybe;
+ /** Token nft associated with the domain, if applicable */
+ tokenNft: Maybe;
+ /** Time-to-live value for the domain */
+ ttl: Maybe;
+};
+
+
+export type DomainSubDomainsArgs = {
+ input: InputMaybe;
+};
+
+export enum DomainDappName {
+ Ens = 'ens'
+}
+
+export type DomainDappName_Comparator_Exp = {
+ _eq: InputMaybe;
+ _in: InputMaybe>;
+};
+
+export enum DomainDappSlug {
+ EnsV1 = 'ens_v1'
+}
+
+export type DomainDappSlug_Comparator_Exp = {
+ _eq: InputMaybe;
+ _in: InputMaybe>;
+};
+
+export type DomainFilter = {
+ isPrimary: InputMaybe;
+ lastUpdatedBlockTimestamp: InputMaybe;
+ name: InputMaybe;
+ owner: InputMaybe;
+ resolvedAddress: InputMaybe;
+};
+
+export type DomainMultiChainAddress = {
+ /** address */
+ address: Maybe;
+ /** symbol according to SLIP-0044 */
+ symbol: Maybe;
+};
+
+export type DomainOrderBy = {
+ createdAtBlockTimestamp: InputMaybe;
+ expiryTimestamp: InputMaybe;
+ lastUpdatedBlockTimestamp: InputMaybe;
+};
+
+export type DomainTexts = {
+ /** key of the text */
+ key: Maybe;
+ /** value of the text */
+ value: Maybe;
+};
+
+export type DomainsInput = {
+ blockchain: Blockchain;
+ cursor: InputMaybe;
+ filter: DomainFilter;
+ limit: InputMaybe;
+ order: InputMaybe>;
+};
+
+export type DomainsNestedInput = {
+ blockchain: InputMaybe;
+ filter: InputMaybe;
+ limit: InputMaybe;
+ order: InputMaybe>>;
+};
+
+export type DomainsOutput = {
+ Domain: Maybe>;
+ pageInfo: Maybe;
+};
+
+export enum EveryBlockchain {
+ All = 'ALL'
+}
+
+export type FarcasterCast = {
+ castedAtTimestamp: Maybe;
+ castedBy: Maybe;
+ channel: Maybe;
+ embeds: Maybe>>;
+ fid: Maybe;
+ frame: Maybe;
+ hash: Maybe;
+ id: Maybe;
+ mentions: Maybe>;
+ numberOfLikes: Maybe;
+ numberOfRecasts: Maybe;
+ numberOfReplies: Maybe;
+ parentCast: Maybe;
+ parentFid: Maybe;
+ parentHash: Maybe;
+ quotedCast: Maybe>>;
+ rawText: Maybe;
+ rootParentUrl: Maybe;
+ socialCapitalValue: Maybe;
+ text: Maybe;
+ url: Maybe;
+};
+
+export type FarcasterCastFilter = {
+ castedAtTimestamp: InputMaybe;
+ castedBy: InputMaybe;
+ frameUrl: InputMaybe;
+ hasEmbeds: InputMaybe;
+ hasFrames: InputMaybe;
+ hasMentions: InputMaybe;
+ hash: InputMaybe;
+ rootParentUrl: InputMaybe;
+ url: InputMaybe;
+};
+
+export type FarcasterCastInput = {
+ blockchain: EveryBlockchain;
+ cursor: InputMaybe;
+ filter: FarcasterCastFilter;
+ limit: InputMaybe;
+};
+
+export type FarcasterCastOutput = {
+ Cast: Maybe>;
+ pageInfo: Maybe;
+};
+
+export type FarcasterChannel = {
+ channelId: Scalars['String']['output'];
+ createdAtTimestamp: Scalars['Time']['output'];
+ dappName: Scalars['String']['output'];
+ dappSlug: Scalars['String']['output'];
+ description: Scalars['String']['output'];
+ followerCount: Maybe;
+ /** Airstack unique identifier for the data point */
+ id: Scalars['ID']['output'];
+ imageUrl: Scalars['String']['output'];
+ isModerationEnabled: Maybe;
+ leadIds: Maybe>;
+ leadProfiles: Maybe>;
+ moderatorIds: Maybe>;
+ moderatorProfiles: Maybe>;
+ name: Scalars['String']['output'];
+ participants: Maybe>;
+ url: Scalars['String']['output'];
+};
+
+
+export type FarcasterChannelLeadProfilesArgs = {
+ input: InputMaybe;
+};
+
+
+export type FarcasterChannelModeratorProfilesArgs = {
+ input: InputMaybe;
+};
+
+
+export type FarcasterChannelParticipantsArgs = {
+ input: InputMaybe;
+};
+
+export enum FarcasterChannelActionType {
+ Cast = 'cast',
+ Follow = 'follow',
+ Reply = 'reply'
+}
+
+export type FarcasterChannelActionType_Comparator_Exp = {
+ _eq: InputMaybe;
+ _in: InputMaybe>;
+};
+
+export type FarcasterChannelFilter = {
+ channelId: InputMaybe;
+ createdAtTimestamp: InputMaybe;
+ leadId: InputMaybe;
+ leadIdentity: InputMaybe;
+ moderatorId: InputMaybe;
+ moderatorIdentity: InputMaybe;
+ name: InputMaybe;
+ url: InputMaybe;
+};
+
+export type FarcasterChannelNestedInput = {
+ blockchain: InputMaybe;
+ filter: InputMaybe;
+ limit: InputMaybe;
+ order: InputMaybe