diff --git a/client/src/components/Navigation/NavigationBar.tsx b/client/src/components/Navigation/NavigationBar.tsx
index fef2927..122b669 100644
--- a/client/src/components/Navigation/NavigationBar.tsx
+++ b/client/src/components/Navigation/NavigationBar.tsx
@@ -5,8 +5,15 @@ import HomeIcon from "~/assets/icons/HomeIcon.svg";
import SearchIcon from "~/assets/icons/SearchIcon.svg";
import PostIcon from "~/assets/icons/PostIcon.svg";
import BeverageIcon from "~/assets/icons/BeverageIcon.svg";
+import { useGlobalNavbarVisibility } from "@/store/useGlobalNavbarVisibility";
-import HOME, { MY_PROFILE, NEW_POST, SEARCH, SIGNIN, WIKI } from "@/const/clientPath";
+import HOME, {
+ MY_PROFILE,
+ NEW_POST,
+ SEARCH,
+ SIGNIN,
+ WIKI,
+} from "@/const/clientPath";
import Link from "next/link";
import { usePathname } from "next/navigation";
import NavbarUserImage from "@/components/Navigation/NavbarUserImage";
@@ -16,6 +23,9 @@ import { useMyInfoQuery } from "@/queries/auth/useMyInfoQuery";
const NavigationBar = () => {
const path = usePathname();
const { data: userInfo } = useMyInfoQuery();
+
+ const isVisible = useGlobalNavbarVisibility(({ isVisible }) => isVisible);
+
const NavbarData = useMemo(
() => [
{
@@ -45,35 +55,32 @@ const NavigationBar = () => {
],
[userInfo]
);
- return (
-
-
- {NavbarData.map(({ label, href, iconComponent, ...others }) => {
- return (
-
- );
- })}
-
-
+ return isVisible ? (
+
+ {NavbarData.map(({ label, href, iconComponent, ...others }) => {
+ return (
+
+ );
+ })}
+
+ ) : (
+ <>>
);
};
-const WrapperStyle = {
+const BtnStyle = {
position: "fixed",
bottom: 0,
left: 0,
right: 0,
- borderRadius: 0,
-};
-const BtnStyle = {
borderRadius: "12px 12px 0 0",
border: "1px solid",
borderBottom: "none",
diff --git a/client/src/components/post/detail/PostCommentDropdown.tsx b/client/src/components/post/detail/PostCommentDropdown.tsx
new file mode 100644
index 0000000..734c0c4
--- /dev/null
+++ b/client/src/components/post/detail/PostCommentDropdown.tsx
@@ -0,0 +1,38 @@
+import { ButtonBase, Menu, MenuItem } from "@mui/material";
+import { MoreVertOutlined } from "@mui/icons-material";
+import { useState } from "react";
+
+const PostCommentDropdown = () => {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+
+ const handleClick = (event: React.MouseEvent) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
+export default PostCommentDropdown;
diff --git a/client/src/components/post/detail/PostCommentInput.tsx b/client/src/components/post/detail/PostCommentInput.tsx
new file mode 100644
index 0000000..328eafe
--- /dev/null
+++ b/client/src/components/post/detail/PostCommentInput.tsx
@@ -0,0 +1,83 @@
+"use client";
+import { InputAdornment, Paper, TextField } from "@mui/material";
+import { useCallback, useContext, useState } from "react";
+import SubmitCommentIcon from "@/assets/icons/comment/SubmitCommentIcon.svg";
+import { useGlobalNavbarVisibility } from "@/store/useGlobalNavbarVisibility";
+import PostDetailPageContext from "@/store/post/PostDetailPageContext";
+import useNewPostCommentMutation from "@/queries/post/comment/useNewPostCommentMutation";
+
+const PostCommentInput = () => {
+ const setIsShowingNavbar = useGlobalNavbarVisibility(
+ ({ setIsVisible }) => setIsVisible
+ );
+ const { data: currentData } = useContext(PostDetailPageContext);
+ const [isEditing, setIsEditing] = useState(false);
+ const [inputValue, setInputValue] = useState("");
+
+ const { mutateAsync: submitForm } = useNewPostCommentMutation(
+ currentData?.postNo ? String(currentData?.postNo) : undefined
+ );
+ const submitHandler = useCallback(
+ (content: string) => {
+ submitForm(content).then(() => {
+ setInputValue("");
+ });
+ },
+ [submitForm, setInputValue]
+ );
+
+ return (
+
+ {
+ setIsShowingNavbar(false);
+ setIsEditing(true);
+ }}
+ onBlur={() => {
+ setIsShowingNavbar(true);
+ setIsEditing(false);
+ }}
+ size="small"
+ autoComplete="off"
+ placeholder="회원님의 생각을 올려보세요"
+ multiline
+ value={inputValue}
+ onChange={(e) => setInputValue(e.target.value)}
+ rows={isEditing ? 5 : 1}
+ InputProps={{
+ endAdornment: (
+ {
+ e.stopPropagation();
+ submitHandler(inputValue)
+ }}
+ sx={{
+ color: inputValue.length > 0 ? "primary.main" : "text.disabled",
+ }}
+ >
+
+
+ ),
+ }}
+ sx={{ backgroundColor: "background.paper" }}
+ />
+
+ );
+};
+
+export default PostCommentInput;
diff --git a/client/src/const/serverPath.ts b/client/src/const/serverPath.ts
index fe1135f..15aa942 100644
--- a/client/src/const/serverPath.ts
+++ b/client/src/const/serverPath.ts
@@ -30,6 +30,10 @@ export const LOGOUT_BFF = "/api/auth/logout-internal" as const;
* 게시물리스트를 받아오거나, 작성하는 Path
*/
export const POST_LIST = "/posts" as const;
+/**
+ * 게시물 pk 를 입력받아 댓글을 조회,생성 하는 URL
+ */
+export const POST_COMMENT = (pk:string) => `${POST_LIST}/${pk}/comments`
/**
* 게시물리스트를 받아오거나, 작성하는 Path 버전2 (Breaking Change)
@@ -39,7 +43,11 @@ export const POST_LIST_V2 = "/posts/v2" as const;
* ID(pk) 를 입력받아 해당 포스트를 지우는 URL
*/
export const REMOVE_POST = (pk: number) => `${POST_LIST}/${pk}` as const;
-
+/**
+ * 포스트의 PK를 입력받아 해당 PK의 게시글의 좋아요를 요청
+ * @param id 게시글의 PK
+ */
+export const POST_LIKE_URL = (id: string) => `/posts/like/${id}` as const;
/**
*
* @param type : 리소스의 타입 POST|PROFILE|ALCOHOL
@@ -63,11 +71,7 @@ export const REMOVE_FILE = (attachNo: string) => `/attach/${attachNo}` as const;
*/
export const GET_ALCOHOL_LIST = "/alcohols" as const;
-/**
- * 포스트의 PK를 입력받아 해당 PK의 게시글의 좋아요를 요청
- * @param id 게시글의 PK
- */
-export const POST_LIKE_URL = (id: string) => `/posts/like/${id}` as const;
+
/**
* 포스트의 PK를 입력받아 해당 PK의 게시글의 좋아요 취소를 요청
diff --git a/client/src/queries/post/comment/useGetCommentQuery.ts b/client/src/queries/post/comment/useGetCommentQuery.ts
new file mode 100644
index 0000000..a1d8704
--- /dev/null
+++ b/client/src/queries/post/comment/useGetCommentQuery.ts
@@ -0,0 +1,29 @@
+import { POST_COMMENT } from "@/const/serverPath";
+import axios from "@/libs/axios";
+import PostCommentListInterface from "@/types/post/PostCommentInterface";
+import { useSuspenseQuery } from "@tanstack/react-query";
+
+interface CommentQueryInterface {
+ postNo: string;
+}
+
+const useGetCommentQuery = ({ postNo }: CommentQueryInterface) => {
+ return useSuspenseQuery({
+ queryKey: commentQueryKey.byId(postNo),
+ queryFn: async () => await getCommentListQueryFn(postNo),
+ });
+};
+
+export const getCommentListQueryFn = async (
+ id: CommentQueryInterface["postNo"]
+) => {
+ const { data } = await axios.get(POST_COMMENT(id));
+ return data;
+};
+
+export const commentQueryKey = {
+ all: ["comment"] as const,
+ byId: (id?: string) => ["comment", { id }] as const,
+};
+
+export default useGetCommentQuery;
diff --git a/client/src/queries/post/comment/useNewPostCommentMutation.ts b/client/src/queries/post/comment/useNewPostCommentMutation.ts
new file mode 100644
index 0000000..789bfe9
--- /dev/null
+++ b/client/src/queries/post/comment/useNewPostCommentMutation.ts
@@ -0,0 +1,71 @@
+import { POST_COMMENT } from "@/const/serverPath";
+import useAxiosPrivate from "@/hooks/useAxiosPrivate";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { commentQueryKey } from "./useGetCommentQuery";
+import PostCommentListInterface from "@/types/post/PostCommentInterface";
+import { useErrorHandler } from "@/utils/errorHandler";
+import { useMyInfoQuery } from "@/queries/auth/useMyInfoQuery";
+
+const useNewPostCommentMutation = (id?: string) => {
+ const queryClient = useQueryClient();
+ const errorHandler = useErrorHandler();
+ const { data: myInfo } = useMyInfoQuery();
+
+ return useMutation({
+ mutationFn: async (content: string) => {
+ if (!id) {
+ return Promise.reject("id가 제공되지않음");
+ } else return postComment(id, content);
+ },
+ onMutate: (content) => {
+ queryClient.cancelQueries({ queryKey: commentQueryKey.byId(id) });
+
+ const querySnapShot = queryClient.getQueryData(
+ commentQueryKey.byId(id)
+ );
+
+ queryClient.setQueryData(
+ commentQueryKey.byId(id),
+ (prev) => {
+ return {
+ list: [
+ {
+ commentNo: Number.MAX_SAFE_INTEGER,
+ commentContent: content,
+ createdDate: String(new Date()),
+ lastModifiedDate: String(new Date()),
+ createdBy: myInfo?.nickname,
+ userId: myInfo?.id,
+ nickname: myInfo?.nickname,
+ profileImgUrls: myInfo?.profileImages,
+ },
+ ...(prev?.list ?? []),
+ ] as PostCommentListInterface["list"],
+ totalCount: (prev?.totalCount ?? 0) + 1,
+ };
+ }
+ );
+ return { querySnapShot };
+ },
+ onError: (err, queryFnParams, context) => {
+ errorHandler(err);
+ queryClient.setQueryData(
+ commentQueryKey.byId(queryFnParams),
+ context?.querySnapShot
+ );
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: commentQueryKey.byId(id) });
+ },
+ });
+};
+export const postComment = async (postNo: string, content: string) => {
+ const axiosPrivate = useAxiosPrivate();
+ const { data } = await axiosPrivate.post<{ commentNo: string }>(
+ POST_COMMENT(postNo),
+ { commentContent: content }
+ );
+ return data;
+};
+
+export default useNewPostCommentMutation;