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':
|