diff --git a/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/ProfileUI.tsx b/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/ProfileUI.tsx new file mode 100644 index 00000000000..22cefdb784f --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/ProfileUI.tsx @@ -0,0 +1,133 @@ +import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { fetchPublishedContracts } from "components/contract-components/fetchPublishedContracts"; +import { PublisherSocials } from "components/contract-components/publisher/PublisherSocials"; +import { EditProfile } from "components/contract-components/publisher/edit-profile"; +import { PublisherAvatar } from "components/contract-components/publisher/masked-avatar"; +import { DeployedContracts } from "components/contract-components/tables/deployed-contracts"; +import type { ProfileMetadata } from "constants/schemas"; +import { Suspense } from "react"; +import { shortenIfAddress } from "utils/usedapp-external"; +import { getSortedDeployedContracts } from "../../../account/contracts/_components/getSortedDeployedContracts"; +import { PublishedContracts } from "./components/published-contracts"; + +export function ProfileUI(props: { + profileAddress: string; + ensName: string | undefined; + publisherProfile: ProfileMetadata; + showEditProfile: boolean; +}) { + const { profileAddress, ensName, publisherProfile, showEditProfile } = props; + + const displayName = shortenIfAddress(ensName || profileAddress).replace( + "deployer.thirdweb.eth", + "thirdweb.eth", + ); + + return ( +
+ {/* Header */} +
+
+ +
+

+ {displayName} +

+ + {publisherProfile.bio && ( +

+ {publisherProfile.bio} +

+ )} + +
+ +
+
+
+ + {showEditProfile && ( +
+ +
+ )} +
+ +
+ +
+

+ Published contracts +

+ +
+ }> + + +
+ +
+ +
+

+ Deployed contracts +

+ +

+ List of contracts deployed across all Mainnets +

+ +
+ }> + + +
+
+ ); +} + +async function AsyncDeployedContracts(props: { + profileAddress: string; +}) { + const contracts = await getSortedDeployedContracts({ + address: props.profileAddress, + onlyMainnet: true, + }); + + return ; +} + +async function AsyncPublishedContracts(props: { + publisherAddress: string; + publisherEnsName: string | undefined; +}) { + const publishedContracts = await fetchPublishedContracts( + props.publisherAddress, + ); + + if (publishedContracts.length === 0) { + return ( +
+ No published contracts found +
+ ); + } + + return ( + + ); +} + +function LoadingSection() { + return ( +
+ +
+ ); +} diff --git a/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/components/PublishedContractTable.tsx b/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/components/PublishedContractTable.tsx new file mode 100644 index 00000000000..73c042d2e3f --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/components/PublishedContractTable.tsx @@ -0,0 +1,205 @@ +import { Img } from "@/components/blocks/Img"; +import { Button } from "@/components/ui/button"; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { ToolTipLabel } from "@/components/ui/tooltip"; +import { TrackedLinkTW } from "@/components/ui/tracked-link"; +import { replaceDeployerAddress } from "components/explore/publisher"; +import { useTrack } from "hooks/analytics/useTrack"; +import { replaceIpfsUrl } from "lib/sdk"; +import { ShieldCheckIcon } from "lucide-react"; +import Link from "next/link"; +import { useMemo } from "react"; +import { type Column, type Row, useTable } from "react-table"; +import type { PublishedContractDetails } from "../../../../../components/contract-components/hooks"; + +interface PublishedContractTableProps { + contractDetails: ContractDataInput[]; + footer?: React.ReactNode; + publisherEnsName: string | undefined; +} + +type ContractDataInput = PublishedContractDetails; +type ContractDataRow = ContractDataInput["metadata"] & { + id: string; +}; + +function convertContractDataToRowData( + input: ContractDataInput, +): ContractDataRow { + return { + id: input.contractId, + ...input.metadata, + }; +} + +export function PublishedContractTable(props: PublishedContractTableProps) { + const { contractDetails, footer, publisherEnsName } = props; + const trackEvent = useTrack(); + const rows = useMemo( + () => contractDetails.map(convertContractDataToRowData), + [contractDetails], + ); + + const tableColumns: Column[] = useMemo(() => { + const cols: Column[] = [ + { + Header: "Logo", + accessor: (row) => row.logo, + // biome-ignore lint/suspicious/noExplicitAny: + Cell: (cell: any) => ( + + } + className="size-8" + /> + ), + }, + { + Header: "Name", + accessor: (row) => row.name, + // biome-ignore lint/suspicious/noExplicitAny: FIXME + Cell: (cell: any) => { + return ( + + {cell.value} + + ); + }, + }, + { + Header: "Description", + accessor: (row) => row.description, + // biome-ignore lint/suspicious/noExplicitAny: FIXME + Cell: (cell: any) => ( + + {cell.value} + + ), + }, + { + Header: "Version", + accessor: (row) => row.version, + // biome-ignore lint/suspicious/noExplicitAny: FIXME + Cell: (cell: any) => ( + {cell.value} + ), + }, + { + id: "audit-badge", + accessor: (row) => ({ audit: row.audit }), + // biome-ignore lint/suspicious/noExplicitAny: FIXME + Cell: (cell: any) => ( + + {cell.value.audit ? ( + + + + ) : null} + + ), + }, + ]; + + return cols; + }, [trackEvent, publisherEnsName]); + + const tableInstance = useTable({ + columns: tableColumns, + data: rows, + }); + + return ( + + + + {tableInstance.headerGroups.map((headerGroup) => { + const { key, ...rowProps } = headerGroup.getHeaderGroupProps(); + return ( + + {headerGroup.headers.map((column, columnIndex) => ( + + + {column.render("Header")} + + + ))} + + ); + })} + + + + {tableInstance.rows.map((row) => { + tableInstance.prepareRow(row); + return ; + })} + +
+ {footer} +
+ ); +} + +function ContractTableRow(props: { + row: Row; +}) { + const { row } = props; + const { key, ...rowProps } = row.getRowProps(); + return ( + <> + + {row.cells.map((cell) => ( + + {cell.render("Cell")} + + ))} + + + ); +} diff --git a/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/components/published-contracts.tsx b/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/components/published-contracts.tsx new file mode 100644 index 00000000000..2cda428919b --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/components/published-contracts.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { useState } from "react"; +import type { fetchPublishedContracts } from "../../../../../components/contract-components/fetchPublishedContracts"; +import { ShowMoreButton } from "../../../../../components/contract-components/tables/show-more-button"; +import { PublishedContractTable } from "./PublishedContractTable"; + +interface PublishedContractsProps { + limit?: number; + publishedContracts: Awaited>; + publisherEnsName: string | undefined; +} + +export const PublishedContracts: React.FC = ({ + limit = 10, + publishedContracts, + publisherEnsName, +}) => { + const [showMoreLimit, setShowMoreLimit] = useState(10); + const slicedData = publishedContracts.slice(0, showMoreLimit); + + return ( + slicedData.length ? ( + + ) : undefined + } + /> + ); +}; diff --git a/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/page.tsx b/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/page.tsx new file mode 100644 index 00000000000..b94cf5f50d1 --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/page.tsx @@ -0,0 +1,100 @@ +import { getActiveAccountCookie } from "@/constants/cookie"; +import { fetchPublisherProfile } from "components/contract-components/fetch-contracts-with-versions"; +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { getAddress } from "thirdweb/utils"; +import { shortenIfAddress } from "utils/usedapp-external"; +import { ProfileUI } from "./ProfileUI"; +import { resolveAddressAndEns } from "./resolveAddressAndEns"; + +type PageProps = { + params: Promise<{ + addressOrEns: string; + }>; +}; + +export default async function Page(props: PageProps) { + const params = await props.params; + const resolvedInfo = await resolveAddressAndEns(params.addressOrEns); + const currentUserAddress = await getCurrentUserAddress(); + + if (!resolvedInfo) { + return notFound(); + } + + const publisherProfile = await fetchPublisherProfile( + resolvedInfo.address, + ).catch(() => null); + + if (!publisherProfile) { + return notFound(); + } + + return ( + + ); +} + +export async function generateMetadata(props: PageProps): Promise { + const params = await props.params; + const resolvedInfo = await resolveAddressAndEns(params.addressOrEns); + + if (!resolvedInfo) { + return notFound(); + } + + const publisherProfile = await fetchPublisherProfile( + resolvedInfo.address, + ).catch(() => null); + + if (!publisherProfile) { + return notFound(); + } + + const displayName = shortenIfAddress( + resolvedInfo.ensName || resolvedInfo.address, + ).replace("deployer.thirdweb.eth", "thirdweb.eth"); + + // TODO - move this to opengraph-image.tsx + // this is not working even on prod + // const ogImageLink = ProfileOG.toUrl({ + // displayName, + // bio: publisherProfile.bio, + // avatar: publisherProfile.avatar, + // }); + + return { + title: displayName, + description: `Visit ${displayName}'s profile. See their published contracts and deploy them in one click.`, + // openGraph: { + // title: displayName, + // images: ogImageLink + // ? [ + // { + // url: ogImageLink.toString(), + // alt: `${displayName}'s profile on thirdweb.com`, + // width: 1200, + // height: 630, + // }, + // ] + // : undefined, + // }, + }; +} + +async function getCurrentUserAddress() { + try { + const currentUserAddress = await getActiveAccountCookie(); + if (!currentUserAddress) { + return null; + } + return getAddress(currentUserAddress); + } catch { + return null; + } +} diff --git a/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/resolveAddressAndEns.tsx b/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/resolveAddressAndEns.tsx new file mode 100644 index 00000000000..68dd1ef7346 --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/resolveAddressAndEns.tsx @@ -0,0 +1,32 @@ +import { getAddress, isAddress } from "thirdweb"; +import { mapThirdwebPublisher } from "../../../../components/contract-components/fetch-contracts-with-versions"; +import { isEnsName, resolveEns } from "../../../../lib/ens"; + +type ResolvedAddressInfo = { + address: string; + ensName: string | undefined; +}; +export async function resolveAddressAndEns( + addressOrEns: string, +): Promise { + if (isAddress(addressOrEns)) { + const res = await resolveEns(addressOrEns).catch(() => null); + return { + address: getAddress(addressOrEns), + ensName: res?.ensName || undefined, + }; + } + + if (isEnsName(addressOrEns)) { + const mappedEns = mapThirdwebPublisher(addressOrEns); + const res = await resolveEns(mappedEns).catch(() => null); + if (res?.address) { + return { + address: getAddress(res?.address), + ensName: mappedEns, + }; + } + } + + return undefined; +} diff --git a/apps/dashboard/src/app/account/contracts/_components/DeployedContractsTable.tsx b/apps/dashboard/src/app/account/contracts/_components/DeployedContractsTable.tsx index 824a76e38fe..4bd6d3a2ac0 100644 --- a/apps/dashboard/src/app/account/contracts/_components/DeployedContractsTable.tsx +++ b/apps/dashboard/src/app/account/contracts/_components/DeployedContractsTable.tsx @@ -11,7 +11,6 @@ export function DeployedContractsTable(props: { ); diff --git a/apps/dashboard/src/app/account/contracts/_components/getSortedDeployedContracts.tsx b/apps/dashboard/src/app/account/contracts/_components/getSortedDeployedContracts.tsx index c0eb66b4a7d..ae6c6bb5b26 100644 --- a/apps/dashboard/src/app/account/contracts/_components/getSortedDeployedContracts.tsx +++ b/apps/dashboard/src/app/account/contracts/_components/getSortedDeployedContracts.tsx @@ -5,6 +5,7 @@ import { fetchChain } from "../../../../utils/fetchChain"; export async function getSortedDeployedContracts(params: { address: string; + onlyMainnet?: boolean; }) { const contracts = await getAllMultichainRegistry({ contract: MULTICHAIN_REGISTRY_CONTRACT, @@ -36,6 +37,11 @@ export async function getSortedDeployedContracts(params: { } mainnetContracts.sort((a, b) => a.chainId - b.chainId); + + if (params.onlyMainnet) { + return mainnetContracts; + } + testnetContracts.sort((a, b) => a.chainId - b.chainId); return [...mainnetContracts, ...testnetContracts]; diff --git a/apps/dashboard/src/components/connect/PlaygroundMenu.tsx b/apps/dashboard/src/components/connect/PlaygroundMenu.tsx index 1ab0ebf9b6c..ac305222f38 100644 --- a/apps/dashboard/src/components/connect/PlaygroundMenu.tsx +++ b/apps/dashboard/src/components/connect/PlaygroundMenu.tsx @@ -1,7 +1,7 @@ +import { useDashboardRouter } from "@/lib/DashboardRouter"; import { type BoxProps, Flex } from "@chakra-ui/react"; import { ChakraNextImage } from "components/Image"; import type { StaticImageData } from "next/image"; -import { useRouter } from "next/router"; import { Card, Text } from "tw-components"; interface PlaygroundMenuProps extends BoxProps { @@ -22,7 +22,7 @@ export const PlaygroundMenu: React.FC = ({ onClick, ...rest }) => { - const router = useRouter(); + const router = useDashboardRouter(); return ( = ({ contractDetails, isFetching, children, hidePublisher }) => { - const rows = useMemo( - () => contractDetails.map(convertContractDataToRowData), - [contractDetails], - ); - - const trackEvent = useTrack(); - - const tableColumns: Column[] = useMemo(() => { - const cols: Column[] = [ - { - Header: "Logo", - accessor: (row) => row.logo, - // biome-ignore lint/suspicious/noExplicitAny: FIXME - Cell: (cell: any) => - cell.value ? ( - typeof cell.value === "string" ? ( - - ) : ( - - ) - ) : null, - }, - { - Header: "Name", - accessor: (row) => row.name, - // biome-ignore lint/suspicious/noExplicitAny: FIXME - Cell: (cell: any) => ( - - {cell.value} - - ), - }, - { - Header: "Description", - accessor: (row) => row.description, - // biome-ignore lint/suspicious/noExplicitAny: FIXME - Cell: (cell: any) => ( - - {cell.value} - - ), - }, - { - Header: "Version", - accessor: (row) => row.version, - // biome-ignore lint/suspicious/noExplicitAny: FIXME - Cell: (cell: any) => {cell.value}, - }, - { - Header: "Published By", - accessor: (row) => row.publisher, - // biome-ignore lint/suspicious/noExplicitAny: FIXME - Cell: (cell: any) => , - }, - { - id: "audit-badge", - accessor: (row) => ({ audit: row.audit }), - // biome-ignore lint/suspicious/noExplicitAny: FIXME - Cell: (cell: any) => ( - - {cell.value.audit ? ( - - Audited Contract - - } - borderRadius="lg" - placement="top" - > - } - onClick={(e) => { - e.stopPropagation(); - trackEvent({ - category: "visit-audit", - action: "click", - label: cell.value.audit, - }); - }} - /> - - ) : null} - - ), - }, - ]; - - return cols.filter( - (col) => !hidePublisher || col.Header !== "Published By", - ); - // this is to avoid re-rendering of the table when the contractIds array changes (it will always be a string array, so we can just join it and compare the string output) - }, [trackEvent, hidePublisher]); - - const tableInstance = useTable({ - columns: tableColumns, - data: rows, - }); - return ( - - {isFetching && ( - - )} - - - {tableInstance.headerGroups.map((headerGroup, headerGroupIndex) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: FIXME - - {headerGroup.headers.map((column, columnIndex) => ( - - ))} - {/* Need to add an empty header for the icon button */} - - ))} - - - {tableInstance.rows.map((row) => { - tableInstance.prepareRow(row); - return ; - })} - -
- - {column.render("Header")} - - -
- {children} -
- ); -}; - -interface ContractTableRowProps { - row: Row; -} - -const ContractTableRow: React.FC = ({ row }) => { - const router = useRouter(); - - return ( - <> - { - router.push( - replaceDeployerAddress( - `/${row.original.publisher}/${row.original.id}`, - ), - undefined, - { - scroll: true, - }, - ); - }} - {...row.getRowProps()} - > - {row.cells.map((cell) => ( - - {cell.render("Cell")} - - ))} - - - - - - ); -}; diff --git a/apps/dashboard/src/components/contract-components/fetch-contracts-with-versions.ts b/apps/dashboard/src/components/contract-components/fetch-contracts-with-versions.ts index 8822f88eeee..aa2a7033a29 100644 --- a/apps/dashboard/src/components/contract-components/fetch-contracts-with-versions.ts +++ b/apps/dashboard/src/components/contract-components/fetch-contracts-with-versions.ts @@ -10,15 +10,17 @@ import { } from "thirdweb/extensions/thirdweb"; import { download } from "thirdweb/storage"; -function mapThirdwebPublisher(publisher: string) { +export function mapThirdwebPublisher(publisher: string) { if (publisher === "thirdweb.eth") { return "deployer.thirdweb.eth"; } + return publisher; } export async function fetchPublisherProfile(publisherAddress: string) { const client = getThirdwebClient(); + const profileUri = await getPublisherProfileUri({ contract: getContractPublisher(client), publisher: isAddress(publisherAddress) @@ -28,9 +30,11 @@ export async function fetchPublisherProfile(publisherAddress: string) { name: mapThirdwebPublisher(publisherAddress), }), }); + if (!profileUri) { return null; } + try { const res = await download({ client, diff --git a/apps/dashboard/src/components/contract-components/publisher/PublisherSocials.tsx b/apps/dashboard/src/components/contract-components/publisher/PublisherSocials.tsx index 6bf8f4e58bf..2538de3f19a 100644 --- a/apps/dashboard/src/components/contract-components/publisher/PublisherSocials.tsx +++ b/apps/dashboard/src/components/contract-components/publisher/PublisherSocials.tsx @@ -1,4 +1,5 @@ -import { ButtonGroup, type ButtonGroupProps } from "@chakra-ui/react"; +import { Button } from "@/components/ui/button"; +import { TrackedLinkTW } from "@/components/ui/tracked-link"; import { DiscordIcon } from "components/icons/brand-icons/DiscordIcon"; import { GithubIcon } from "components/icons/brand-icons/GithubIcon"; import { LinkedInIcon } from "components/icons/brand-icons/LinkedinIcon"; @@ -9,163 +10,128 @@ import { TelegramIcon } from "components/icons/brand-icons/TelegramIcon"; import { XIcon } from "components/icons/brand-icons/XIcon"; import type { ProfileMetadata } from "constants/schemas"; import { GlobeIcon } from "lucide-react"; -import { LinkButton, TrackedIconButton } from "tw-components"; import { hostnameEndsWith } from "../../../utils/url"; const TRACKING_CATEGORY = "releaser-header"; -interface PublisherSocialsProps extends ButtonGroupProps { +export const PublisherSocials: React.FC<{ publisherProfile: ProfileMetadata; -} - -export const PublisherSocials: React.FC = ({ - publisherProfile, - spacing = 0, - size = "sm", - ...props -}) => ( - +}> = ({ publisherProfile }) => ( +
{publisherProfile.twitter && ( } - category={TRACKING_CATEGORY} label="twitter" + icon={XIcon} /> )} + {publisherProfile.discord && ( } - category={TRACKING_CATEGORY} + icon={DiscordIcon} label="discord" /> )} + {publisherProfile.github && ( } - category={TRACKING_CATEGORY} + icon={GithubIcon} label="github" /> )} + {publisherProfile.website && ( } - category={TRACKING_CATEGORY} + icon={GlobeIcon} label="website" /> )} {publisherProfile.medium && ( } - category={TRACKING_CATEGORY} + icon={MediumIcon} label="medium" /> )} + {publisherProfile.telegram && ( } - category={TRACKING_CATEGORY} + icon={TelegramIcon} label="telegram" /> )} + {publisherProfile.facebook && ( } - category={TRACKING_CATEGORY} + icon={MetaIcon} label="facebook" /> )} + {publisherProfile.reddit && ( } - category={TRACKING_CATEGORY} + icon={RedditIcon} label="reddit" /> )} + {publisherProfile.linkedin && ( } - category={TRACKING_CATEGORY} + icon={LinkedInIcon} label="linkedin" /> )} - +
); + +function TrackedIconButton(props: { + icon: React.FC<{ className?: string }>; + href: string; + label: string; +}) { + return ( + + ); +} diff --git a/apps/dashboard/src/components/contract-components/publisher/edit-profile.tsx b/apps/dashboard/src/components/contract-components/publisher/edit-profile.tsx index 306a9660320..8664c42ec20 100644 --- a/apps/dashboard/src/components/contract-components/publisher/edit-profile.tsx +++ b/apps/dashboard/src/components/contract-components/publisher/edit-profile.tsx @@ -1,6 +1,9 @@ "use client"; +import { FormFieldSetup } from "@/components/blocks/FormFieldSetup"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { Sheet, SheetContent, @@ -8,12 +11,10 @@ import { SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; +import { Textarea } from "@/components/ui/textarea"; import { useThirdwebClient } from "@/constants/thirdweb.client"; -import { Box, FormControl, Input, Textarea } from "@chakra-ui/react"; +import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import { useQueryClient } from "@tanstack/react-query"; -import { DiscordIcon } from "components/icons/brand-icons/DiscordIcon"; -import { GithubIcon } from "components/icons/brand-icons/GithubIcon"; -import { XIcon } from "components/icons/brand-icons/XIcon"; import { FileInput } from "components/shared/FileInput"; import { DASHBOARD_ENGINE_RELAYER_URL, @@ -22,7 +23,7 @@ import { import type { ProfileMetadata, ProfileMetadataInput } from "constants/schemas"; import { useTrack } from "hooks/analytics/useTrack"; import { useImageFileOrUrl } from "hooks/useImageFileOrUrl"; -import { EditIcon, GlobeIcon, ImageIcon, PencilIcon } from "lucide-react"; +import { EditIcon } from "lucide-react"; import { useId, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -32,7 +33,6 @@ import { } from "thirdweb/extensions/thirdweb"; import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react"; import { upload } from "thirdweb/storage"; -import { FormErrorMessage, FormLabel } from "tw-components"; import { MaskedAvatar } from "tw-components/masked-avatar"; interface EditProfileProps { @@ -73,16 +73,16 @@ export const EditProfile: React.FC = ({ return ( - - + Edit your profile
{ if (!address) { @@ -93,150 +93,146 @@ export const EditProfile: React.FC = ({ action: "edit", label: "attempt", }); - try { - const tx = setPublisherProfileUri({ - contract: getContractPublisher(client), - asyncParams: async () => { - return { - publisher: address, - uri: await upload({ - files: [d], - client, - }), - }; - }, - }); - const promise = sendTx.mutateAsync(tx, { - onSuccess: async () => { - await queryClient.invalidateQueries({ - queryKey: ["releaser-profile", address], - }); - trackEvent({ - category: "profile", - action: "edit", - label: "success", - }); - setOpen(false); - }, - onError: (error) => { - trackEvent({ - category: "profile", - action: "edit", - label: "error", - error, - }); - }, - }); - toast.promise(promise, { - loading: "Updating profile", - success: "Profile updated successfully", - error: "Failed to update profile", - }); - } catch (err) { - console.error(err); - toast.error("Failed to update profile"); - } + const tx = setPublisherProfileUri({ + contract: getContractPublisher(client), + asyncParams: async () => { + return { + publisher: address, + uri: await upload({ + files: [d], + client, + }), + }; + }, + }); + + const promise = sendTx.mutateAsync(tx, { + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ["releaser-profile", address], + }); + trackEvent({ + category: "profile", + action: "edit", + label: "success", + }); + setOpen(false); + }, + onError: (error) => { + trackEvent({ + category: "profile", + action: "edit", + label: "error", + error, + }); + }, + }); + + toast.promise(promise, { + success: "Profile updated successfully", + error: "Failed to update profile", + }); })} > - - -
- - Avatar -
-
- - setValue("avatar", file)} - className="rounded border border-border transition-all" - renderPreview={(fileUrl) => ( - - )} - /> - - - {errors?.avatar?.message as unknown as string} - -
- - -
- - Bio -
-
+ + setValue("avatar", file)} + className="max-w-[250px] rounded border border-border transition-all" + renderPreview={(fileUrl) => ( + + )} + previewMaxWidth="200px" + /> + + +