From 4bdb5b47a5895c4584e41db95fa6b01027dec7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=EC=A0=95?= Date: Tue, 21 May 2024 09:36:32 +0900 Subject: [PATCH 1/3] =?UTF-8?q?1=EC=A3=BC=EC=B0=A8=20=EC=88=98=EC=97=85=20?= =?UTF-8?q?=EC=A7=84=EB=8F=84=20-=20=ED=8F=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=ED=99=94=EB=A9=B4=20=EB=B0=8F=20=ED=8F=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 +- src/api/index.ts | 14 ++++----- src/components/Header.tsx | 2 +- src/components/PostListItem.tsx | 9 +++++- src/pages/Home.tsx | 52 +++++++++++++++------------------ src/pages/Layout.tsx | 15 ++++++---- src/pages/Post.tsx | 47 ++++++++++++++++++++++++++++- 7 files changed, 96 insertions(+), 45 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 70925be7..030cc96e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,7 +16,7 @@ function App() { } /> } /> - {/*todo (3-3) Post 추가*/} + } /> {/*todo (5-1) Write 추가*/} diff --git a/src/api/index.ts b/src/api/index.ts index cfe4414c..80213cf2 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,25 +1,25 @@ -import axios from 'axios'; -import { IResponsePostList } from './types'; +import axios, { AxiosResponse } from 'axios'; +import { IPost, IResponsePostList } from './types'; const instance = axios.create({ headers: { 'Content-Type': 'application/json', }, - baseURL: '', + baseURL: 'http://34.64.250.51:8080/', }); // todo (6) api 작성 -export const getPostList = () => { - return null; +export const getPostList = (): Promise> => { + return instance.get('/posts'); }; export const createPost = () => { return null; }; -export const getPostById = () => { - return null; +export const getPostById = (id: string): Promise> => { + return instance.get(`/posts/${id}`); }; export const updatePostById = () => { diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 14a002f5..d5282641 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -28,7 +28,7 @@ const Header = () => {
-

kimsudal.log

+

aaaaaa

