From 5ccf28a6e8aa40db6475697bb0c2723f9323e029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20K=C5=82osi=C5=84ski?= Date: Tue, 7 Jan 2025 16:13:16 +0100 Subject: [PATCH] feature: Add pictures to post MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new library to dropzone, - Enhance the modal and add possibility to send pictures. Refs: 8697bp6d2 Signed-off-by: Patryk Kłosiński --- frontend/package-lock.json | 31 +++++ frontend/package.json | 1 + .../src/Features/CreatePost/CreatePost.tsx | 94 ++++++++++----- .../components/PostForm/PostForm.tsx | 110 ++++++++++++++++++ .../CreatePost/components/PostForm/index.tsx | 1 + frontend/src/main.tsx | 9 +- 6 files changed, 210 insertions(+), 36 deletions(-) create mode 100644 frontend/src/Features/CreatePost/components/PostForm/PostForm.tsx create mode 100644 frontend/src/Features/CreatePost/components/PostForm/index.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 64951a3..cd714bd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "@mantine/carousel": "^7.15.2", "@mantine/core": "^7.15.2", "@mantine/dates": "^7.15.2", + "@mantine/dropzone": "^7.15.2", "@mantine/form": "^7.15.2", "@mantine/hooks": "^7.15.2", "@mantine/modals": "^7.15.2", @@ -2065,6 +2066,21 @@ "react-dom": "^18.x || ^19.x" } }, + "node_modules/@mantine/dropzone": { + "version": "7.15.2", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.15.2.tgz", + "integrity": "sha512-6jT1tFfUEhjWeZywohC8OMJnii0omOnqlqqGMQc0MZBpdMQmmTGJ3Z6oLkCadVH97FrROm8rtUOnMSA9CqiAGw==", + "license": "MIT", + "dependencies": { + "react-dropzone-esm": "15.2.0" + }, + "peerDependencies": { + "@mantine/core": "7.15.2", + "@mantine/hooks": "7.15.2", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, "node_modules/@mantine/form": { "version": "7.15.2", "resolved": "https://registry.npmjs.org/@mantine/form/-/form-7.15.2.tgz", @@ -8709,6 +8725,21 @@ "react": "^18.3.1" } }, + "node_modules/react-dropzone-esm": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/react-dropzone-esm/-/react-dropzone-esm-15.2.0.tgz", + "integrity": "sha512-pPwR8xWVL+tFLnbAb8KVH5f6Vtl397tck8dINkZ1cPMxHWH+l9dFmIgRWgbh7V7jbjIcuKXCsVrXbhQz68+dVA==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-i18next": { "version": "15.0.3", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3191fe4..845deba 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,7 @@ "@mantine/carousel": "^7.15.2", "@mantine/core": "^7.15.2", "@mantine/dates": "^7.15.2", + "@mantine/dropzone": "^7.15.2", "@mantine/form": "^7.15.2", "@mantine/hooks": "^7.15.2", "@mantine/modals": "^7.15.2", diff --git a/frontend/src/Features/CreatePost/CreatePost.tsx b/frontend/src/Features/CreatePost/CreatePost.tsx index a63456e..343700c 100644 --- a/frontend/src/Features/CreatePost/CreatePost.tsx +++ b/frontend/src/Features/CreatePost/CreatePost.tsx @@ -1,60 +1,90 @@ import {IconPencil} from "@tabler/icons-react"; +import {Button} from "@mantine/core"; +import {ModificationModal} from "../shared/components/ModificationModal"; +import {PostForm} from "./components/PostForm"; import {useRef, useState} from "react"; -import api from "../shared/services/api.ts"; import {useAlert} from "../../Providers/AlertProvider.tsx"; -import {ModificationModal} from "../shared/components/ModificationModal"; -import {Button} from "@mantine/core"; -import {ModalRichContent} from "../shared/consts"; +import api from "../shared/services/api.ts"; export const CreatePost = () => { - const [content, setContent] = useState(''); + const [content, setContent] = useState(""); const contentRef = useRef(content); + const [images, setImages] = useState([]); + const imagesRef = useRef(images); const alert = useAlert(); const handleContentChange = (html: string) => { setContent(html); contentRef.current = html; - } + }; + + const handleImagesChange = (files: File[]) => { + setImages(files); + imagesRef.current = files; + }; - const handleCreatePost = () => { + const handleCreatePost = async () => { const contentToSave = contentRef.current; - console.log('Create post: ', contentToSave); - if (!contentToSave) { + const images = imagesRef.current; + + // Walidacja: musi być treść lub zdjęcia + if (!contentToSave && images.length === 0) { alert.showError({ - title: 'Error', - message: 'Content is empty', - level: 'WARNING', - timestamp: new Date().toISOString() + title: "Error", + message: "Post cannot be empty. Please add content or images.", + level: "WARNING", + timestamp: new Date().toISOString(), }); return; } - api.post('/api/posts', null, {params: {content: contentToSave}}).then((response) => { - if (response.status === 200) { - close(); - } + // Prepare form data + const formData = new FormData(); + if (contentToSave) formData.append("content", contentToSave); + images.forEach((image, index) => { + formData.append(`images[${index}]`, image); }); - } + + // TODO: After photos integration + const response = await api.post("/api/posts", formData, { + headers: {"Content-Type": "multipart/form-data"}, + }); + + if (response.status === 200) { + alert.showError({ + title: "Success", + message: "Post created successfully!", + level: "INFO", + timestamp: new Date().toISOString(), + }); + close(); + } + }; return ( - ); -} \ No newline at end of file +}; diff --git a/frontend/src/Features/CreatePost/components/PostForm/PostForm.tsx b/frontend/src/Features/CreatePost/components/PostForm/PostForm.tsx new file mode 100644 index 0000000..b3ef93a --- /dev/null +++ b/frontend/src/Features/CreatePost/components/PostForm/PostForm.tsx @@ -0,0 +1,110 @@ +import {useState} from "react"; +import {Dropzone, MIME_TYPES} from "@mantine/dropzone"; +import {Button, Group, Image, Paper, rem, SimpleGrid, Text} from "@mantine/core"; +import {ModalRichContent} from "../../../shared/consts"; +import {useAlert} from "../../../../Providers/AlertProvider.tsx"; +import {IconPhoto, IconUpload, IconX} from "@tabler/icons-react"; + +type PostFormProps = { + handleContentChange: (html: string) => void; + setImages: (files: File[]) => void; +}; + +export const PostForm = ({handleContentChange, setImages}: PostFormProps) => { + const [localImages, setLocalImages] = useState([]); + const [previews, setPreviews] = useState<{ src: string; name: string }[]>([]); + const alert = useAlert(); + + // Function to handle dropping images + const handleDrop = (files: File[]) => { + if (localImages.length + files.length > 5) { + alert.showError({ + title: "Too many images", + message: "You can upload up to 5 images", + level: "WARNING", + timestamp: new Date().toISOString(), + }); + return; + } + + // Add new images to the existing ones + const updatedImages = [...localImages, ...files]; + setLocalImages(updatedImages); + setImages(updatedImages); // Passing to CreatePost + + setPreviews((prevPreviews) => [ + ...prevPreviews, + ...files.map((file) => ({src: URL.createObjectURL(file), name: file.name})), + ]); + }; + + // Function to remove an image + const handleRemoveImage = (index: number) => { + const updatedImages = localImages.filter((_, i) => i !== index); + setLocalImages(updatedImages); + setImages(updatedImages); // Passing to CreatePost + + URL.revokeObjectURL(previews[index].src); + setPreviews((prevPreviews) => prevPreviews.filter((_, i) => i !== index)); + }; + + return ( + <> + + + + + + + + + + + + + + +
+ + Drag images here or click to select files + + + Attach up to 5 files, each file should not exceed 3MB + +
+
+
+ + {previews.length > 0 && ( + + {previews.map((preview, index) => ( + + {`preview-${index}`} + + + {preview.name} + + + + ))} + + )} + + ); +}; diff --git a/frontend/src/Features/CreatePost/components/PostForm/index.tsx b/frontend/src/Features/CreatePost/components/PostForm/index.tsx new file mode 100644 index 0000000..aa920b0 --- /dev/null +++ b/frontend/src/Features/CreatePost/components/PostForm/index.tsx @@ -0,0 +1 @@ +export {PostForm} from './PostForm'; \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index e746d98..a9aa131 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -6,6 +6,7 @@ import '@mantine/dates/styles.css'; import '@mantine/tiptap/styles.css'; import '@mantine/notifications/styles.css'; import '@mantine/carousel/styles.css'; +import '@mantine/dropzone/styles.css'; import i18next from "i18next"; import {createTheme, MantineProvider} from "@mantine/core"; import {BrowserRouter} from "react-router-dom"; @@ -27,8 +28,8 @@ const theme = createTheme({ createRoot(document.getElementById('root')!).render( - - + + @@ -37,7 +38,7 @@ createRoot(document.getElementById('root')!).render( - - + + )