From 2586425bc40ddb25ef9eb285d992c6cb5241bf4e Mon Sep 17 00:00:00 2001 From: Rushikesh Nimkar Date: Sun, 15 Sep 2024 20:42:54 +0530 Subject: [PATCH] fix : walrus file storage --- components/walrus/FileStorage.tsx | 147 +++++++++++++++++++----------- package.json | 1 + pages/filedownload.tsx | 140 ++++++++++++++++++++++++++++ pnpm-lock.yaml | 9 ++ 4 files changed, 246 insertions(+), 51 deletions(-) create mode 100644 pages/filedownload.tsx diff --git a/components/walrus/FileStorage.tsx b/components/walrus/FileStorage.tsx index 0937d44..cd7d4cf 100644 --- a/components/walrus/FileStorage.tsx +++ b/components/walrus/FileStorage.tsx @@ -1,23 +1,31 @@ -import React, { useState, useRef } from 'react'; -import axios from 'axios'; -import { FiUploadCloud, FiDownload, FiShare2, FiCopy, FiX } from 'react-icons/fi'; +'use client' -const PUBLISHER = 'https://publisher-devnet.walrus.space'; -const AGGREGATOR = 'https://aggregator-devnet.walrus.space'; +import React, { useState, useRef, FormEvent, ChangeEvent } from 'react'; +import { FiUploadCloud, FiDownload, FiShare2, FiX, FiCopy, FiCheck } from 'react-icons/fi'; +import { v4 as uuidv4 } from 'uuid'; + +const PUBLISHER_URL = 'https://publisher-devnet.walrus.space'; +const AGGREGATOR_URL = 'https://aggregator-devnet.walrus.space'; +const EPOCHS = '5'; interface FileInfo { - name: string; + fileName: string; blobId: string; + mediaType: string; + blobUrl: string; + suiUrl: string; + isImage: boolean; } -const FileStorage: React.FC = () => { +export default function FileStorage() { const [files, setFiles] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fileInputRef = useRef(null); - const [shareUrl, setShareUrl] = useState(null); + const [shareLink, setShareLink] = useState(null); + const [copied, setCopied] = useState(false); - const handleFileChange = (e: React.ChangeEvent) => { + const handleFileChange = (e: ChangeEvent) => { if (e.target.files) { setLoading(true); setError(null); @@ -27,22 +35,39 @@ const FileStorage: React.FC = () => { const uploadFile = async (file: File) => { try { + setLoading(true); const formData = new FormData(); formData.append('file', file); - const response = await axios.put(`${PUBLISHER}/v1/store`, formData, { - headers: { 'Content-Type': 'multipart/form-data' }, + const response = await fetch(`${PUBLISHER_URL}/v1/store?epochs=${EPOCHS}`, { + method: "PUT", + body: file, }); - let blobId = ''; - if (response.data.newlyCreated) { - blobId = response.data.newlyCreated.blobObject.blobId; - } else if (response.data.alreadyCertified) { - blobId = response.data.alreadyCertified.blobId; + if (response.status === 200) { + const info = await response.json(); + let blobId = ''; + let suiUrl = ''; + + if (info.alreadyCertified) { + blobId = info.alreadyCertified.blobId; + suiUrl = `https://suiscan.xyz/testnet/tx/${info.alreadyCertified.event.txDigest}`; + } else if (info.newlyCreated) { + blobId = info.newlyCreated.blobObject.blobId; + suiUrl = `https://suiscan.xyz/testnet/object/${info.newlyCreated.blobObject.id}`; + } + + const blobUrl = `${AGGREGATOR_URL}/v1/${blobId}`; + const isImage = file.type.startsWith('image/'); + + setFiles((prev) => [ + ...prev, + { fileName: file.name, blobId, mediaType: file.type, blobUrl, suiUrl, isImage }, + ]); + } else { + throw new Error("Failed to upload file"); } - - setFiles(prev => [...prev, { name: file.name, blobId }]); - } catch (err) { + } catch (err: any) { setError(`Failed to upload ${file.name}. Please try again.`); console.error(err); } finally { @@ -52,35 +77,39 @@ const FileStorage: React.FC = () => { const handleDownload = async (fileInfo: FileInfo) => { try { - const response = await axios.get(`${AGGREGATOR}/v1/${fileInfo.blobId}`, { - responseType: 'blob', - }); - - const url = window.URL.createObjectURL(new Blob([response.data])); + const response = await fetch(fileInfo.blobUrl); + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; - link.setAttribute('download', fileInfo.name); + link.setAttribute('download', fileInfo.fileName); document.body.appendChild(link); link.click(); link.remove(); - } catch (err) { - setError(`Failed to download ${fileInfo.name}. Please try again.`); + } catch (err: any) { + setError(`Failed to download ${fileInfo.fileName}. Please try again.`); console.error(err); } }; - const handleShare = (fileInfo: FileInfo) => { - const url = `${AGGREGATOR}/v1/${fileInfo.blobId}`; - setShareUrl(url); + const handleShare = (fileInfo: FileInfo) => { + const encodedFileInfo = btoa(JSON.stringify(fileInfo)); + const link = `${window.location.origin}/filedownload?file=${encodedFileInfo}`; + setShareLink(link); }; - const handleCopyUrl = () => { - if (shareUrl) { - navigator.clipboard.writeText(shareUrl); - + const handleCopy = () => { + if (shareLink) { + navigator.clipboard.writeText(shareLink); + setCopied(true); + setTimeout(() => setCopied(false), 2000); } }; + const closeShareModal = () => { + setShareLink(null); + setCopied(false); + }; return (

Walrus File Storage

@@ -101,7 +130,7 @@ const FileStorage: React.FC = () => { />
- {loading &&

Uploading to Walrus network...

} + {loading &&

Uploading...

}
{files.length > 0 && ( @@ -109,7 +138,20 @@ const FileStorage: React.FC = () => {

Files on Walrus Network

{files.map((fileInfo, index) => (
- {fileInfo.name} +
+ {fileInfo.isImage ? ( + + ) : ( +
+ {fileInfo.fileName.slice(-3).toUpperCase()} +
+ )} + {fileInfo.fileName} +
- {error &&

{error}

} - - {shareUrl && ( -
-
+ {shareLink && ( +
+
-

Share Walrus File

-
-

Your file is available on the Walrus network:

-
+
-
+

+ {copied ? "Copied to clipboard!" : "Click the copy icon to copy the link"} +

)} + + {error &&

{error}

}
); -}; - -export default FileStorage; \ No newline at end of file +} \ No newline at end of file diff --git a/package.json b/package.json index bdd0beb..be5e8e0 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "stripe": "^14.14.0", "styled-components": "^6.1.12", "thirdweb": "^5.15.0", + "uuid": "^10.0.0", "viem": "^2.9.31", "wagmi": "^2.8.1" }, diff --git a/pages/filedownload.tsx b/pages/filedownload.tsx new file mode 100644 index 0000000..0df7698 --- /dev/null +++ b/pages/filedownload.tsx @@ -0,0 +1,140 @@ +'use client' + +import React, { useState } from 'react'; +import { FiDownload, FiFile, FiLink, FiCheckCircle, FiAlertCircle } from 'react-icons/fi'; + +interface FileInfo { + fileName: string; + blobId: string; + mediaType: string; + blobUrl: string; + suiUrl: string; + isImage: boolean; +} + +export default function FileDownloader() { + const [link, setLink] = useState(''); + const [fileInfo, setFileInfo] = useState(null); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const handleLinkChange = (e: React.ChangeEvent) => { + setLink(e.target.value); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + setIsLoading(true); + + try { + const url = new URL(link); + const encodedFileInfo = url.searchParams.get('file'); + + if (!encodedFileInfo) { + throw new Error('Invalid share link'); + } + + // Simulate a delay to show loading state + await new Promise(resolve => setTimeout(resolve, 1000)); + + const decodedFileInfo = JSON.parse(atob(encodedFileInfo)); + setFileInfo(decodedFileInfo); + } catch (err: any) { + setError(err.message || 'An error occurred while processing the link'); + } finally { + setIsLoading(false); + } + }; + + const handleDownload = async () => { + if (!fileInfo) return; + + try { + setIsLoading(true); + const response = await fetch(fileInfo.blobUrl); + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', fileInfo.fileName); + document.body.appendChild(link); + link.click(); + link.remove(); + } catch (err: any) { + setError(`Failed to download ${fileInfo.fileName}. Please try again.`); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+

+ + Walrus File Downloader +

+
+
+ + +
+ +
+ + {error && ( +
+ +

{error}

+
+ )} + + {fileInfo && ( +
+
+
+ +
+
+

{fileInfo.fileName}

+

{fileInfo.mediaType}

+
+
+
+ +
+
+ )} + + {fileInfo && ( + + )} +
+
+ ); +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5552753..6313f57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -218,6 +218,9 @@ importers: thirdweb: specifier: ^5.15.0 version: 5.15.0(@react-native-async-storage/async-storage@1.23.1(react-native@0.74.1(@babel/core@7.24.5)(@babel/preset-env@7.24.5(@babel/core@7.24.5))(@types/react@18.2.47)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.2.0)(utf-8-validate@5.0.10)))(@types/react@18.2.47)(bufferutil@4.0.8)(encoding@0.1.13)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) + uuid: + specifier: ^10.0.0 + version: 10.0.0 viem: specifier: ^2.9.31 version: 2.9.31(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@5.0.10)(zod@3.22.4) @@ -10673,6 +10676,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + uuid@7.0.3: resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} hasBin: true @@ -26090,6 +26097,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@10.0.0: {} + uuid@7.0.3: {} uuid@8.3.2: {}