diff --git a/pkgs/frontend/.env.example b/pkgs/frontend/.env.example index b1024c6..86c625b 100644 --- a/pkgs/frontend/.env.example +++ b/pkgs/frontend/.env.example @@ -12,4 +12,6 @@ VITE_SPLITS_CREATOR_ADDRESS=0x9c3648df4bb82fdf067a9b083900a986f9b27e9a VITE_PIMLICO_API_KEY= -VITE_PINATA_JWT= \ No newline at end of file +VITE_PINATA_JWT= + +VITE_PINATA_GATEWAY= \ No newline at end of file diff --git a/pkgs/frontend/app/routes/_index.tsx b/pkgs/frontend/app/routes/_index.tsx index 1bf028f..dabd1c3 100644 --- a/pkgs/frontend/app/routes/_index.tsx +++ b/pkgs/frontend/app/routes/_index.tsx @@ -1,7 +1,11 @@ -import { Box } from "@chakra-ui/react"; +import { Box, Input } from "@chakra-ui/react"; import type { MetaFunction } from "@remix-run/node"; import { CommonButton } from "~/components/common/CommonButton"; import { useBigBang } from "hooks/useBigBang"; +import { + useUploadMetadataToIpfs, + useUploadImageFileToIpfs, +} from "hooks/useIpfs"; export const meta: MetaFunction = () => { return [ @@ -12,6 +16,13 @@ export const meta: MetaFunction = () => { export default function Index() { const { bigbang, isLoading } = useBigBang(); + const { uploadMetadataToIpfs, isLoading: isUploadingMetadataToIpfs } = + useUploadMetadataToIpfs(); + const { + uploadImageFileToIpfs, + setImageFile, + isLoading: isUploadingImageFileToIpfs, + } = useUploadImageFileToIpfs(); const handleBigBang = async () => { const res = await bigbang({ @@ -26,11 +37,39 @@ export default function Index() { console.log(res); }; + const metadata = { + name: "Toban test", + description: "Toban test", + responsibilities: "Toban test", + authorities: "Toban test", + eligibility: true, + toggle: true, + }; + return ( BigBang + uploadMetadataToIpfs(metadata)} + > + Upload Metadata to IPFS + + ) => + setImageFile(e.target.files?.[0] || null) + } + /> + + Upload Image File to IPFS + ); } diff --git a/pkgs/frontend/hooks/useIpfs.ts b/pkgs/frontend/hooks/useIpfs.ts index 127c718..6b18ffa 100644 --- a/pkgs/frontend/hooks/useIpfs.ts +++ b/pkgs/frontend/hooks/useIpfs.ts @@ -1,9 +1,9 @@ import { useState } from "react"; -import { PinataSDK } from "pinata-web3"; -import { Readable } from "stream"; +import { ipfsUploadJson, ipfsUploadFile } from "utils/ipfs"; export const useUploadMetadataToIpfs = () => { const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); const uploadMetadataToIpfs = async ({ name, @@ -19,13 +19,12 @@ export const useUploadMetadataToIpfs = () => { authorities: string; eligibility: boolean; toggle: boolean; - }) => { + }): Promise<{ ipfsCid: string; ipfsUri: string } | null> => { setIsLoading(true); + setError(null); try { - const pinata = new PinataSDK({ pinataJwt: process.env.VITE_PINATA_JWT }); - - const upload = await pinata.upload.json({ + const upload = await ipfsUploadJson({ type: "1.0", data: { name, @@ -37,45 +36,64 @@ export const useUploadMetadataToIpfs = () => { }, }); - console.log("CID:", upload.IpfsHash); - console.log("URI:", `ipfs://${upload.IpfsHash}`); - } catch (error) { - console.error(error); + const ipfsCid = upload.IpfsHash; + const ipfsUri = `ipfs://${ipfsCid}`; + + console.log("Successfully uploaded metadata to IPFS"); + console.log("IPFS CID:", ipfsCid); + console.log("IPFS URI:", ipfsUri); + + return { ipfsCid, ipfsUri }; + } catch (err) { + setError( + err instanceof Error ? err : new Error("Unknown error occurred") + ); + return null; } finally { setIsLoading(false); } }; - return { uploadMetadataToIpfs, isLoading }; + return { uploadMetadataToIpfs, isLoading, error }; }; -export const useUploadImageToIpfs = () => { +export const useUploadImageFileToIpfs = () => { const [isLoading, setIsLoading] = useState(false); const [imageFile, setImageFile] = useState(null); + const [error, setError] = useState(null); - const uploadImageToIpfs = async () => { - if (!imageFile) return; + const uploadImageFileToIpfs = async (): Promise<{ + ipfsCid: string; + ipfsUri: string; + } | null> => { + if (!imageFile || !imageFile.type.startsWith("image/")) { + setError(new Error("Invalid or no image file selected")); + return null; + } setIsLoading(true); + setError(null); try { - const pinata = new PinataSDK({ pinataJwt: process.env.VITE_PINATA_JWT }); + const upload = await ipfsUploadFile(imageFile); - const buffer = await imageFile.arrayBuffer(); - const stream = Readable.from(Buffer.from(buffer)); + const ipfsCid = upload.IpfsHash; + const ipfsUri = `ipfs://${ipfsCid}`; - const upload = await pinata.upload.stream(stream, { - metadata: { name: `TobanFrontend_${new Date().getTime()}` }, - }); + console.log("Successfully uploaded image file to IPFS"); + console.log("IPFS CID:", ipfsCid); + console.log("IPFS URI:", ipfsUri); - console.log("CID:", upload.IpfsHash); - console.log("URI:", `ipfs://${upload.IpfsHash}`); - } catch (error) { - console.error(error); + return { ipfsCid, ipfsUri }; + } catch (err) { + setError( + err instanceof Error ? err : new Error("Unknown error occurred") + ); + return null; } finally { setIsLoading(false); } }; - return { uploadImageToIpfs, setImageFile, isLoading }; + return { uploadImageFileToIpfs, imageFile, setImageFile, isLoading, error }; }; diff --git a/pkgs/frontend/utils/ipfs.ts b/pkgs/frontend/utils/ipfs.ts new file mode 100644 index 0000000..d22d008 --- /dev/null +++ b/pkgs/frontend/utils/ipfs.ts @@ -0,0 +1,51 @@ +import { PinataSDK } from "pinata-web3"; + +const getPinataConfig = () => { + const pinataJwt = import.meta.env.VITE_PINATA_JWT; + const pinataGateway = import.meta.env.VITE_PINATA_GATEWAY; + + if (!pinataJwt) { + throw new Error("VITE_PINATA_JWT is not defined"); + } + if (!pinataGateway) { + throw new Error("VITE_PINATA_GATEWAY is not defined"); + } + + return { pinataJwt, pinataGateway }; +}; + +let ipfsClient: PinataSDK | null = null; + +export const createIpfsClient = () => { + if (ipfsClient) return ipfsClient; + + const { pinataJwt, pinataGateway } = getPinataConfig(); + ipfsClient = new PinataSDK({ + pinataJwt, + pinataGateway, + }); + + return ipfsClient; +}; + +export const ipfsUploadJson = async (object: object) => { + try { + const ipfsClient = createIpfsClient(); + const upload = await ipfsClient.upload.json(object); + return upload; + } catch (error) { + console.error("Failed to upload JSON to IPFS:", error); + throw error; + } +}; + +export const ipfsUploadFile = async (file: File) => { + try { + const ipfsClient = createIpfsClient(); + const upload = await ipfsClient.upload.file(file); + return upload; + } catch (error) { + console.error("Failed to upload file to IPFS:", error); + throw error; + } +};