diff --git a/src/common/apis/blog.tsx b/src/common/apis/blog.tsx
index c10a69f..417ef83 100644
--- a/src/common/apis/blog.tsx
+++ b/src/common/apis/blog.tsx
@@ -8,5 +8,8 @@ export const getMemberPosts = (postListMemberRequest: PostListMemberRequest) =>
export const getMemberFolders = (username: string) =>
axiosApi.get(`/folders/${username}`);
+export const getMemberTags = (username: string) =>
+ axiosApi.get(`/tag/${username}`);
+
export const saveAndUpdateFolder = (folderRequestList: FolderRequest[]) =>
axiosApi.put("/folders", folderRequestList);
\ No newline at end of file
diff --git a/src/common/apis/post.tsx b/src/common/apis/post.tsx
index ff6a14c..83bc9c7 100644
--- a/src/common/apis/post.tsx
+++ b/src/common/apis/post.tsx
@@ -1,6 +1,17 @@
import axiosApi from "./AxiosApi.tsx";
-import {PostFolderRequest, PostListRequest, PostRequest, PostSearchRequest, PostUpdateRequest} from "../types/post.tsx";
-import {postListRequestToParameter, postListSearchRequestToParameter} from "../util/url.tsx";
+import {
+ PostFolderRequest,
+ PostListRequest,
+ PostListTagRequest,
+ PostRequest,
+ PostSearchRequest,
+ PostUpdateRequest
+} from "../types/post.tsx";
+import {
+ postListRequestToParameter,
+ postListSearchRequestToParameter,
+ postListTagRequestToParameter
+} from "../util/url.tsx";
export const createPost = async (postRequest: PostRequest) =>
await axiosApi.post("/post", postRequest);
@@ -20,5 +31,8 @@ export const getPost = async (id: string) =>
export const getPosts = async (postListRequest: PostListRequest) =>
await axiosApi.get(`/post${postListRequestToParameter(postListRequest)}`);
+export const getMemberTagPosts = async (postListTagRequest: PostListTagRequest) =>
+ await axiosApi.get(`/post/member/tag${postListTagRequestToParameter(postListTagRequest)}`);
+
export const searchPost = async (postSearchRequest: PostSearchRequest) =>
await axiosApi.get(`/post/search${postListSearchRequestToParameter(postSearchRequest)}`);
\ No newline at end of file
diff --git a/src/common/router/blogRouter.tsx b/src/common/router/blogRouter.tsx
index c5b6a3c..dd6f259 100644
--- a/src/common/router/blogRouter.tsx
+++ b/src/common/router/blogRouter.tsx
@@ -1,5 +1,5 @@
import {Suspense} from "react";
-import {Blog, Loading} from "./page.tsx";
+import {Blog, BlogTag, Loading} from "./page.tsx";
const blogRouter = () => {
@@ -8,6 +8,10 @@ const blogRouter = () => {
path: ':username',
element:
},
+ {
+ path: ':username/tag',
+ element:
+ },
];
}
diff --git a/src/common/router/page.tsx b/src/common/router/page.tsx
index 63bcab4..6b29228 100644
--- a/src/common/router/page.tsx
+++ b/src/common/router/page.tsx
@@ -28,3 +28,4 @@ export const Post = lazy(() => import('../../pages/post/PostPage.tsx'));
export const Write = lazy(() => import('../../pages/post/WritePage.tsx'));
export const Blog = lazy(() => import('../../pages/blog/BlogPage.tsx'));
+export const BlogTag = lazy(() => import('../../pages/blog/BlogTagPage.tsx'));
diff --git a/src/common/types/post.tsx b/src/common/types/post.tsx
index c13a32a..3398dc3 100644
--- a/src/common/types/post.tsx
+++ b/src/common/types/post.tsx
@@ -33,6 +33,13 @@ export interface PostSearchRequest {
isDescending: boolean,
}
+export interface PostListTagRequest {
+ username: string,
+ tagId: string,
+ page: number,
+ size: number,
+}
+
export interface PostListResponse {
content: PostResponse[];
page: PageResponse;
diff --git a/src/common/util/string.tsx b/src/common/util/string.tsx
index 6592c79..157db19 100644
--- a/src/common/util/string.tsx
+++ b/src/common/util/string.tsx
@@ -3,7 +3,7 @@ export const getFolderTitle = (title: string, depth: number) => {
if (depth === 1) {
prefix = "↳";
} else if (depth === 2) {
- prefix = "-";
+ prefix = "↳";
}
return `${prefix} ${title}`;
}
\ No newline at end of file
diff --git a/src/common/util/url.tsx b/src/common/util/url.tsx
index 3eec18c..0078556 100644
--- a/src/common/util/url.tsx
+++ b/src/common/util/url.tsx
@@ -1,4 +1,4 @@
-import {PostListRequest, PostSearchRequest} from "../types/post.tsx";
+import {PostListRequest, PostListTagRequest, PostSearchRequest} from "../types/post.tsx";
import {CommentListRequest} from "../types/comment.tsx";
import {PostListMemberRequest} from "../types/blog.tsx";
import {MemberProfileSearchRequest} from "../types/member.tsx";
@@ -39,6 +39,10 @@ export const postListSearchRequestToParameter = (postSearchRequest: PostSearchRe
return `?${sorts}&keyword=${postSearchRequest.keyword}&option=${postSearchRequest.option}&page=${postSearchRequest.page}&size=${postSearchRequest.size}&isDescending=${postSearchRequest.isDescending}`;
}
+export const postListTagRequestToParameter = (postListTagRequest: PostListTagRequest) => {
+ return `?username=${postListTagRequest.username}&tagId=${postListTagRequest.tagId}&page=${postListTagRequest.page}&size=${postListTagRequest.size}`;
+}
+
export const memberProfileSearchRequestToParameter = (memberProfileSearchRequest: MemberProfileSearchRequest) => {
return `?username=${memberProfileSearchRequest.username}&page=${memberProfileSearchRequest.page}&size=${memberProfileSearchRequest.size}`;
}
\ No newline at end of file
diff --git a/src/components/blog/BlogSideBar.tsx b/src/components/blog/BlogSideBar.tsx
index 8286839..4aebc2a 100644
--- a/src/components/blog/BlogSideBar.tsx
+++ b/src/components/blog/BlogSideBar.tsx
@@ -1,17 +1,18 @@
import {FolderType} from "../../common/types/blog.tsx";
import {useSelector} from "react-redux";
import {RootState} from "../../store.tsx";
-import {faker} from "@faker-js/faker/locale/ko";
import {OutlineLink} from "../common/OutlineButton.tsx";
import BlogTagCard from "./BlogTagCard.tsx";
import {getFolderTitle} from "../../common/util/string.tsx";
import ProfileImageCircle from "../common/ProfileImageCircle.tsx";
+import {TagResponse} from "../../common/types/post.tsx";
-function BlogSideBar({folders, username, profileUrl, addTag, setSelectedFolder, bgColor, side}: {
+function BlogSideBar({folders, tags, username, profileUrl, selectedFolder, setSelectedFolder, bgColor, side}: {
folders: FolderType[],
+ tags: TagResponse[],
username: string | undefined,
profileUrl: string | null,
- addTag: (tagName: string) => void,
+ selectedFolder: FolderType | null,
setSelectedFolder: (folder: FolderType) => void,
bgColor?: string,
side?: boolean,
@@ -38,28 +39,28 @@ function BlogSideBar({folders, username, profileUrl, addTag, setSelectedFolder,
폴더
태그
- {[Array.from({length: 24}).map(() => (
-
- ))]}
+ {tags.map((tag) => (
+
+ ))}
);
}
-function BlogFolderList({depth = 0, folders, setSelectedFolder}: {
+function BlogFolderList({depth = 0, folders, selectedFolder, setSelectedFolder}: {
depth?: number,
folders: FolderType[],
+ selectedFolder: FolderType | null,
setSelectedFolder: (folder: FolderType) => void,
}) {
@@ -83,16 +84,19 @@ function BlogFolderList({depth = 0, folders, setSelectedFolder}: {
{depth === 0 &&
}
)}
diff --git a/src/components/blog/BlogTagCard.tsx b/src/components/blog/BlogTagCard.tsx
index ac9c8e1..99f187e 100644
--- a/src/components/blog/BlogTagCard.tsx
+++ b/src/components/blog/BlogTagCard.tsx
@@ -1,14 +1,18 @@
import {TagResponse} from "../../common/types/post.tsx";
import {TextButton} from "../common/TextButton.tsx";
+import {useNavigate} from "react-router-dom";
-function BlogTagCard({tag, addTag}: {
+function BlogTagCard({tag, username}: {
tag: TagResponse,
- addTag: (tagName: string) => void,
+ username: string,
}) {
+
+ const navigate = useNavigate();
+
return (
addTag(tag.name)}
+ onClick={() => navigate(`/blog/${username}/tag`, {state: {tagId: tag.id}})}
addStyle={"!px-1 !py-0 hover:text-lime-700"}/>
);
}
diff --git a/src/pages/blog/BlogPage.tsx b/src/pages/blog/BlogPage.tsx
index 1224efc..6cb2885 100644
--- a/src/pages/blog/BlogPage.tsx
+++ b/src/pages/blog/BlogPage.tsx
@@ -8,10 +8,11 @@ import {FolderType, toFolderTypeList} from "../../common/types/blog.tsx";
import BlogSideBar from "../../components/blog/BlogSideBar.tsx";
import IconButton from "../../components/common/IconButton.tsx";
import {PageResponse} from "../../common/types/common.tsx";
-import {getMemberFolders, getMemberPosts} from "../../common/apis/blog.tsx";
-import {PostResponse} from "../../common/types/post.tsx";
+import {getMemberFolders, getMemberPosts, getMemberTags} from "../../common/apis/blog.tsx";
+import {PostResponse, TagResponse} from "../../common/types/post.tsx";
import {MemberProfileResponse} from "../../common/types/member.tsx";
import {getProfileByUsername} from "../../common/apis/member.tsx";
+import {TextLink} from "../../components/common/TextButton.tsx";
function BlogPage() {
@@ -28,10 +29,10 @@ function BlogPage() {
const [posts, setPosts] = useState([]);
const [member, setMember] = useState({username: username || "", profileUrl: null});
const [folders, setFolders] = useState([]);
+ const [tags, setTags] = useState([]);
const [isOpen, setIsOpen] = useState(false);
const [selectedFolder, setSelectedFolder] = useState(null);
- const [selectedTagList, setSelectedTagList] = useState([]);
const sideBarRef = useRef(null);
@@ -44,10 +45,6 @@ function BlogPage() {
}
};
- const addTag = (selectTag: string) => {
- setSelectedTagList([...selectedTagList, selectTag]);
- }
-
const handlePage = (page: number) => {
setFolderParam({...folderParam, "page": page.toString()});
setPage(page);
@@ -128,6 +125,15 @@ function BlogPage() {
.then(res => {
const folders = toFolderTypeList(res.data);
setFolders(folders);
+ setFolders(prev => [
+ {
+ id: "",
+ title: "전체",
+ postCount: 0,
+ subFolders: []
+ },
+ ...prev,
+ ])
setPage(Number(folderParam.get("page")) || 0);
@@ -140,6 +146,12 @@ function BlogPage() {
setSelectedFolder(initialSelectedFolder);
}
});
+
+ getMemberTags(username)
+ .then(res => {
+ setTags(res.data);
+ })
+ .catch(error => alert(error.response.data.message));
}, [username]);
useEffect(() => {
@@ -164,15 +176,18 @@ function BlogPage() {
-
-
{username}의 블로그
+
+
}
onClick={handleMenuOpen}
addStyle="lg:hidden"/>
-
@@ -204,9 +220,10 @@ function BlogPage() {
diff --git a/src/pages/blog/BlogTagPage.tsx b/src/pages/blog/BlogTagPage.tsx
new file mode 100644
index 0000000..164345f
--- /dev/null
+++ b/src/pages/blog/BlogTagPage.tsx
@@ -0,0 +1,101 @@
+import BasicLayout from "../../layout/BasicLayout.tsx";
+import {useLocation, useNavigate, useParams} from "react-router-dom";
+import {useEffect, useState} from "react";
+import {PostResponse, TagResponse} from "../../common/types/post.tsx";
+import {PageResponse} from "../../common/types/common.tsx";
+import {getMemberTags} from "../../common/apis/blog.tsx";
+import TagCard from "../../components/post/TagCard.tsx";
+import PostCard from "../../components/post/PostCard.tsx";
+import {getMemberTagPosts} from "../../common/apis/post.tsx";
+import PaginationButton from "../../components/common/PaginationButton.tsx";
+import {TextLink} from "../../components/common/TextButton.tsx";
+
+function BlogTagPage() {
+
+ const {username} = useParams();
+ const navigate = useNavigate();
+ const {state} = useLocation();
+ const {tagId} = state;
+
+
+ const [tags, setTags] = useState([]);
+ const [posts, setPosts] = useState([]);
+ const [selectedTag, setSelectedTag] = useState({
+ id: tagId || "",
+ name: "",
+ });
+
+ const [page, setPage] = useState(0);
+ const [pageInfo, setPageInfo] = useState({
+ number: 0, size: 3, totalPages: 0, totalElements: 0,
+ });
+
+ const handleSelectedTag = (tag: TagResponse) => {
+ setSelectedTag(tag);
+ setPage(0);
+ }
+
+ useEffect(() => {
+ if (!username) {
+ alert("잘못된 url 경로입니다.");
+ navigate(-1);
+ return;
+ }
+
+ getMemberTags(username)
+ .then(res => {
+ const tags: TagResponse[] = res.data;
+ setTags(tags);
+ setSelectedTag(tags.find(tag => tag.id === tagId) || null);
+ })
+ .catch(error => alert(error.response.data.message));
+ }, []);
+
+ useEffect(() => {
+ if (!selectedTag) {
+ return;
+ }
+
+ if (typeof username !== "string") {
+ return;
+ }
+
+ getMemberTagPosts({
+ username: username,
+ tagId: selectedTag.id,
+ page: page,
+ size: pageInfo.size,
+ })
+ .then(res => {
+ setPosts(res.data.content);
+ setPageInfo(res.data.page);
+ })
+ .catch(error => alert(error.response.data.message));
+ }, [selectedTag, page]);
+
+ return (
+
+
+
+
+ {tags.map(tag =>
+ handleSelectedTag(tag)}/>)}
+
+
+ {selectedTag && "{selectedTag.name}"}
+ {pageInfo.totalElements}개의 게시글
+
+
+ {posts.map(post =>
)}
+
+
+
+
+ );
+}
+
+export default BlogTagPage;
\ No newline at end of file
diff --git a/src/pages/post/WritePage.tsx b/src/pages/post/WritePage.tsx
index 2bd4787..f31ede4 100644
--- a/src/pages/post/WritePage.tsx
+++ b/src/pages/post/WritePage.tsx
@@ -169,6 +169,20 @@ function WritePage() {
}, []);
useEffect(() => {
+ getMemberFolders(loginState.username)
+ .then(res => {
+ setFolders(toFolderTypeList(res.data));
+ setFolders(prev => [
+ {
+ id: "empty",
+ title: "폴더 없음",
+ postCount: 0,
+ subFolders: [],
+ },
+ ...prev
+ ]);
+ });
+
if (!path.endsWith("edit")) {
return;
}
@@ -195,20 +209,6 @@ function WritePage() {
});
})
.catch((error) => alert(error.response.data.message));
-
- getMemberFolders(loginState.username)
- .then(res => {
- setFolders(toFolderTypeList(res.data));
- setFolders(prev => [
- {
- id: "empty",
- title: "폴더 없음",
- postCount: 0,
- subFolders: [],
- },
- ...prev
- ]);
- });
}, []);
useEffect(() => {
@@ -220,7 +220,7 @@ function WritePage() {
return (
-
+
-1;
}
- const getTargetFolderId = (targetFolder : FolderType | null) => {
+ const getTargetFolderId = (targetFolder: FolderType | null) => {
if (!targetFolder || targetFolder.id === "empty") {
return null;
}
@@ -65,7 +66,12 @@ function PostSettingPage() {
return targetFolder.id;
}
const submitPostFolderUpdate = () => {
- if (!confirm("변경사항을 저장하시겠습니까?")) {
+ if (!targetFolder) {
+ alert("폴더를 선택해주세요.");
+ return;
+ }
+
+ if (!confirm(`선택한 게시글을 ${targetFolder.title}로 이동하시겠습니까?`)) {
return;
}
@@ -75,6 +81,17 @@ function PostSettingPage() {
})
.then(() => {
alert("폴더가 수정되었습니다.");
+ getMemberPosts({
+ username: loginState.username,
+ folderIds: [],
+ page: page,
+ size: pageInfo.size,
+ })
+ .then((res) => {
+ setPosts([...res.data.content]);
+ setPageInfo(res.data.page);
+ })
+ .catch((error) => alert(error.response.data.message));
setIsFolderEdit(false);
})
.catch((error) => alert(error.response.data.message));
@@ -137,6 +154,10 @@ function PostSettingPage() {
{post.title}
{fullDateToKorean(post.createdAt)}
+
+
+ {post.folder ? post.folder.title : "폴더 없음"}
+
{!isFolderEdit &&
diff --git a/src/pages/setting/ProfileSettingPage.tsx b/src/pages/setting/ProfileSettingPage.tsx
index 7209f2f..253072b 100644
--- a/src/pages/setting/ProfileSettingPage.tsx
+++ b/src/pages/setting/ProfileSettingPage.tsx
@@ -1,7 +1,7 @@
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "../../store.tsx";
import {MdOutlineEdit} from "react-icons/md";
-import {ChangeEvent, useEffect, useRef, useState} from "react";
+import {ChangeEvent, useRef, useState} from "react";
import {FillButton} from "../../components/common/FillButton.tsx";
import {setUsername} from "../../common/slices/loginSlice.tsx";
import {TextButton} from "../../components/common/TextButton.tsx";
@@ -75,20 +75,13 @@ function ProfileSettingPage() {
.catch((error) => alert(error.response.data.message));
}
- useEffect(() => {
- if (!loginState.isLogin) {
- alert("로그인이 필요한 페이지입니다.");
- navigate("/login");
- }
- }, []);
-
return (
프로필 관리
-
+
{isImageEdit && image
?
![]()