Spaces:
Sleeping
Sleeping
add image upload functionality to Cloudinary and remove temporary URL management
Browse files- .gitignore +22 -0
- src/App.jsx +35 -39
.gitignore
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# dependencies
|
| 2 |
+
/node_modules
|
| 3 |
+
/.pnp
|
| 4 |
+
.pnp.js
|
| 5 |
+
|
| 6 |
+
# testing
|
| 7 |
+
/coverage
|
| 8 |
+
|
| 9 |
+
# production
|
| 10 |
+
/build
|
| 11 |
+
|
| 12 |
+
# misc
|
| 13 |
+
.DS_Store
|
| 14 |
+
.env
|
| 15 |
+
.env.local
|
| 16 |
+
.env.development.local
|
| 17 |
+
.env.test.local
|
| 18 |
+
.env.production.local
|
| 19 |
+
|
| 20 |
+
npm-debug.log*
|
| 21 |
+
yarn-debug.log*
|
| 22 |
+
yarn-error.log*
|
src/App.jsx
CHANGED
|
@@ -25,6 +25,14 @@ import axios from 'axios';
|
|
| 25 |
import JSZip from 'jszip';
|
| 26 |
import { saveAs } from 'file-saver';
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
// Function to get file extension
|
| 29 |
const getFileExtension = filename => {
|
| 30 |
return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);
|
|
@@ -35,6 +43,29 @@ const getFilenameWithoutExtension = filename => {
|
|
| 35 |
return filename.substring(0, filename.lastIndexOf("."));
|
| 36 |
}
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
const App = () => {
|
| 39 |
const [apiKey, setApiKey] = useState('');
|
| 40 |
const [uploadedImages, setUploadedImages] = useState([]);
|
|
@@ -43,7 +74,6 @@ const App = () => {
|
|
| 43 |
const [progress, setProgress] = useState(0);
|
| 44 |
const fileInputRef = useRef(null);
|
| 45 |
const toast = useToast();
|
| 46 |
-
const temporaryUrlsRef = useRef([]);
|
| 47 |
|
| 48 |
const handleFileChange = async (e) => {
|
| 49 |
const files = Array.from(e.target.files);
|
|
@@ -90,10 +120,6 @@ const App = () => {
|
|
| 90 |
setProcessing(true);
|
| 91 |
const pendingImages = uploadedImages.filter(img => img.status === 'pending');
|
| 92 |
|
| 93 |
-
// Clean up any previous temporary URLs
|
| 94 |
-
temporaryUrlsRef.current.forEach(url => URL.revokeObjectURL(url));
|
| 95 |
-
temporaryUrlsRef.current = [];
|
| 96 |
-
|
| 97 |
for (let i = 0; i < pendingImages.length; i++) {
|
| 98 |
const image = pendingImages[i];
|
| 99 |
|
|
@@ -105,13 +131,12 @@ const App = () => {
|
|
| 105 |
));
|
| 106 |
|
| 107 |
try {
|
| 108 |
-
//
|
| 109 |
-
const
|
| 110 |
-
temporaryUrlsRef.current.push(imageUrl);
|
| 111 |
|
| 112 |
-
// Call the GLIF API with the
|
| 113 |
const response = await axios.post('https://simple-api.glif.app/cm7yya7850000la0ckalxpix2', {
|
| 114 |
-
image:
|
| 115 |
}, {
|
| 116 |
headers: {
|
| 117 |
'Authorization': `Bearer ${apiKey}`,
|
|
@@ -142,10 +167,6 @@ const App = () => {
|
|
| 142 |
}
|
| 143 |
}
|
| 144 |
|
| 145 |
-
// Clean up temporary URLs after processing is done
|
| 146 |
-
temporaryUrlsRef.current.forEach(url => URL.revokeObjectURL(url));
|
| 147 |
-
temporaryUrlsRef.current = [];
|
| 148 |
-
|
| 149 |
setProgress(100);
|
| 150 |
setProcessing(false);
|
| 151 |
|
|
@@ -212,39 +233,14 @@ const App = () => {
|
|
| 212 |
};
|
| 213 |
|
| 214 |
const removeImage = (id) => {
|
| 215 |
-
// Find the image to remove
|
| 216 |
-
const imageToRemove = uploadedImages.find(img => img.id === id);
|
| 217 |
-
if (imageToRemove && imageToRemove.preview) {
|
| 218 |
-
// Revoke the object URL to prevent memory leaks
|
| 219 |
-
URL.revokeObjectURL(imageToRemove.preview);
|
| 220 |
-
}
|
| 221 |
-
|
| 222 |
setUploadedImages(prev => prev.filter(img => img.id !== id));
|
| 223 |
};
|
| 224 |
|
| 225 |
const clearAll = () => {
|
| 226 |
-
// Revoke all preview URLs to prevent memory leaks
|
| 227 |
-
uploadedImages.forEach(img => {
|
| 228 |
-
if (img.preview) URL.revokeObjectURL(img.preview);
|
| 229 |
-
});
|
| 230 |
-
|
| 231 |
setUploadedImages([]);
|
| 232 |
if (fileInputRef.current) fileInputRef.current.value = '';
|
| 233 |
};
|
| 234 |
|
| 235 |
-
// Clean up URLs when component unmounts
|
| 236 |
-
React.useEffect(() => {
|
| 237 |
-
return () => {
|
| 238 |
-
// Clean up all preview URLs
|
| 239 |
-
uploadedImages.forEach(img => {
|
| 240 |
-
if (img.preview) URL.revokeObjectURL(img.preview);
|
| 241 |
-
});
|
| 242 |
-
|
| 243 |
-
// Clean up temporary URLs
|
| 244 |
-
temporaryUrlsRef.current.forEach(url => URL.revokeObjectURL(url));
|
| 245 |
-
};
|
| 246 |
-
}, []);
|
| 247 |
-
|
| 248 |
const getStatusBadge = (status) => {
|
| 249 |
switch (status) {
|
| 250 |
case 'pending':
|
|
|
|
| 25 |
import JSZip from 'jszip';
|
| 26 |
import { saveAs } from 'file-saver';
|
| 27 |
|
| 28 |
+
// Function to convert file to base64
|
| 29 |
+
const toBase64 = file => new Promise((resolve, reject) => {
|
| 30 |
+
const reader = new FileReader();
|
| 31 |
+
reader.readAsDataURL(file);
|
| 32 |
+
reader.onload = () => resolve(reader.result.split(',')[1]);
|
| 33 |
+
reader.onerror = error => reject(error);
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
// Function to get file extension
|
| 37 |
const getFileExtension = filename => {
|
| 38 |
return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);
|
|
|
|
| 43 |
return filename.substring(0, filename.lastIndexOf("."));
|
| 44 |
}
|
| 45 |
|
| 46 |
+
// Function to upload image to Cloudinary
|
| 47 |
+
const uploadToCloudinary = async (file) => {
|
| 48 |
+
const formData = new FormData();
|
| 49 |
+
formData.append('file', file);
|
| 50 |
+
formData.append('upload_preset', process.env.REACT_APP_CLOUDINARY_UPLOAD_PRESET);
|
| 51 |
+
|
| 52 |
+
try {
|
| 53 |
+
const response = await axios.post(
|
| 54 |
+
process.env.REACT_APP_CLOUDINARY_UPLOAD_URL,
|
| 55 |
+
formData
|
| 56 |
+
);
|
| 57 |
+
|
| 58 |
+
if (response.status === 200) {
|
| 59 |
+
return response.data.secure_url;
|
| 60 |
+
} else {
|
| 61 |
+
throw new Error('Failed to upload to Cloudinary');
|
| 62 |
+
}
|
| 63 |
+
} catch (error) {
|
| 64 |
+
console.error('Error uploading to Cloudinary:', error);
|
| 65 |
+
throw error;
|
| 66 |
+
}
|
| 67 |
+
};
|
| 68 |
+
|
| 69 |
const App = () => {
|
| 70 |
const [apiKey, setApiKey] = useState('');
|
| 71 |
const [uploadedImages, setUploadedImages] = useState([]);
|
|
|
|
| 74 |
const [progress, setProgress] = useState(0);
|
| 75 |
const fileInputRef = useRef(null);
|
| 76 |
const toast = useToast();
|
|
|
|
| 77 |
|
| 78 |
const handleFileChange = async (e) => {
|
| 79 |
const files = Array.from(e.target.files);
|
|
|
|
| 120 |
setProcessing(true);
|
| 121 |
const pendingImages = uploadedImages.filter(img => img.status === 'pending');
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
for (let i = 0; i < pendingImages.length; i++) {
|
| 124 |
const image = pendingImages[i];
|
| 125 |
|
|
|
|
| 131 |
));
|
| 132 |
|
| 133 |
try {
|
| 134 |
+
// First upload image to Cloudinary
|
| 135 |
+
const cloudinaryUrl = await uploadToCloudinary(image.file);
|
|
|
|
| 136 |
|
| 137 |
+
// Call the GLIF API with the Cloudinary URL
|
| 138 |
const response = await axios.post('https://simple-api.glif.app/cm7yya7850000la0ckalxpix2', {
|
| 139 |
+
image: cloudinaryUrl
|
| 140 |
}, {
|
| 141 |
headers: {
|
| 142 |
'Authorization': `Bearer ${apiKey}`,
|
|
|
|
| 167 |
}
|
| 168 |
}
|
| 169 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
setProgress(100);
|
| 171 |
setProcessing(false);
|
| 172 |
|
|
|
|
| 233 |
};
|
| 234 |
|
| 235 |
const removeImage = (id) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
setUploadedImages(prev => prev.filter(img => img.id !== id));
|
| 237 |
};
|
| 238 |
|
| 239 |
const clearAll = () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
setUploadedImages([]);
|
| 241 |
if (fileInputRef.current) fileInputRef.current.value = '';
|
| 242 |
};
|
| 243 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
const getStatusBadge = (status) => {
|
| 245 |
switch (status) {
|
| 246 |
case 'pending':
|