Added CommunityPost Image Function

This commit is contained in:
Rykkel
2024-08-02 07:29:04 +08:00
parent 0fc5dec607
commit 324fc6bb24
10 changed files with 1675 additions and 1244 deletions

556
client/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
import React, { useState } from 'react';
interface InsertPostImageProps {
onImageSelected: (file: File | null) => void;
}
const InsertPostImage: React.FC<InsertPostImageProps> = ({ onImageSelected }) => {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [previewImage, setPreviewImage] = useState<string>('');
const handleImageSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedFiles = event.target.files as FileList;
const file = selectedFiles?.[0] || null;
setSelectedFile(file);
setPreviewImage(file ? URL.createObjectURL(file) : '');
onImageSelected(file);
};
return (
<div className={`flex flex-col dark:bg-zinc-800 rounded-md ${selectedFile ? 'h-auto' : 'h-20'}`}
style={{ width: 300 }}>
<div>
<div className="flex flex-col">
<input
type="file"
onChange={handleImageSelect}
className="mb-3"
/>
</div>
{selectedFile && (
<div>
<img
src={previewImage}
alt="Selected Image"
className="w-full h-full object-cover rounded-md"
/>
</div>
)}
</div>
</div>
);
};
export default InsertPostImage;

View File

@@ -542,19 +542,17 @@ export const ArrowTopRightOnSquare = () => {
export const TrashDeleteIcon = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
className="size-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="size-6">
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
);
};

View File

