Spaces:
Running
Running
File size: 5,996 Bytes
1d7f851 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | import { useState } from 'react';
import toast from 'react-hot-toast';
export default function Downloader() {
const [url, setUrl] = useState('');
const [loading, setLoading] = useState(false);
const [progress, setProgress] = useState('');
const [downloadLink, setDownloadLink] = useState(null);
const isValidUrl = (string) => {
try {
new URL(string);
return string.includes('deviantart.com');
} catch (_) {
return false;
}
};
const handleProcess = async (e) => {
e.preventDefault();
if (!url.trim()) {
toast.error('Please enter a DeviantArt URL');
return;
}
if (!isValidUrl(url)) {
toast.error('Please enter a valid DeviantArt URL');
return;
}
setLoading(true);
setProgress('Initializing...');
setDownloadLink(null);
try {
const response = await fetch('/api/process', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ url }),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to process request');
}
// Simulate progress updates if the backend is fast
setProgress('Downloading images...');
await new Promise(r => setTimeout(r, 800));
setProgress('Creating ZIP archive...');
await new Promise(r => setTimeout(r, 800));
setProgress('Uploading to MediaFire...');
await new Promise(r => setTimeout(r, 800));
const data = await response.json();
if (data.success) {
setDownloadLink(data.downloadUrl);
setProgress('Complete!');
toast.success('ZIP file generated successfully!');
} else {
throw new Error(data.error || 'Unknown error occurred');
}
} catch (error) {
console.error(error);
toast.error(error.message || 'Something went wrong');
setProgress('Error occurred');
} finally {
setLoading(false);
}
};
return (
<div className="bg-gray-800/50 border border-gray-700 rounded-2xl p-6 md:p-8 shadow-xl">
<form onSubmit={handleProcess} className="space-y-6">
<div>
<label htmlFor="url" className="block text-sm font-medium text-gray-300 mb-2">
DeviantArt Artwork URL
</label>
<div className="relative">
<input
type="url"
id="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://www.deviantart.com/username/art/artwork-title"
disabled={loading}
className="w-full bg-gray-900 border border-gray-600 rounded-lg px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent transition-all disabled:opacity-50 disabled:cursor-not-allowed"
/>
{url && (
<button
type="button"
onClick={() => setUrl('')}
disabled={loading}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-white transition-colors"
>
✕
</button>
)}
</div>
<p className="mt-2 text-xs text-gray-500">
Paste the direct link to the DeviantArt artwork page.
</p>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-gradient-to-r from-brand-600 to-brand-500 hover:from-brand-500 hover:to-brand-400 text-white font-semibold py-3 px-6 rounded-lg shadow-lg shadow-brand-500/20 transform hover:-translate-y-0.5 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
>
{loading ? 'Processing...' : 'Download & Zip'}
</button>
</form>
{loading && (
<div className="mt-8 space-y-4">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Status</span>
<span className="text-brand-400 font-medium animate-pulse">{progress}</span>
</div>
<div className="h-2 bg-gray-700 rounded-full overflow-hidden">
<div className="h-full bg-gradient-to-r from-brand-600 to-purple-500 w-2/3 animate-[shimmer_1.5s_infinite]"></div>
</div>
</div>
)}
{downloadLink && !loading && (
<div className="mt-8 p-4 bg-green-900/20 border border-green-700/50 rounded-lg">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-green-500/20 flex items-center justify-center mt-0.5">
<svg className="w-4 h-4 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
<div className="flex-grow">
<h4 className="text-green-400 font-medium mb-1">Ready for Download</h4>
<p className="text-sm text-gray-400 mb-3">
Your image has been packaged and is ready to be downloaded.
</p>
<a
href={downloadLink}
download="deviantart-image.zip"
className="inline-flex items-center gap-2 bg-green-600 hover:bg-green-500 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
Download ZIP
</a>
</div>
</div>
</div>
)}
</div>
);
} |