Skip to content

Commit

Permalink
Merge pull request #139 from NetSepio/walrus-file-storage
Browse files Browse the repository at this point in the history
fix : walrus file storage
  • Loading branch information
Rushikeshnimkar committed Sep 15, 2024
2 parents fe597c2 + 2586425 commit 01caae1
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 51 deletions.
147 changes: 96 additions & 51 deletions components/walrus/FileStorage.tsx
Original file line number Diff line number Diff line change
@@ -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<FileInfo[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const [shareUrl, setShareUrl] = useState<string | null>(null);
const [shareLink, setShareLink] = useState<string | null>(null);
const [copied, setCopied] = useState(false);

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
setLoading(true);
setError(null);
Expand All @@ -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 {
Expand All @@ -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 (
<div className="bg-[#202333] border border-[#0162FF] rounded-3xl p-6 w-full h-[400px] flex flex-col">
<h2 className="text-2xl font-semibold text-white mb-4">Walrus File Storage</h2>
Expand All @@ -101,15 +130,28 @@ const FileStorage: React.FC = () => {
/>
</div>

{loading && <p className="text-white mb-4">Uploading to Walrus network...</p>}
{loading && <p className="text-white mb-4">Uploading...</p>}

<div className="flex-grow overflow-hidden">
{files.length > 0 && (
<div className="bg-[#2A2D3E] rounded-xl p-4 h-full overflow-y-auto">
<h3 className="text-white font-semibold mb-2">Files on Walrus Network</h3>
{files.map((fileInfo, index) => (
<div key={index} className="flex items-center justify-between text-white py-2 border-b border-gray-700 last:border-b-0">
<span className="truncate flex-grow">{fileInfo.name}</span>
<div className="flex items-center flex-grow">
{fileInfo.isImage ? (
<object
type={fileInfo.mediaType}
data={fileInfo.blobUrl}
className="w-10 h-10 object-cover rounded mr-2"
/>
) : (
<div className="w-10 h-10 bg-gray-600 flex items-center justify-center rounded mr-2">
<span>{fileInfo.fileName.slice(-3).toUpperCase()}</span>
</div>
)}
<span className="truncate">{fileInfo.fileName}</span>
</div>
<div className="flex space-x-2">
<button onClick={() => handleDownload(fileInfo)} className="p-1 hover:bg-[#0162FF] rounded">
<FiDownload />
Expand All @@ -124,34 +166,37 @@ const FileStorage: React.FC = () => {
)}
</div>

{error && <p className="text-red-500 mt-2">{error}</p>}

{shareUrl && (
<div className="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div className="bg-[#2A2D3E] rounded-xl p-6 max-w-md w-full">
{shareLink && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-[#2A2D3E] rounded-xl p-6 w-full max-w-md">
<div className="flex justify-between items-center mb-4">
<h3 className="text-white font-semibold">Share Walrus File</h3>
<button onClick={() => setShareUrl(null)} className="text-gray-400 hover:text-white">
<h3 className="text-white font-semibold">Share Link</h3>
<button onClick={closeShareModal} className="text-gray-400 hover:text-white">
<FiX />
</button>
</div>
<p className="text-gray-400 mb-2">Your file is available on the Walrus network:</p>
<div className="flex items-center bg-[#202333] rounded p-2">
<div className="bg-[#202333] rounded-lg p-3 flex items-center mb-4">
<input
type="text"
value={shareUrl}
value={shareLink}
readOnly
className="bg-transparent text-white flex-grow mr-2 outline-none"
/>
<button onClick={handleCopyUrl} className="text-[#0162FF] hover:text-white">
<FiCopy />
<button
onClick={handleCopy}
className="text-[#0162FF] hover:text-white transition-colors"
>
{copied ? <FiCheck /> : <FiCopy />}
</button>
</div>
<p className="text-gray-400 text-sm">
{copied ? "Copied to clipboard!" : "Click the copy icon to copy the link"}
</p>
</div>
</div>
)}

{error && <p className="text-red-500 mt-2">{error}</p>}
</div>
);
};

export default FileStorage;
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
140 changes: 140 additions & 0 deletions pages/filedownload.tsx
Original file line number Diff line number Diff line change
@@ -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<FileInfo | null>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);

const handleLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setLink(e.target.value);
};

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
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 (
<div className="bg-gray-900 min-h-screen flex items-center justify-center p-4">
<div className="bg-gray-800 rounded-lg shadow-2xl w-full max-w-md p-8">
<h2 className="text-3xl font-bold text-gray-100 mb-6 flex items-center justify-center">
<FiDownload className="mr-3 text-blue-400" />
Walrus File Downloader
</h2>
<form onSubmit={handleSubmit} className="mb-6">
<div className="relative">
<FiLink className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
<input
type="text"
value={link}
onChange={handleLinkChange}
placeholder="Paste your share link here"
className="w-full pl-10 pr-4 py-3 bg-gray-700 border border-gray-600 rounded-lg text-gray-100 placeholder-gray-400 focus:outline-none focus:border-blue-500 transition duration-200"
/>
</div>
<button
type="submit"
disabled={isLoading}
className={`w-full mt-4 bg-blue-500 text-white p-3 rounded-lg hover:bg-blue-600 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${isLoading ? 'opacity-50 cursor-not-allowed' : ''}`}
>
{isLoading ? 'Processing...' : 'Get File'}
</button>
</form>

{error && (
<div className="bg-red-900 bg-opacity-50 border-l-4 border-red-500 text-red-100 p-4 mb-6 rounded-r-lg flex items-center">
<FiAlertCircle className="mr-3 flex-shrink-0" />
<p>{error}</p>
</div>
)}

{fileInfo && (
<div className="bg-gray-700 rounded-lg p-6 animate-fade-in">
<div className="flex items-center mb-4">
<div className="bg-blue-500 p-3 rounded-full mr-4">
<FiFile className="text-white w-6 h-6" />
</div>
<div>
<h3 className="text-lg font-semibold text-gray-100">{fileInfo.fileName}</h3>
<p className="text-gray-400 text-sm">{fileInfo.mediaType}</p>
</div>
</div>
<div className="mt-4 pt-4 border-t border-gray-600">
<button
onClick={handleDownload}
disabled={isLoading}
className={`w-full bg-green-500 text-white p-3 rounded-lg hover:bg-green-600 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 flex items-center justify-center ${isLoading ? 'opacity-50 cursor-not-allowed' : ''}`}
>
<FiDownload className="mr-2" />
{isLoading ? 'Downloading...' : 'Download File'}
</button>
</div>
</div>
)}

{fileInfo && (
<div className="mt-6 text-center">
<a href={fileInfo.suiUrl} target="_blank" rel="noopener noreferrer" className="text-blue-400 hover:text-blue-300 transition-colors">
View on Sui Explorer
</a>
</div>
)}
</div>
</div>
);
}
Loading

0 comments on commit 01caae1

Please sign in to comment.