From 3bc3ff5ba7a98e6af7dc85d6f13108f17cf6b573 Mon Sep 17 00:00:00 2001 From: rishi Date: Sun, 1 Dec 2024 17:26:06 +0000 Subject: [PATCH] Images Gallery --- client/src/App.jsx | 11 +- client/src/components/Dashboard.jsx | 1 + client/src/components/ImagesGallery.jsx | 177 ++++++++++++++++++++++++ client/src/styles/main.scss | 171 +++++++++++++++++++++++ 4 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 client/src/components/ImagesGallery.jsx diff --git a/client/src/App.jsx b/client/src/App.jsx index ed66208..60f02f6 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,9 +1,10 @@ -// src/App.jsx +// Update src/App.jsx import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { AuthProvider, useAuth } from './contexts/AuthContext'; import Login from './components/Login'; import Dashboard from './components/Dashboard'; import Editor from './components/Editor'; +import ImagesGallery from './components/ImagesGallery'; function PrivateRoute({ children }) { const { isAuthenticated } = useAuth(); @@ -25,6 +26,14 @@ function App() { } /> + + + + } + /> My Blog Posts
New Post + Images
diff --git a/client/src/components/ImagesGallery.jsx b/client/src/components/ImagesGallery.jsx new file mode 100644 index 0000000..04b15e5 --- /dev/null +++ b/client/src/components/ImagesGallery.jsx @@ -0,0 +1,177 @@ +// src/components/ImagesGallery.jsx +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import { useNavigate } from 'react-router-dom'; +import { useAuth } from '../contexts/AuthContext'; + +export default function ImagesGallery() { + const [images, setImages] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [uploading, setUploading] = useState(false); + const [selectedImage, setSelectedImage] = useState(null); + const { logout } = useAuth(); + const navigate = useNavigate(); + + useEffect(() => { + loadImages(); + }, []); + + const loadImages = async () => { + try { + const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/images`, { + headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } + }); + setImages(response.data); + setError(null); + } catch (err) { + console.error('Error loading images:', err); + setError('Failed to load images'); + if (err.response?.status === 401) { + logout(); + navigate('/login'); + } + } finally { + setLoading(false); + } + }; + + const handleFileChange = async (event) => { + const file = event.target.files[0]; + if (!file) return; + + setUploading(true); + const formData = new FormData(); + formData.append('file', file); + + try { + const response = await axios.post( + `${import.meta.env.VITE_API_URL}/api/images/upload`, + formData, + { + headers: { + Authorization: `Bearer ${localStorage.getItem('token')}`, + 'Content-Type': 'multipart/form-data' + } + } + ); + setImages(prev => [response.data, ...prev]); + setError(null); + } catch (err) { + console.error('Error uploading image:', err); + setError('Failed to upload image'); + } finally { + setUploading(false); + } + }; + + const handleImageClick = (image) => { + setSelectedImage(image); + }; + + const copyUrlToClipboard = async (url) => { + try { + await navigator.clipboard.writeText(`${import.meta.env.VITE_API_URL}${url}`); + alert('URL copied to clipboard!'); + } catch (err) { + console.error('Failed to copy URL:', err); + alert('Failed to copy URL'); + } + }; + + if (loading) return
Loading...
; + + return ( +
+
+

Image Gallery

+
+ + +
+
+ + {error &&
{error}
} + +
+ {images.map(image => ( +
handleImageClick(image)} + > + {image.filename} +
+ {image.filename} + +
+
+ ))} +
+ + {selectedImage && ( +
setSelectedImage(null)} + > +
e.stopPropagation()} + > + {selectedImage.filename} +
+

{selectedImage.filename}

+
+ + +
+ +
+
+
+ )} +
+ ); +} \ No newline at end of file diff --git a/client/src/styles/main.scss b/client/src/styles/main.scss index 53f09e1..e2af3a0 100644 --- a/client/src/styles/main.scss +++ b/client/src/styles/main.scss @@ -381,4 +381,175 @@ body { // Override some MD Editor default styles .w-md-editor { --md-editor-box-shadow: none !important; +} + + +.images-gallery { + padding: 20px; + max-width: 1200px; + margin: 0 auto; + + .gallery-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + + h1 { + margin: 0; + color: $primary-color; + } + + .actions { + display: flex; + gap: 10px; + + .button { + width: auto; + + &.upload { + background-color: $secondary-color; + cursor: pointer; + + &:hover { + background-color: darken($secondary-color, 10%); + } + } + } + } + } + + .images-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 20px; + margin-top: 20px; + } + + .image-card { + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + cursor: pointer; + transition: transform 0.2s; + + &:hover { + transform: translateY(-2px); + } + + img { + width: 100%; + height: 200px; + object-fit: cover; + } + + .image-info { + padding: 10px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + + .filename { + font-size: 0.875rem; + color: $text-color; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .copy-url { + padding: 4px 8px; + font-size: 0.875rem; + background: $secondary-color; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + + &:hover { + background: darken($secondary-color, 10%); + } + } + } + } + + .image-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + padding: 20px; + + .modal-content { + background: white; + border-radius: 8px; + overflow: hidden; + max-width: 90vw; + max-height: 90vh; + + img { + max-width: 100%; + max-height: 70vh; + object-fit: contain; + } + + .modal-info { + padding: 20px; + + h3 { + margin: 0 0 10px; + color: $primary-color; + } + + .url-container { + display: flex; + gap: 10px; + margin-bottom: 20px; + + input { + flex: 1; + padding: 8px; + border: 1px solid $border-color; + border-radius: 4px; + font-size: 14px; + } + + button { + padding: 8px 16px; + background: $secondary-color; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + + &:hover { + background: darken($secondary-color, 10%); + } + } + } + + .close { + width: 100%; + padding: 8px; + background: #95a5a6; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + + &:hover { + background: darken(#95a5a6, 10%); + } + } + } + } + } } \ No newline at end of file