@@ -57,6 +57,7 @@ export default function CommunityPage() {
const [search, setSearch] = useState(""); // Search Function
const [userInformation, setUserInformation] = useState<Record<string, User>>({});
const [currentUserInfo, setCurrentUserInfo] = useState(null);
const [imageErrorFlags, setImageErrorFlags] = useState<Record<string, boolean>>({});
let accessToken = localStorage.getItem("accessToken");
if (!accessToken) {
@@ -80,7 +81,6 @@ export default function CommunityPage() {
setCommunityList(res.data);
});
};
useEffect(() => {
getPosts();
}, []);
@@ -165,6 +165,7 @@ export default function CommunityPage() {
}
};
const handlePostClick = (id: string) => {
navigate(`post/${id}`);
};
@@ -174,6 +175,13 @@ export default function CommunityPage() {
return `${config.serverAddress}/users/profile-image/${userId}`;
};
const handleImageError = (postId: string) => {
setImageErrorFlags((prevFlags) => ({
...prevFlags,
[postId]: true,
}));
};
return (
<div className="w-full h-full">
<div className="flex flex-row gap-4 m-10">
@@ -244,9 +252,13 @@ export default function CommunityPage() {
<div>
<p>{post.content}</p>
</div>
<div>
<p>Image</p>
</div>
{!imageErrorFlags[post.id] && post.postImage && post.postImage !== null && (
<div>
<img src={`${config.serverAddress}/post/post-image/${post.id}`}
className="w-[300px] h-auto rounded-lg object-cover"
onError={() => handleImageError(post.id)} />
</div>
)}
</div>
<div className="flex flex-col gap-2">
<div className="flex flex-row gap-2">

View File

@@ -9,6 +9,7 @@ import config from "../config";
import { ArrowUTurnLeftIcon } from "../icons";
import { useEffect, useState } from "react";
import { retrieveUserInformation } from "../security/users";
import InsertPostImage from "../components/InsertPostImage";
const validationSchema = Yup.object({
title: Yup.string()
@@ -29,16 +30,20 @@ const validationSchema = Yup.object({
"Only letters, numbers, commas, spaces, exclamation marks, quotations, and common symbols are allowed"
)
.required("Content is required"),
postImage: Yup.mixed(),
});
function CreatePostPage() {
const navigate = useNavigate();
const [userId, setUserId] = useState(null);
// Add state for image selection
const initialValues = {
title: "",
content: "",
postImage: null,
tags: "",
userId: "",
};
useEffect(() => {
@@ -55,17 +60,30 @@ function CreatePostPage() {
const handleSubmit = async (
values: any,
{ setSubmitting, resetForm, setFieldError }: any
{ setSubmitting, resetForm, setFieldError, setFieldValue }: any
) => {
try {
const postData = {
...values,
userId: userId
const formData = new FormData();
formData.append("title", values.title);
formData.append("content", values.content);
if (values.postImage) {
formData.append("postImage", values.postImage);
}
const response = await axios.post(config.serverAddress + "/post", postData); // Assuming an API route
formData.append("tags", values.tags);
formData.append("userId", userId || ""); // Ensure userId is appended to formData
console.log("Submitting formData:", formData);
const response = await axios.post(config.serverAddress + "/post", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
if (response.status === 200) {
console.log("Post created successfully:", response.data);
resetForm(); // Clear form after successful submit
setFieldValue("postImage", null);
navigate(-1);
} else {
console.error("Error creating post:", response.statusText);
@@ -99,7 +117,7 @@ function CreatePostPage() {
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ isValid, dirty, isSubmitting }) => (
{({ isValid, dirty, isSubmitting, setFieldValue }) => (
<Form className="flex flex-col gap-5">
<div>
<NextUIFormikInput
@@ -110,11 +128,6 @@ function CreatePostPage() {
labelPlacement="inside"
/>
</div>
<div className="text-sm">
<div>
<p>Image</p>
</div>
</div>
<div>
<NextUIFormikTextarea
label="Content"
@@ -131,10 +144,19 @@ function CreatePostPage() {
labelPlacement="inside"
/>
</div>
<div className="text-sm">
<div className="flex flex-row gap-10">
<InsertPostImage
onImageSelected={(file) => {
setFieldValue("postImage", file);
}}
/>
</div>
</div>
<div className="flex flex-row-reverse">
<Button
type="submit"
className="bg-primary-color text-white text-xl w-1/12"
className="bg-primary-950 text-white text-xl w-1/12"
disabled={!isValid || !dirty || isSubmitting}
>
<p>Post</p>

View File

@@ -9,6 +9,7 @@ import NextUIFormikTextarea from "../components/NextUIFormikTextarea";
import config from "../config";
import instance from "../security/http";
import { ArrowUTurnLeftIcon } from "../icons";
import InsertPostImage from "../components/InsertPostImage";
const validationSchema = Yup.object({
title: Yup.string()
@@ -29,20 +30,26 @@ const validationSchema = Yup.object({
"Only letters, numbers, commas, spaces, exclamation marks, quotations, and common symbols are allowed"
)
.required("Content is required"),
postImage: Yup.mixed(),
});
function editPost() {
function EditPostPage() {
const { id } = useParams();
const navigate = useNavigate();
const [post, setPost] = useState({
title: "",
content: "",
postImage: null,
tags: "",
});
const [loading, setLoading] = useState(true);
useEffect(() => {
instance.get(config.serverAddress + `/post/${id}`).then((res) => {
setPost(res.data);
setPost({
...res.data,
postImage: `${config.serverAddress}/post/post-image/${id}`, // Set image URL
});
setLoading(false);
});
}, [id]);
@@ -52,13 +59,24 @@ function editPost() {
{ setSubmitting, resetForm, setFieldError }: any
) => {
try {
const formData = new FormData();
formData.append("title", values.title);
formData.append("content", values.content);
if (values.postImage) {
formData.append("postImage", values.postImage);
}
formData.append("tags", values.tags);
const response = await instance.put(
config.serverAddress + `/post/${id}`,
values
formData,
{ headers: { "Content-Type": "multipart/form-data" } }
);
if (response.status === 200) {
console.log("Post updated successfully:", response.data);
resetForm();
// Set a flag to indicate a refresh is needed
navigate(-1);
} else {
console.error("Error updating post:", response.statusText);
@@ -93,7 +111,7 @@ function editPost() {
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ isValid, dirty, isSubmitting }) => (
{({ isValid, dirty, isSubmitting, setFieldValue }) => (
<Form className="flex flex-col gap-5">
<div>
<NextUIFormikInput
@@ -104,9 +122,6 @@ function editPost() {
labelPlacement="inside"
/>
</div>
<div className="text-sm">
<p>Image</p>
</div>
<div>
<NextUIFormikTextarea
label="Content"
@@ -123,10 +138,19 @@ function editPost() {
labelPlacement="inside"
/>
</div>
<div className="text-sm">
<div className="flex flex-row gap-10">
<InsertPostImage
onImageSelected={(file) => {
setFieldValue("postImage", file);
}}
/>
</div>
</div>
<div className="flex flex-row-reverse">
<Button
type="submit"
className="bg-primary-color text-white text-xl w-1/6"
className="bg-primary-950 text-white text-xl w-1/6"
disabled={!isValid || !dirty || isSubmitting}
>
<p>Update</p>
@@ -141,4 +165,4 @@ function editPost() {
);
}
export default editPost;
export default EditPostPage;

View File

@@ -50,6 +50,7 @@ const PostPage: React.FC = () => {
const { isOpen, onOpen, onOpenChange } = useDisclosure();
const [selectedPost, setSelectedPost] = useState<Post | null>(null);
const [userInformation, setUserInformation] = useState<Record<string, User>>({});
const [imageErrorFlags, setImageErrorFlags] = useState<Record<string, boolean>>({});
useEffect(() => {
if (id) {
@@ -57,7 +58,7 @@ const PostPage: React.FC = () => {
.then((res) => setPost(res.data))
.catch((error) => console.error("Error fetching post:", error));
}
}, [id]);
}, []);
useEffect(() => {
if (post) {
@@ -112,6 +113,13 @@ const PostPage: React.FC = () => {
};
const profilePictureUrl = post ? getProfilePicture(post.userId) : "";
const handleImageError = (postId: string) => {
setImageErrorFlags((prevFlags) => ({
...prevFlags,
[postId]: true,
}));
};
return (
<div className="w-full h-full">
<section className="flex">
@@ -147,8 +155,8 @@ const PostPage: React.FC = () => {
</div>
<div className="flex flex-row-reverse justify-center items-center">
<Dropdown>
<DropdownTrigger
className="justify-center items-center">
<DropdownTrigger
className="justify-center items-center">
<Button isIconOnly variant="light">
<EllipsisHorizontalIcon />
</Button>
@@ -179,15 +187,14 @@ const PostPage: React.FC = () => {
<div>
<p>{post.content}</p>
</div>
<div>
<p>Image</p>
{/* {userInformation && (
<UserPostImage
userId={userInformation}
editable={true}
/>
)} */}
</div>
{!imageErrorFlags[post.id] && post.postImage && post.postImage !== null && (
<div>
<img src={`${config.serverAddress}/post/post-image/${post.id}`}
alt="PostImage"
className="w-[300px] h-auto rounded-lg object-cover"
onError={() => handleImageError(post.id)} />
</div>
)}
</div>
<div className="flex flex-col gap-2">
<div className="flex flex-row gap-2">