This commit is contained in:
MisbehavedNinjaRadiator 2025-07-04 11:35:48 +02:00 committed by MisbehavedNinjaRadiator
parent 2641e5cf19
commit 7592d4f9cd

View file

@ -1,12 +1,12 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { import {
Box, Box,
Card, Card,
CardMedia, CardMedia,
CardActionArea, CardActionArea,
IconButton, IconButton,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import Chip from "@mui/material/Chip"; import Chip from "@mui/material/Chip";
import Autocomplete from "@mui/material/Autocomplete"; import Autocomplete from "@mui/material/Autocomplete";
@ -19,306 +19,306 @@ import ButtonPrimary from "../../components/buttons/buttonRotkehlchen/ButtonRotk
import "./postCreation.css"; import "./postCreation.css";
const theme = createTheme({ const theme = createTheme({
palette: { palette: {
primary: { primary: {
main: "#e79a0e;", main: "#e79a0e;",
}, },
}, },
}); });
function PostCreation() { function PostCreation() {
const { user } = useAuth(); const { user } = useAuth();
interface ImageItem { interface ImageItem {
src: string; src: string;
title: string; title: string;
} }
const [isSend, setIsSend] = useState<boolean>(false); const [isSend, setIsSend] = useState<boolean>(false);
const [options, setOptions] = useState<string[]>([]); const [options, setOptions] = useState<string[]>([]);
const [tags, setTags] = useState<string[]>([]); const [tags, setTags] = useState<string[]>([]);
const [description, setDescription] = useState<string>(""); const [description, setDescription] = useState<string>("");
const [selectedImage, setSelectedImage] = useState<string>(); const [selectedImage, setSelectedImage] = useState<string>();
const [data, setData] = useState<ImageItem[]>([]); const [data, setData] = useState<ImageItem[]>([]);
const navigate = useNavigate(); const navigate = useNavigate();
const [fileList, setFileList] = useState<File[]>([]); const [fileList, setFileList] = useState<File[]>([]);
const inputFile = useRef<HTMLInputElement | null>(null); const inputFile = useRef<HTMLInputElement | null>(null);
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
(async () => { (async () => {
try { try {
const res = await api.get<string[]>("/posts/tags"); // GET /api/posts/tags const res = await api.get<string[]>("/posts/tags"); // GET /api/posts/tags
if (!cancelled) setOptions(res.data); if (!cancelled) setOptions(res.data);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, []); }, []);
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setDescription(event.target.value); setDescription(event.target.value);
}; };
const handleTags = (event: any, newValue: string[]) => { const handleTags = (event: any, newValue: string[]) => {
newValue.forEach((val) => { newValue.forEach((val) => {
if (!options.includes(val)) { if (!options.includes(val)) {
setOptions((prev) => [...prev, val]); setOptions((prev) => [...prev, val]);
} }
}); });
setTags(newValue); setTags(newValue);
}; };
const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => { const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files; const files = event.target.files;
if (files && files.length > 0) { if (files && files.length > 0) {
const fileArr = Array.from(files); const fileArr = Array.from(files);
const newItems: ImageItem[] = Array.from(files).map((file) => ({ const newItems: ImageItem[] = Array.from(files).map((file) => ({
src: URL.createObjectURL(file), src: URL.createObjectURL(file),
title: file.name, title: file.name,
})); }));
const lastImageUrl = newItems[newItems.length - 1].src; const lastImageUrl = newItems[newItems.length - 1].src;
setData((prev) => [...prev, ...newItems]); setData((prev) => [...prev, ...newItems]);
setSelectedImage(lastImageUrl); setSelectedImage(lastImageUrl);
setFileList((prev) => [...prev, ...fileArr]); setFileList((prev) => [...prev, ...fileArr]);
} }
}; };
const onEmptyImgClick = () => { const onEmptyImgClick = () => {
if (inputFile.current) { if (inputFile.current) {
inputFile.current?.click(); inputFile.current?.click();
} }
}; };
const handleDelete = (idx: number) => { const handleDelete = (idx: number) => {
setData((prev) => prev.filter((_, i) => i !== idx)); setData((prev) => prev.filter((_, i) => i !== idx));
if (idx - 1 < 0) { if (idx - 1 < 0) {
if (data.length == 1) { if (data.length == 1) {
setSelectedImage(""); setSelectedImage("");
} else { } else {
setSelectedImage(data[idx + 1].src); setSelectedImage(data[idx + 1].src);
} }
} else { } else {
setSelectedImage(data[idx - 1].src); setSelectedImage(data[idx - 1].src);
} }
}; };
const onSubmit = async (event: React.FormEvent) => { const onSubmit = async (event: React.FormEvent) => {
if (!isSend) { if (!isSend) {
setIsSend(true); setIsSend(true);
event.preventDefault(); event.preventDefault();
if (!fileList) { if (!fileList) {
return; return;
} }
const fData = new FormData(); const fData = new FormData();
files.forEach((file, i) => { files.forEach((file, i) => {
fData.append(`images`, file, file.name); fData.append(`images`, file, file.name);
}); });
fData.append("status", "PUBLIC"); fData.append("status", "PUBLIC");
fData.append("description", description); fData.append("description", description);
tags.forEach((tag) => { tags.forEach((tag) => {
fData.append("tags", tag); fData.append("tags", tag);
}); });
try { try {
await api.post("/posts/upload", fData); await api.post("/posts/upload", fData);
navigate(`/profile/${user?.username}`); navigate(`/profile/${user?.username}`);
} catch (error: any) { } catch (error: any) {
console.log(error); console.log(error);
} }
} }
}; };
const onCancel = () => { const onCancel = () => {
navigate(`/profile/${user?.username}`); navigate(`/profile/${user?.username}`);
}; };
const files = fileList ? [...fileList] : []; const files = fileList ? [...fileList] : [];
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<div className="create-display"> <div className="create-display">
<div className="create-part"> <div className="create-part">
<form onSubmit={onSubmit}> <form onSubmit={onSubmit}>
<h1>Create Post</h1> <h1>Create Post</h1>
<div className="create-layout"> <div className="create-layout">
<div className="create-preview"> <div className="create-preview">
{selectedImage ? ( {selectedImage ? (
<img <img
src={selectedImage} src={selectedImage}
className="create-post-image" className="create-post-image"
alt="Add an Image" alt="Add an Image"
></img> ></img>
) : ( ) : (
<label <label
className="create-post-img-layer" className="create-post-img-layer"
onClick={onEmptyImgClick} onClick={onEmptyImgClick}
> >
<strong>Add Picture</strong> <strong>Add Picture</strong>
</label> </label>
)} )}
</div> </div>
<div className="create-post-desc"> <div className="create-post-desc">
<h2>Description*</h2> <h2>Description*</h2>
<textarea <textarea
className="create-post-description" className="create-post-description"
value={description} value={description}
onChange={handleChange} onChange={handleChange}
required required
></textarea> ></textarea>
</div> </div>
<Box <Box
className="strip" className="strip"
sx={{ sx={{
display: "flex", display: "flex",
gap: 1, gap: 1,
py: 1, py: 1,
overflowX: "auto", overflowX: "auto",
overflowY: "hidden", overflowY: "hidden",
width: "100%", width: "100%",
maxWidth: { xs: "90vw", sm: "600px" }, maxWidth: { xs: "90vw", sm: "600px" },
mx: "auto", mx: "auto",
scrollSnapType: "x mandatory", scrollSnapType: "x mandatory",
"& > *": { scrollSnapAlign: "center" }, "& > *": { scrollSnapAlign: "center" },
"::-webkit-scrollbar": { display: "none" }, "::-webkit-scrollbar": { display: "none" },
}} }}
> >
{/* Upload card */} {/* Upload card */}
<Card <Card
key="add" key="add"
variant="outlined" variant="outlined"
sx={{ sx={{
minWidth: 60, minWidth: 60,
width: 60, width: 60,
height: 60, height: 60,
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
flexShrink: 0, flexShrink: 0,
}} }}
> >
<label <label
style={{ cursor: "pointer", width: "100%", height: "100%" }} style={{ cursor: "pointer", width: "100%", height: "100%" }}
> >
<img <img
src="/assets/icons/add-plus.svg" src="/assets/icons/add-plus.svg"
alt="Upload" alt="Upload"
style={{ width: "100%", height: "100%" }} style={{ width: "100%", height: "100%" }}
/> />
<input <input
id="create-file-upload" id="create-file-upload"
type="file" type="file"
accept="image/*" accept="image/*"
multiple multiple
onChange={handleImageUpload} onChange={handleImageUpload}
style={{ display: "none" }} style={{ display: "none" }}
ref={inputFile} ref={inputFile}
required required
/> />
</label> </label>
</Card> </Card>
{/* Image cards */} {/* Image cards */}
{data.map((item, idx) => ( {data.map((item, idx) => (
<Card <Card
key={`${item.title}-${idx}`} key={`${item.title}-${idx}`}
variant="outlined" variant="outlined"
sx={{ sx={{
position: "relative", position: "relative",
minWidth: 60, minWidth: 60,
width: 60, width: 60,
height: 60, height: 60,
flexShrink: 0, flexShrink: 0,
}} }}
onClick={() => setSelectedImage(item.src)} onClick={() => setSelectedImage(item.src)}
> >
{/* Delete button */} {/* Delete button */}
<IconButton <IconButton
size="small" size="small"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleDelete(idx); handleDelete(idx);
}} }}
sx={{ sx={{
position: "absolute", position: "absolute",
top: 2, top: 2,
right: 2, right: 2,
backgroundColor: "background.paper", backgroundColor: "background.paper",
zIndex: 1, zIndex: 1,
}} }}
> >
<CloseIcon fontSize="small" /> <CloseIcon fontSize="small" />
</IconButton> </IconButton>
<CardActionArea sx={{ width: "100%", height: "100%" }}> <CardActionArea sx={{ width: "100%", height: "100%" }}>
<CardMedia <CardMedia
component="img" component="img"
image={item.src} image={item.src}
alt={item.title} alt={item.title}
sx={{ sx={{
width: "100%", width: "100%",
height: "100%", height: "100%",
objectFit: "cover", objectFit: "cover",
}} }}
/> />
</CardActionArea> </CardActionArea>
</Card> </Card>
))} ))}
</Box> </Box>
<Autocomplete <Autocomplete
className="create-tags" className="create-tags"
multiple multiple
id="tags-filled" id="tags-filled"
options={options} options={options}
freeSolo freeSolo
value={tags} value={tags}
onChange={handleTags} onChange={handleTags}
sx={{ width: "100%" }} sx={{ width: "100%" }}
renderValue={(value: readonly string[], getItemProps) => renderValue={(value: readonly string[], getItemProps) =>
value.map((tags: string, index: number) => { value.map((tags: string, index: number) => {
const { key, ...itemProps } = getItemProps({ index }); const { key, ...itemProps } = getItemProps({ index });
return ( return (
<Chip <Chip
variant="outlined" variant="outlined"
label={tags} label={tags}
key={key} key={key}
{...itemProps} {...itemProps}
/> />
); );
}) })
} }
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextField
{...params} {...params}
variant="outlined" variant="outlined"
color="primary" color="primary"
label="Tags" label="Tags"
placeholder="Add Tags" placeholder="Add Tags"
/> />
)} )}
/> />
<div className="create-buttons"> <div className="create-buttons">
<ButtonPrimary <ButtonPrimary
style="secondary" style="secondary"
label="Cancel" label="Cancel"
type="button" type="button"
onClick={onCancel} onClick={onCancel}
></ButtonPrimary> ></ButtonPrimary>
<ButtonPrimary <ButtonPrimary
style="primary" style="primary"
label="Post" label="Post"
type="submit" type="submit"
></ButtonPrimary> ></ButtonPrimary>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</ThemeProvider> </ThemeProvider>
); );
} }
export default PostCreation; export default PostCreation;