diff --git a/src/components/PostListItem.tsx b/src/components/PostListItem.tsx index d57920ca..57349f7d 100644 --- a/src/components/PostListItem.tsx +++ b/src/components/PostListItem.tsx @@ -23,7 +23,14 @@ const Contents = styled.p` `; const PostListItem = (props: IPost) => { - return
{/*todo (3-2) 게시글 목록 아이템 작성*/}
; + const { id, title, contents, tag } = props; + return ( + +

{title}

+ {contents} + #{tag} +
+ ); }; export default PostListItem; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 3e4f9110..ee7e53d1 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,38 +1,32 @@ import { useEffect, useState } from 'react'; import { getPostList } from '../api'; import PostListItem from '../components/PostListItem'; -import { IResponsePostList, TAG } from '../api/types'; +import { IResponsePostList } from '../api/types'; import NoPostList from '../components/NoPostList'; -const list = [ - { - post: { - id: 1, - title: '1번 게시글', - contents: '내용', - tag: TAG.REACT, - }, - }, - { - post: { - id: 2, - title: '2번 게시글', - contents: '내용', - tag: TAG.REACT, - }, - }, - { - post: { - id: 3, - title: '3번 게시글', - contents: '내용', - tag: TAG.REACT, - }, - }, -]; - const Home = () => { - return
{/*todo (3-1) post 목록 작성*/}
; + const [postList, setPostList] = useState([]); + + const fetchPostList = async () => { + const { data } = await getPostList(); + setPostList(data); + }; + + useEffect(() => { + fetchPostList(); + }, []); + + if (postList.length === 0) { + return ; + } + + return ( +
+ {postList.map(item => ( + + ))} +
+ ); }; export default Home; diff --git a/src/pages/Layout.tsx b/src/pages/Layout.tsx index d6ebd90e..019affac 100644 --- a/src/pages/Layout.tsx +++ b/src/pages/Layout.tsx @@ -58,16 +58,21 @@ const Layout = () => { return (
- {/*todo (1) 프로필 꾸미기*/}
- +
- 이름 - 설명 + 수정 + 2024 실전코딩
- +
); diff --git a/src/pages/Post.tsx b/src/pages/Post.tsx index 78faa049..d5efe71b 100644 --- a/src/pages/Post.tsx +++ b/src/pages/Post.tsx @@ -60,8 +60,53 @@ const Text = styled.p` `; const Post = () => { + const params = useParams(); + const { postId = '' } = params; + const [post, setPost] = useState(null); + + const fetchPostById = async (id: string) => { + const { data } = await getPostById(id); + setPost(data); + }; + + useEffect(() => { + if (postId) { + fetchPostById(postId); + } + }, []); + + if (!post) { + return ; + } + // todo (4) post 컴포넌트 작성 - return
; + return ( +
+
+ {post.title} + + +
n분전
+
+
+ {/*todo 수정/삭제 버튼 작성*/} + 수정 + 삭제 +
+
+ {post?.tag && ( + + #{post.tag} + + )} +
+ + {post.contents.split('\n').map((text, index) => ( + {text} + ))} + +
+ ); }; export default Post; From 41b782875587822d51d0e25a96cee175338d2d87 Mon Sep 17 00:00:00 2001 From: YoungMin Date: Sat, 1 Jun 2024 13:56:29 +0900 Subject: [PATCH 2/3] Commit1 --- .github/workflows/deploy.yml | 51 ++++++++++++++++++++++++ package-lock.json | 51 ++++++++++++++++++++++++ package.json | 2 + src/App.tsx | 29 ++++++++------ src/api/index.ts | 26 +++++++----- src/pages/Layout.tsx | 4 +- src/pages/Post.tsx | 40 +++++++++---------- src/pages/Resume.tsx | 20 +++++++++- src/pages/Write.tsx | 68 +++++++++++++++++++++++++++++--- src/queries/useCreatePost.ts | 16 ++++++++ src/queries/useDeletePostById.ts | 24 +++++++++++ src/queries/useGetPostById.ts | 17 ++++++++ src/queries/useGetPostList.ts | 16 ++++++++ src/queries/useUpdatePostById.ts | 16 ++++++++ vite.config.ts | 1 + 15 files changed, 332 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 src/queries/useCreatePost.ts create mode 100644 src/queries/useDeletePostById.ts create mode 100644 src/queries/useGetPostById.ts create mode 100644 src/queries/useGetPostList.ts create mode 100644 src/queries/useUpdatePostById.ts diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..b560501c --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,51 @@ +# GitHub Pages에 정적 콘텐츠를 배포하기 위한 간단한 워크플로우 +name: Deploy static content to Pages + +on: + # 기본 브랜치에 대한 푸시 이벤트 발생 시 실행 + push: + branches: ['main'] + + # Actions 탭에서 수동으로 워크플로우를 실행할 수 있도록 구성 + workflow_dispatch: + +# GITHUB_TOKEN의 권한을 설정하여 GitHub Pages에 배포할 수 있도록 함 +permissions: + contents: read + pages: write + id-token: write + +# 동시에 하나의 배포만 허용하도록 구성 +concurrency: + group: 'pages' + cancel-in-progress: true + +jobs: + # 단순히 배포만 수행하기에 하나의 잡으로만 구성 + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + - name: Install dependencies + run: npm ci + - name: Build + run: npm run build + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + # dist 디렉터리 업로드 + path: './dist' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e3d6053d..651faee6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.0", "dependencies": { "@emotion/styled": "^11.11.0", + "@tanstack/react-query": "^5.40.0", + "@tanstack/react-query-devtools": "^5.40.0", "axios": "^1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -1100,6 +1102,55 @@ "node": ">=14" } }, + "node_modules/@tanstack/query-core": { + "version": "5.40.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.40.0.tgz", + "integrity": "sha512-eD8K8jsOIq0Z5u/QbvOmfvKKE/XC39jA7yv4hgpl/1SRiU+J8QCIwgM/mEHuunQsL87dcvnHqSVLmf9pD4CiaA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.37.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.37.1.tgz", + "integrity": "sha512-XcG4IIHIv0YQKrexTqo2zogQWR1Sz672tX2KsfE9kzB+9zhx44vRKH5si4WDILE1PIWQpStFs/NnrDQrBAUQpg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.40.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.40.0.tgz", + "integrity": "sha512-iv/W0Axc4aXhFzkrByToE1JQqayxTPNotCoSCnarR/A1vDIHaoKpg7FTIfP3Ev2mbKn1yrxq0ZKYUdLEJxs6Tg==", + "dependencies": { + "@tanstack/query-core": "5.40.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.40.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.40.0.tgz", + "integrity": "sha512-JoQOQj/LKaHoHVMAh73R0pc4lIAHiZMV8L4DGHsTSvHcKi22LZmSC9aYBY9oMkqGpFtKmbspwrUIn55+czNSbA==", + "dependencies": { + "@tanstack/query-devtools": "5.37.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.40.0", + "react": "^18 || ^19" + } + }, "node_modules/@types/history": { "version": "4.7.11", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", diff --git a/package.json b/package.json index 73781edf..1eee78e3 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ }, "dependencies": { "@emotion/styled": "^11.11.0", + "@tanstack/react-query": "^5.40.0", + "@tanstack/react-query-devtools": "^5.40.0", "axios": "^1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/App.tsx b/src/App.tsx index 030cc96e..ba662e78 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,21 +6,28 @@ import Post from './pages/Post'; import Resume from './pages/Resume'; import Write from './pages/Write'; import Header from './components/Header'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; function App() { + const queryClient = new QueryClient(); + return ( - - - }> - }> - } /> - } /> + + + + + }> + }> + } /> + } /> + + } /> - } /> - - {/*todo (5-1) Write 추가*/} - - + } /> + + + ); } diff --git a/src/api/index.ts b/src/api/index.ts index 80213cf2..1e821816 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,5 +1,5 @@ import axios, { AxiosResponse } from 'axios'; -import { IPost, IResponsePostList } from './types'; +import { IPost, IResponsePostList, TAG } from './types'; const instance = axios.create({ headers: { @@ -8,24 +8,30 @@ const instance = axios.create({ baseURL: 'http://34.64.250.51:8080/', }); -// todo (6) api 작성 - export const getPostList = (): Promise> => { return instance.get('/posts'); }; -export const createPost = () => { - return null; +export const createPost = (title: string, contents: string, tag: TAG) => { + return instance.post('/posts', { + title, + contents, + tag, + }); }; export const getPostById = (id: string): Promise> => { return instance.get(`/posts/${id}`); }; -export const updatePostById = () => { - return null; +export const updatePostById = (id: string, title: string, contents: string, tag: TAG) => { + return instance.put(`/posts/${id}`, { + title, + contents, + tag, + }); }; -export const deletePostById = () => { - return null; -}; +export const deletePostById = (id: string) => { + return instance.delete(`/posts/${id}`); +}; \ No newline at end of file diff --git a/src/pages/Layout.tsx b/src/pages/Layout.tsx index 019affac..3994017a 100644 --- a/src/pages/Layout.tsx +++ b/src/pages/Layout.tsx @@ -60,12 +60,12 @@ const Layout = () => {
- 수정 + 조영민 2024 실전코딩
diff --git a/src/pages/Post.tsx b/src/pages/Post.tsx index d5efe71b..4469f8b0 100644 --- a/src/pages/Post.tsx +++ b/src/pages/Post.tsx @@ -1,10 +1,9 @@ -import { useEffect, useState } from 'react'; -import { useParams, Link, useNavigate } from 'react-router-dom'; +import { useParams, Link } from 'react-router-dom'; import styled from '@emotion/styled'; -import { deletePostById, getPostById } from '../api'; -import { IPost } from '../api/types'; import NotFound from '../components/NotFound'; import Tag from '../components/Tag'; +import useGetPostById from '../queries/useGetPostById.ts'; +import useDeletePostById from '../queries/useDeletePostById.ts'; const Title = styled.h1` font-size: 3rem; @@ -62,36 +61,37 @@ const Text = styled.p` const Post = () => { const params = useParams(); const { postId = '' } = params; - const [post, setPost] = useState(null); + const { data: post, isError, isLoading } = useGetPostById(postId); + const { mutate: deletePost } = useDeletePostById(); - const fetchPostById = async (id: string) => { - const { data } = await getPostById(id); - setPost(data); + const clickDeleteButton = () => { + const result = window.confirm('정말로 게시글을 삭제하시겠습니까?'); + if (result) { + deletePost({ postId }); + } }; - useEffect(() => { - if (postId) { - fetchPostById(postId); - } - }, []); + if (isLoading) { + return
...불러오는 중...
; + } - if (!post) { + if (!post || isError) { return ; } - // todo (4) post 컴포넌트 작성 return (
- {post.title} + {post?.title}
n분전
- {/*todo 수정/삭제 버튼 작성*/} - 수정 - 삭제 + + 수정 + + 삭제
{post?.tag && ( @@ -101,7 +101,7 @@ const Post = () => { )}
- {post.contents.split('\n').map((text, index) => ( + {post?.contents?.split('\n').map((text, index) => ( {text} ))} diff --git a/src/pages/Resume.tsx b/src/pages/Resume.tsx index 4f2c0051..51c2f4d5 100644 --- a/src/pages/Resume.tsx +++ b/src/pages/Resume.tsx @@ -1,5 +1,23 @@ const Resume = () => { - return
나는 프로젝트 내역
; + return

진행했던 수업

+
+

2023.09~2023.12

+
+ 웹프로그래밍 수강 +
+
+
+

2024.03~2024.06

+
+ 실전코딩 수강 +
+
+ +

자랑거리가 없네!

+
+
; + + }; export default Resume; diff --git a/src/pages/Write.tsx b/src/pages/Write.tsx index cbc05038..5d8f71f2 100644 --- a/src/pages/Write.tsx +++ b/src/pages/Write.tsx @@ -1,8 +1,10 @@ import { ChangeEvent, useEffect, useState } from 'react'; import { Link, useLocation, useNavigate } from 'react-router-dom'; import styled from '@emotion/styled'; -import { createPost, getPostById, updatePostById } from '../api'; import { TAG } from '../api/types'; +import useCreatePost from '../queries/useCreatePost.ts'; +import useUpdatePostById from '../queries/useUpdatePostById.ts'; +import useGetPostById from '../queries/useGetPostById.ts'; const TitleInput = styled.input` display: block; @@ -85,12 +87,68 @@ const SaveButton = styled.button` `; const Write = () => { - // todo (5) 게시글 작성 페이지 만들기 + const { state } = useLocation(); + const isEdit = state?.postId; + const navigate = useNavigate(); + const { data: post, isSuccess: isSuccessFetchPost } = useGetPostById(state?.postId); + const { mutate: createPost } = useCreatePost(); + const { mutate: updatePost } = useUpdatePostById(); + + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const [tag, setTag] = useState(TAG.REACT); + const tagList = Object.keys(TAG); + + const handleChangeTitle = (event: ChangeEvent) => { + setTitle(event.target.value); + }; + const handleChangeContent = (event: ChangeEvent) => { + setContent(event.target.value); + }; + + const handleChangeTag = (event: ChangeEvent) => { + setTag(event.target.value as TAG); + }; + + const clickConfirm = () => { + if (!title || !content) { + alert('빈 값이 있습니다.'); + return; + } + + if (isEdit) { + updatePost({ postId: state.postId, title, contents: content, tag }); + } else { + createPost({ title, contents: content, tag }); + } + navigate('/'); + }; + + useEffect(() => { + if (isSuccessFetchPost) { + setTitle(post.title); + setContent(post.contents); + setTag(post.tag); + } + }, [isSuccessFetchPost]); + return (
- 나는 글쓰기 -
{/*todo (5-2) 제목 / 태그 셀렉 / 내용 입력란 추가*/}
- {/*todo (5-3) 나가기, 저장하기 버튼 추가*/} +
+ + + {tagList.map(tag => { + return ; + })} + + +
+ + + 나가기 + + 저장하기 +
); }; diff --git a/src/queries/useCreatePost.ts b/src/queries/useCreatePost.ts new file mode 100644 index 00000000..80d2a506 --- /dev/null +++ b/src/queries/useCreatePost.ts @@ -0,0 +1,16 @@ +import { createPost } from '../api'; +import { useMutation } from '@tanstack/react-query'; +import { TAG } from '../api/types.ts'; + +const useCreatePost = () => { + const mutation = async ({ title, contents, tag }: { title: string; contents: string; tag: TAG }) => { + await createPost(title, contents, tag); + }; + + return useMutation({ + mutationKey: ['createPost'], + mutationFn: mutation, + }); +}; + +export default useCreatePost; \ No newline at end of file diff --git a/src/queries/useDeletePostById.ts b/src/queries/useDeletePostById.ts new file mode 100644 index 00000000..a7bb8401 --- /dev/null +++ b/src/queries/useDeletePostById.ts @@ -0,0 +1,24 @@ +import { deletePostById } from '../api'; +import { useMutation } from '@tanstack/react-query'; +import { useNavigate } from 'react-router-dom'; + +const useDeletePostById = () => { + const navigate = useNavigate(); + + const mutate = async ({ postId }: { postId: string }) => { + const { data } = await deletePostById(postId); + return data; + }; + + return useMutation({ + mutationFn: mutate, + onSuccess: () => { + navigate('/'); + }, + onError: () => { + alert('게시글 삭제에 실패하였습니다. 잠시 후 다시 시도해주세요.'); + }, + }); +}; + +export default useDeletePostById; \ No newline at end of file diff --git a/src/queries/useGetPostById.ts b/src/queries/useGetPostById.ts new file mode 100644 index 00000000..9ef408fc --- /dev/null +++ b/src/queries/useGetPostById.ts @@ -0,0 +1,17 @@ +import { getPostById } from '../api'; +import { useQuery } from '@tanstack/react-query'; + +const useGetPostById = (postId: string) => { + const fetcher = async () => { + const { data } = await getPostById(postId); + return data; + }; + + return useQuery({ + queryKey: ['getPostListById', postId], + queryFn: fetcher, + enabled: !!postId, + }); +}; + +export default useGetPostById; \ No newline at end of file diff --git a/src/queries/useGetPostList.ts b/src/queries/useGetPostList.ts new file mode 100644 index 00000000..df8e175c --- /dev/null +++ b/src/queries/useGetPostList.ts @@ -0,0 +1,16 @@ +import { getPostList } from '../api'; +import { useQuery } from '@tanstack/react-query'; + +const useGetPostList = () => { + const fetcher = async () => { + const { data } = await getPostList(); + return data; + }; + + return useQuery({ + queryKey: ['getPostList'], + queryFn: fetcher, + }); +}; + +export default useGetPostList; \ No newline at end of file diff --git a/src/queries/useUpdatePostById.ts b/src/queries/useUpdatePostById.ts new file mode 100644 index 00000000..1f0016e6 --- /dev/null +++ b/src/queries/useUpdatePostById.ts @@ -0,0 +1,16 @@ +import { updatePostById } from '../api'; +import { useMutation } from '@tanstack/react-query'; +import { TAG } from '../api/types.ts'; + +const useUpdatePostById = () => { + const mutation = async ({ postId, title, contents, tag }: { postId: string; title: string; contents: string; tag: TAG }) => { + await updatePostById(postId, title, contents, tag); + }; + + return useMutation({ + mutationKey: ['updatePost'], + mutationFn: mutation, + }); +}; + +export default useUpdatePostById; \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 627a3196..f49dabb1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,5 +3,6 @@ import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ + base: '/CNU_Blog/', plugins: [react()], }); From ee270f37a05aff904120911343dc4aabdf763617 Mon Sep 17 00:00:00 2001 From: 202002561 <162092934+202002561@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:40:01 +0900 Subject: [PATCH 3/3] Update deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b560501c..9ce71ea6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,4 +48,4 @@ jobs: path: './dist' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: actions/deploy-pages@v3