diff --git a/client/src/components/NavigationBar.tsx b/client/src/components/NavigationBar.tsx
index ea6f863..8be17f0 100644
--- a/client/src/components/NavigationBar.tsx
+++ b/client/src/components/NavigationBar.tsx
@@ -95,7 +95,7 @@ export default function NavigationBar() {
navigate("/community");
}}
>
-
Community
+ Community Forums
diff --git a/client/src/icons.tsx b/client/src/icons.tsx
index 44c535b..ca83e42 100644
--- a/client/src/icons.tsx
+++ b/client/src/icons.tsx
@@ -135,3 +135,136 @@ export const ArrowRightStartOnRectangleIcon = () => {
);
};
+
+export const EllipsisHorizontalIcon = () => {
+ return (
+
+ );
+};
+
+export const PlusIcon = () => {
+ return (
+
+ );
+};
+
+export const ArrowUTurnLeftIcon = () => {
+ return (
+
+ );
+};
+
+export const MagnifyingGlassIcon = () => {
+ return (
+
+ );
+};
+
+export const XMarkIcon = () => {
+ return (
+
+ );
+};
+
+export const HandThumbsUpIcon = () => {
+ return (
+
+ );
+};
+
+export const ChatBubbleOvalLeftEllipsisIcon = () => {
+ return (
+
+ );
+};
diff --git a/client/src/layouts/default.tsx b/client/src/layouts/default.tsx
index 49de78b..034492f 100644
--- a/client/src/layouts/default.tsx
+++ b/client/src/layouts/default.tsx
@@ -13,6 +13,12 @@ export default function DefaultLayout({
{children}
+
+ {/*
+ A div that becomes black in dark mode to cover white color parts
+ of the website when scrolling past the window's original view.
+ */}
+
);
}
diff --git a/client/src/pages/CommunityPage.tsx b/client/src/pages/CommunityPage.tsx
index 965617c..162496a 100644
--- a/client/src/pages/CommunityPage.tsx
+++ b/client/src/pages/CommunityPage.tsx
@@ -1,10 +1,35 @@
// import { title } from "@/components/primitives";
import DefaultLayout from "../layouts/default";
-import { SetStateAction, useEffect, useState } from 'react';
-import { Button, Avatar, Link, Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Input } from "@nextui-org/react";
-import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, useDisclosure } from "@nextui-org/react";
+import { SetStateAction, useEffect, useState } from "react";
+import {
+ Button,
+ Avatar,
+ Dropdown,
+ DropdownTrigger,
+ DropdownMenu,
+ DropdownItem,
+ Input,
+ Chip,
+} from "@nextui-org/react";
+import {
+ Modal,
+ ModalContent,
+ ModalHeader,
+ ModalBody,
+ ModalFooter,
+ useDisclosure,
+} from "@nextui-org/react";
import config from "../config";
import instance from "../security/http";
+import {
+ ChatBubbleOvalLeftEllipsisIcon,
+ EllipsisHorizontalIcon,
+ HandThumbsUpIcon,
+ MagnifyingGlassIcon,
+ PlusIcon,
+ XMarkIcon,
+} from "../icons";
+import { useNavigate } from "react-router-dom";
interface Post {
title: string;
@@ -14,6 +39,7 @@ interface Post {
}
export default function CommunityPage() {
+ const navigate = useNavigate();
const { isOpen, onOpen, onOpenChange } = useDisclosure();
const [selectedPost, setSelectedPost] = useState(null);
@@ -24,21 +50,21 @@ export default function CommunityPage() {
const [communityList, setCommunityList] = useState([]);
// Search Function
- const [search, setSearch] = useState('');
- const onSearchChange = (e: { target: { value: SetStateAction; }; }) => {
+ const [search, setSearch] = useState("");
+ const onSearchChange = (e: { target: { value: SetStateAction } }) => {
setSearch(e.target.value);
};
const getPosts = () => {
- instance
- .get(config.serverAddress + '/post').then((res) => {
- setCommunityList(res.data);
- });
+ instance.get(config.serverAddress + "/post").then((res) => {
+ setCommunityList(res.data);
+ });
};
const searchPosts = () => {
instance
- .get(config.serverAddress + `/post?search=${search}`).then((res) => {
+ .get(config.serverAddress + `/post?search=${search}`)
+ .then((res) => {
setCommunityList(res.data);
});
};
@@ -47,7 +73,7 @@ export default function CommunityPage() {
getPosts();
}, []);
- const onSearchKeyDown = (e: { key: string; }) => {
+ const onSearchKeyDown = (e: { key: string }) => {
if (e.key === "Enter") {
searchPosts();
}
@@ -55,18 +81,17 @@ export default function CommunityPage() {
const onClickSearch = () => {
searchPosts();
- }
+ };
const onClickClear = () => {
- setSearch('');
+ setSearch("");
getPosts();
};
useEffect(() => {
- instance
- .get(config.serverAddress + '/post').then((res) => {
- console.log(res.data);
- setCommunityList(res.data);
- });
+ instance.get(config.serverAddress + "/post").then((res) => {
+ console.log(res.data);
+ setCommunityList(res.data);
+ });
}, []);
const handleDeleteClick = (post: Post) => {
@@ -76,58 +101,55 @@ export default function CommunityPage() {
const handleDeleteConfirm = async () => {
if (selectedPost) {
try {
- await instance
- .delete(config.serverAddress + `/post/${selectedPost.id}`);
- setCommunityList((prevList) => prevList.filter(post => post.id !== selectedPost.id));
+ await instance.delete(
+ config.serverAddress + `/post/${selectedPost.id}`
+ );
+ setCommunityList((prevList) =>
+ prevList.filter((post) => post.id !== selectedPost.id)
+ );
onOpenChange();
} catch (error) {
- console.error('Error deleting post:', error);
+ console.error("Error deleting post:", error);
}
}
};
-
return (
-
-
- {
- communityList.map((post) => {
+
+
+
+
Community Forums
+
+ Socialize, share your experience or ask a question!
+
+
+
+ {communityList.map((post) => {
return (
-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
-
+
@@ -135,13 +157,17 @@ export default function CommunityPage() {
{
- // Navigate to editPost page with post.id
- const editPostUrl = `/editPost/${post.id}`; // Replace with your actual edit post route
- window.location.href = editPostUrl;
- }}>
+ navigate(`/editPost/${post.id}`);
+ }}
+ >
Edit
- handleDeleteClick(post)}>
+ handleDeleteClick(post)}
+ >
Delete
@@ -149,112 +175,69 @@ export default function CommunityPage() {
-
- {post.content}
-
+
{post.content}
-
-
-
tag1
+
-
-
-
-
-
...
+
+
+
+
);
- })
- }
-
-
-
-
-
-
-
-
-
+ })}
-
-
+
+
+
}
+ className=" bg-red-500 dark:bg-red-700 text-white"
+ size="lg"
+ onPress={() => {
+ navigate("/createPost");
+ }}
+ >
+
Create a post!
+
+
+
+
+
+ }
+ />
+
+
{(onClose) => (
<>
- Confirm Delete
+
+ Confirm Delete
+
Are you sure you want to delete this post?
diff --git a/client/src/pages/CreatePostPage.tsx b/client/src/pages/CreatePostPage.tsx
index b37b216..0d1b6a4 100644
--- a/client/src/pages/CreatePostPage.tsx
+++ b/client/src/pages/CreatePostPage.tsx
@@ -1,127 +1,131 @@
-import DefaultLayout from '../layouts/default';
-import { Button, Link } from "@nextui-org/react";
+import DefaultLayout from "../layouts/default";
+import { Button } from "@nextui-org/react";
import { Formik, Form } from "formik";
-import * as Yup from 'yup';
+import * as Yup from "yup";
import axios from "axios";
-import { useNavigate } from 'react-router-dom';
-import NextUIFormikInput from '../components/NextUIFormikInput';
-import NextUIFormikTextarea from '../components/NextUIFormikTextarea';
-import config from '../config';
+import { useNavigate } from "react-router-dom";
+import NextUIFormikInput from "../components/NextUIFormikInput";
+import NextUIFormikTextarea from "../components/NextUIFormikTextarea";
+import config from "../config";
+import { ArrowUTurnLeftIcon } from "../icons";
const validationSchema = Yup.object({
- title: Yup.string().trim()
- .min(3, 'Title must be at least 3 characters')
- .max(200, 'Title must be at most 200 characters')
- .matches(/^[a-zA-Z0-9\s]+$/, "Title can only contain letters, numbers, and spaces")
- .required('Title is required'),
- content: Yup.string().trim()
- .min(3, 'Content must be at least 3 characters')
- .max(500, 'Content must be at most 500 characters')
- .matches(/^[a-zA-Z0-9,\s!"'-]*$/, 'Only letters, numbers, commas, spaces, exclamation marks, quotations, and common symbols are allowed')
- .required('Content is required')
+ title: Yup.string()
+ .trim()
+ .min(3, "Title must be at least 3 characters")
+ .max(200, "Title must be at most 200 characters")
+ .matches(
+ /^[a-zA-Z0-9\s]+$/,
+ "Title can only contain letters, numbers, and spaces"
+ )
+ .required("Title is required"),
+ content: Yup.string()
+ .trim()
+ .min(3, "Content must be at least 3 characters")
+ .max(500, "Content must be at most 500 characters")
+ .matches(
+ /^[a-zA-Z0-9,\s!"'-]*$/,
+ "Only letters, numbers, commas, spaces, exclamation marks, quotations, and common symbols are allowed"
+ )
+ .required("Content is required"),
});
function CreatePostPage() {
- const navigate = useNavigate();
+ const navigate = useNavigate();
- const initialValues = {
- title: '',
- content: '',
- tags: ''
- };
+ const initialValues = {
+ title: "",
+ content: "",
+ tags: "",
+ };
- const handleSubmit = async (values: any, { setSubmitting, resetForm, setFieldError }: any) => {
- try {
- const response = await axios
- .post(config.serverAddress + '/post', values); // Assuming an API route
- if (response.status === 200) {
- console.log('Post created successfully:', response.data);
- resetForm(); // Clear form after successful submit
- navigate("/community");
- } else {
- console.error('Error creating post:', response.statusText);
- }
- } catch (error: any) {
- if (error.response && error.response.data && error.response.data.field) {
- setFieldError(error.response.data.field, error.response.data.error);
- } else {
- console.error('Unexpected error:', error);
- }
- } finally {
- setSubmitting(false);
- }
- };
+ const handleSubmit = async (
+ values: any,
+ { setSubmitting, resetForm, setFieldError }: any
+ ) => {
+ try {
+ const response = await axios.post(config.serverAddress + "/post", values); // Assuming an API route
+ if (response.status === 200) {
+ console.log("Post created successfully:", response.data);
+ resetForm(); // Clear form after successful submit
+ navigate("/community");
+ } else {
+ console.error("Error creating post:", response.statusText);
+ }
+ } catch (error: any) {
+ if (error.response && error.response.data && error.response.data.field) {
+ setFieldError(error.response.data.field, error.response.data.error);
+ } else {
+ console.error("Unexpected error:", error);
+ }
+ } finally {
+ setSubmitting(false);
+ }
+ };
- return (
-
-
-
-
+
+
+
+
+
+ {({ isValid, dirty, isSubmitting }) => (
+
+ )}
+
+
+
+ );
}
export default CreatePostPage;
diff --git a/client/src/pages/EditPostPage.tsx b/client/src/pages/EditPostPage.tsx
index f6a177b..d0fc401 100644
--- a/client/src/pages/EditPostPage.tsx
+++ b/client/src/pages/EditPostPage.tsx
@@ -1,5 +1,5 @@
import DefaultLayout from "../layouts/default";
-import { Button, Link } from "@nextui-org/react";
+import { Button } from "@nextui-org/react";
import { Formik, Form } from "formik";
import * as Yup from "yup";
import { useEffect, useState } from "react";
@@ -9,6 +9,7 @@ import NextUIFormikInput from "../components/NextUIFormikInput";
import NextUIFormikTextarea from "../components/NextUIFormikTextarea";
import config from "../config";
import instance from "../security/http";
+import { ArrowUTurnLeftIcon } from "../icons";
const validationSchema = Yup.object({
title: Yup.string()
@@ -34,13 +35,6 @@ const validationSchema = Yup.object({
function editPost() {
const { id } = useParams();
const navigate = useNavigate();
-
- // const initialValues = {
- // title: '',
- // content: '',
- // tags: ''
- // };
-
const [post, setPost] = useState({
title: "",
content: "",
@@ -62,10 +56,10 @@ function editPost() {
const response = await instance.put(
config.serverAddress + `/post/${id}`,
values
- ); // Assuming an API route
+ );
if (response.status === 200) {
console.log("Post updated successfully:", response.data);
- resetForm(); // Clear form after successful submit
+ resetForm();
navigate("/community");
} else {
console.error("Error updating post:", response.statusText);
@@ -84,21 +78,14 @@ function editPost() {
return (
-
-
-
+