Skip to content

Commit

Permalink
fix: images not showing properly (#179)
Browse files Browse the repository at this point in the history
* image fallback

* fixed lounge

* refactor: extracted starknet nft api calls to its own helper

* refactor: endpoints

* fixed ark project logo link not working when both wallets connected
  • Loading branch information
YohanTz authored Apr 5, 2024
1 parent 041e911 commit 3656a86
Show file tree
Hide file tree
Showing 16 changed files with 238 additions and 204 deletions.
10 changes: 9 additions & 1 deletion apps/web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ const config = {
defaultLocale: "en",
},
images: {
remotePatterns: [{ protocol: "https", hostname: "**" }],
// remotePatterns: [{ protocol: "https", hostname: "**" }],
remotePatterns: [
{
protocol: "https",
hostname: "**",
port: "",
pathname: "**",
},
],
},
webpack: (config) => {
config.externals.push("pino-pretty", "lokijs", "encoding");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default function TransferNftsList() {
media={selectedNft.media}
width={52}
/>
<div className="flex flex-col">
<div className="flex w-full flex-col">
<Typography ellipsable variant="body_text_14">
{selectedNft.collectionName}
</Typography>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,17 @@ export default function useEthereumCollectionApproval() {

const {
data: approveHash,
error,
failureReason,
isLoading: isSigning,
writeContract: writeContractApprove,
} = useWriteContract();
console.error("error: ", error);
console.error("failureReason: ", failureReason);

function approveForAll() {
try {
writeContractApprove({
abi: erc721Abi,
address: selectedCollectionAddress as `0x${string}`,
args: [
process.env.NEXT_PUBLIC_L1_BRIDGE_ADDRESS as `0x${string}`,
true,
],
functionName: "setApprovalForAll",
});
} catch (e) {
console.error(e);
}
writeContractApprove({
abi: erc721Abi,
address: selectedCollectionAddress as `0x${string}`,
args: [process.env.NEXT_PUBLIC_L1_BRIDGE_ADDRESS as `0x${string}`, true],
functionName: "setApprovalForAll",
});
}

const { isLoading: isApproveLoading } = useWaitForTransactionReceipt({
Expand Down
10 changes: 7 additions & 3 deletions apps/web/src/app/(routes)/lounge/_components/NftTransferItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ function getDisplayedDate(timestamp?: number) {

const date = new Date(timestamp * 1000);

return `${
date.getMonth() + 1
}/${date.getDate()}/${date.getFullYear()} - ${date.getHours()}:${date.getMinutes()}`;
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
const year = date.getFullYear();
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");

return `${month}/${day}/${year} - ${hours}:${minutes}`;
}

export default function NftTransferItem({
Expand Down
15 changes: 0 additions & 15 deletions apps/web/src/app/(routes)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
"use client";

import { useAccount as useStarknetAccount } from "@starknet-react/core";
import { Typography } from "design-system";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { useAccount as useEthereumAccount } from "wagmi";

import ConnectWalletsButton from "../_components/ConnectWalletsButton";
import Footer from "../_components/Footer";

export default function Page() {
const { address: starknetAddress } = useStarknetAccount();
const { address: ethereumAddress } = useEthereumAccount();

const router = useRouter();

useEffect(() => {
if (starknetAddress !== undefined && ethereumAddress !== undefined) {
void router.push("/bridge");
}
}, [starknetAddress, ethereumAddress, router]);

return (
<>
<div className="flex">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function NftTabsList() {
} = useInfiniteStarknetCollections();

return (
<Tabs.List className="flex items-center gap-4 overflow-x-scroll">
<Tabs.List className="flex items-center gap-4 overflow-x-auto">
<NftTabsTrigger
totalCount={
l1CollectionsTotalCount === undefined ||
Expand Down
25 changes: 14 additions & 11 deletions apps/web/src/app/(routes)/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"use client";

import {
type Chain,
// type Chain,
// goerli as starknetGoerli,
mainnet as starknetMainnet,
} from "@starknet-react/chains";
import {
StarknetConfig,
// jsonRpcProvider,
publicProvider,
jsonRpcProvider,
// publicProvider,
// publicProvider,
} from "@starknet-react/core";
import { ThemeProvider } from "next-themes";
Expand All @@ -25,22 +26,24 @@ const wagmiConfig = createConfig({
// chains: [goerli],
chains: [mainnet],
connectors: ethereumConnectors,
ssr: true,
ssr: false,
transports: {
[mainnet.id]: http(
process.env.NEXT_PUBLIC_ALCHEMY_ETHEREUM_RPC_ENDPOINT ?? ""
),
},
});

// function starknetRpc(chain: Chain) {
// if (chain.network === "goerli")
// return { nodeUrl: `https://juno.testnet.arkproject.dev/` };
function starknetRpc(chain: Chain) {
if (chain.network === "goerli")
return { nodeUrl: `https://juno.testnet.arkproject.dev/` };

// return { nodeUrl: `https://juno.mainnet.arkproject.dev/` };
// }
return {
nodeUrl: process.env.NEXT_PUBLIC_ALCHEMY_STARKNET_RPC_ENDPOINT ?? "",
};
}

const starknetProvider = publicProvider();
// const starknetProvider = publicProvider();

interface ProvidersProps {
children: React.ReactNode;
Expand All @@ -53,8 +56,8 @@ export default function Providers({ children }: ProvidersProps) {
// chains={[starknetGoerli]}
chains={[starknetMainnet]}
connectors={starknetConnectors}
// provider={jsonRpcProvider({ rpc: starknetRpc })}
provider={starknetProvider}
provider={jsonRpcProvider({ rpc: starknetRpc })}
// provider={starknetProvider}
>
<WagmiProvider config={wagmiConfig}>
<WalletModalsProvider>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/_components/ConnectWalletsButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function ConnectWalletsButton() {
toggleConnectEthereumWalletModal();
return;
}
toggleConnectWalletsModal;
toggleConnectWalletsModal();
}

return (
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/app/_components/Media.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Image from "next/image";
import { useTheme } from "next-themes";
import { useState } from "react";

import { type NftMedia } from "~/server/api/types";
/* eslint-disable @next/next/no-img-element */
Expand All @@ -20,8 +21,9 @@ export default function Media({
width,
}: MediaProps) {
const { resolvedTheme } = useTheme();
const [hasFailedToLoad, setHasFailedToLoad] = useState(false);

if (media.src === undefined || media.src.length === 0) {
if (media.src === undefined || media.src.length === 0 || hasFailedToLoad) {
return (
<Image
alt={alt}
Expand Down Expand Up @@ -53,6 +55,7 @@ export default function Media({
alt={alt}
className={className}
height={height}
onError={() => setHasFailedToLoad(true)}
src={media.src}
width={width}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
useNetwork as useStarknetNetwork,
} from "@starknet-react/core";
import { SideDialog } from "design-system";
import { usePathname } from "next/navigation";
import { usePathname, useRouter } from "next/navigation";
import {
type PropsWithChildren,
createContext,
Expand Down Expand Up @@ -38,6 +38,7 @@ export function WalletModalsProvider({ children }: PropsWithChildren) {
>(null);

const pathname = usePathname();
const router = useRouter();

const toggleConnectEthereumWalletModal = useCallback(() => {
if (userOpenedModal === "ethereumWallet") {
Expand Down Expand Up @@ -88,6 +89,7 @@ export function WalletModalsProvider({ children }: PropsWithChildren) {
ethereumAddress !== undefined
) {
setUserOpenedModal(null);
void router.push("/bridge");
return;
}
}, [starknetAddress, ethereumAddress, userOpenedModal]);
Expand All @@ -111,8 +113,7 @@ export function WalletModalsProvider({ children }: PropsWithChildren) {
return;
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pathname]);
}, [pathname, starknetAddress, ethereumAddress]);

return (
<WalletModalsContext.Provider
Expand Down
16 changes: 16 additions & 0 deletions apps/web/src/server/api/helpers/l1nfts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type Media } from "alchemy-sdk";

import { type NftMedia } from "../types";

export function getMediaObjectFromAlchemyMedia(
alchemyMedia: Media | undefined
): NftMedia {
if (alchemyMedia === undefined) {
return { format: "image", src: undefined };
}
const mediaSrc =
alchemyMedia?.gateway ?? alchemyMedia?.thumbnail ?? alchemyMedia?.raw;
const mediaFormat = alchemyMedia?.format === "mp4" ? "video" : "image";

return { format: mediaFormat, src: mediaSrc };
}
124 changes: 124 additions & 0 deletions apps/web/src/server/api/helpers/l2nfts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { validateAndParseAddress } from "starknet";

import { type NftMedia } from "../types";

const requestsHeader = {
"Content-Type": "application/json",
"X-API-KEY": process.env.ARK_API_KEY ?? "",
};
const nftApiUrl = process.env.NEXT_PUBLIC_ARK_API_DOMAIN ?? "";

type ArkCollectionsApiResponse = {
result: Array<{
contract_address: string;
contract_type: string;
image?: string;
name: string;
symbol: string;
tokens_count: number;
}>;
total_count: number;
};
export async function getL2ContractsForOwner(address: string) {
const url = `${nftApiUrl}/v1/owners/${validateAndParseAddress(
address
)}/contracts`;

const contractsResponse = await fetch(url, {
headers: requestsHeader,
});
const contracts =
(await contractsResponse.json()) as ArkCollectionsApiResponse;

return contracts;
}

type ArkBatchNftsApiResponse = {
result: Array<{
contract_address: string;
contract_name: string;
metadata?: { normalized: { image?: string; name?: string } };
owner: string;
token_id: string;
}>;
};
export async function getL2NftsMetadataBatch(
tokens: Array<{ contract_address: string; token_id: string }>
) {
const url = `${nftApiUrl}/v1/tokens/batch`;

const nftsResponse = await fetch(url, {
body: JSON.stringify({
tokens: tokens.map((token) => ({
contract_address: validateAndParseAddress(token.contract_address),
token_id: token.token_id,
})),
}),
headers: requestsHeader,
method: "POST",
});

const nfts = (await nftsResponse.json()) as ArkBatchNftsApiResponse;

return nfts;
}

type ArkNftsApiResponse = {
result: Array<{
contract_address: string;
metadata: {
normalized: { image: null | string; name: null | string };
} | null;
owner: string;
token_id: string;
}>;
total_count: number;
};
export async function getL2NftsForOwner(
userAddress: string,
contractAddress: string | undefined
) {
const url = `${nftApiUrl}/v1/owners/${validateAndParseAddress(
userAddress
)}/tokens${
contractAddress !== undefined
? `?contract_address=${validateAndParseAddress(contractAddress)}`
: ""
}`;

const nftsResponse = await fetch(url, {
headers: requestsHeader,
});

const nfts = (await nftsResponse.json()) as ArkNftsApiResponse;

return nfts;
}

type ArkCollectionInfoApiResponse = {
result: { contract_address: string; name: string; symbol: string };
};
export async function getL2ContractMetadata(contractAddress: string) {
const url = `${nftApiUrl}/v1/contracts/${validateAndParseAddress(
contractAddress
)}`;

const contractInfoResponse = await fetch(url, {
headers: requestsHeader,
});

const contractInfo =
(await contractInfoResponse.json()) as ArkCollectionInfoApiResponse;

return contractInfo;
}

export function getMediaObjectFromUrl(image: string | undefined): NftMedia {
if (image === undefined) {
return { format: "image", src: undefined };
}
const mediaSrc = image.replace("ipfs://", process.env.IPFS_GATEWAY ?? "");
const mediaFormat = mediaSrc?.split(".").pop() === "mp4" ? "video" : "image";

return { format: mediaFormat, src: mediaSrc };
}
Loading

0 comments on commit 3656a86

Please sign in to comment.