Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merging Main with prod #140

Merged
merged 2 commits into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading