From 2a4b61493b008e38777220ee8a5d30946969e5ae Mon Sep 17 00:00:00 2001 From: yoonseo choi Date: Thu, 19 Sep 2024 16:55:17 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=EB=82=98=EC=9D=98=20=EB=94=94=EC=8A=A4?= =?UTF-8?q?=EC=BB=A4=EC=85=98=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84=20(issue=20#469)=20(#471)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 나의 디스커션 목록 조회 API 구현 * refactor: swagger description 수정 --- backend/secrets | 2 +- .../main/java/develup/api/DiscussionApi.java | 8 ++++ .../discussion/DiscussionService.java | 10 ++++- .../discussion/DiscussionRepository.java | 13 ++++++- .../java/develup/api/DiscussionApiTest.java | 24 ++++++++++++ .../discussion/DiscussionServiceTest.java | 18 +++++++++ .../discussion/DiscussionRepositoryTest.java | 39 +++++++++++++++++-- 7 files changed, 108 insertions(+), 6 deletions(-) diff --git a/backend/secrets b/backend/secrets index f362f7de..870d4711 160000 --- a/backend/secrets +++ b/backend/secrets @@ -1 +1 @@ -Subproject commit f362f7de6ffd220b7a9347945476e057aa0523ed +Subproject commit 870d471133ea7aa76b9e415c27c5e4d7c05dbe58 diff --git a/backend/src/main/java/develup/api/DiscussionApi.java b/backend/src/main/java/develup/api/DiscussionApi.java index 4b4d6fa6..70eb9f98 100644 --- a/backend/src/main/java/develup/api/DiscussionApi.java +++ b/backend/src/main/java/develup/api/DiscussionApi.java @@ -58,4 +58,12 @@ public ResponseEntity> createDiscussion( return ResponseEntity.ok(new ApiResponse<>(response)); } + + @GetMapping("/discussions/mine") + @Operation(summary = "나의 디스커션 목록 조회 API", description = "내가 작성한 디스커션 목록을 조회합니다.") + public ResponseEntity>> getMyDiscussions(@Auth Accessor accessor) { + List response = discussionService.getDiscussionsByMemberId(accessor.id()); + + return ResponseEntity.ok(new ApiResponse<>(response)); + } } diff --git a/backend/src/main/java/develup/application/discussion/DiscussionService.java b/backend/src/main/java/develup/application/discussion/DiscussionService.java index 201b7da9..38fce4ee 100644 --- a/backend/src/main/java/develup/application/discussion/DiscussionService.java +++ b/backend/src/main/java/develup/application/discussion/DiscussionService.java @@ -52,7 +52,15 @@ public DiscussionResponse create(Long memberId, CreateDiscussionRequest request) } public List getSummaries(String mission, String hashTagName) { - return discussionRepository.findByMissionAndHashTagName(mission, hashTagName).stream() + return discussionRepository.findAllByMissionAndHashTagName(mission, hashTagName).stream() + .map(SummarizedDiscussionResponse::from) + .toList(); + } + + public List getDiscussionsByMemberId(Long memberId) { + List myDiscussions = discussionRepository.findAllByMember_Id(memberId); + + return myDiscussions.stream() .map(SummarizedDiscussionResponse::from) .toList(); } diff --git a/backend/src/main/java/develup/domain/discussion/DiscussionRepository.java b/backend/src/main/java/develup/domain/discussion/DiscussionRepository.java index 48d34a00..140416b4 100644 --- a/backend/src/main/java/develup/domain/discussion/DiscussionRepository.java +++ b/backend/src/main/java/develup/domain/discussion/DiscussionRepository.java @@ -25,7 +25,7 @@ public interface DiscussionRepository extends JpaRepository { AND sht.name = :hashTag )) """) - List findByMissionAndHashTagName( + List findAllByMissionAndHashTagName( @Param("mission") String mission, @Param("hashTag") String hashTagName ); @@ -41,4 +41,15 @@ List findByMissionAndHashTagName( WHERE d.id = :id """) Optional findFetchById(Long id); + + @Query(""" + SELECT d + FROM Discussion d + JOIN FETCH d.mission m + JOIN FETCH d.member me + JOIN FETCH d.discussionHashTags.hashTags dhts + JOIN FETCH dhts.hashTag ht + WHERE me.id = :memberId + """) + List findAllByMember_Id(Long memberId); } diff --git a/backend/src/test/java/develup/api/DiscussionApiTest.java b/backend/src/test/java/develup/api/DiscussionApiTest.java index e30f8e3f..bb854067 100644 --- a/backend/src/test/java/develup/api/DiscussionApiTest.java +++ b/backend/src/test/java/develup/api/DiscussionApiTest.java @@ -100,6 +100,30 @@ void getSolution() throws Exception { .andExpect(jsonPath("$.data.mission.url", equalTo("https://github.com/develup-mission/java-smoking"))); } + @Test + @DisplayName("나의 디스커션 목록을 조회한다.") + void getMyDiscussions() throws Exception { + List myDiscussions = List.of( + SummarizedDiscussionResponse.from(createDiscussion()), + SummarizedDiscussionResponse.from(createDiscussion()) + ); + + BDDMockito.given(discussionService.getDiscussionsByMemberId(any())) + .willReturn(myDiscussions); + + mockMvc.perform(get("/discussions/mine")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data[0].id", equalTo(1))) + .andExpect(jsonPath("$.data[0].title", equalTo("루터회관 흡연단속 구현에 대한 고찰"))) + .andExpect(jsonPath("$.data[0].mission", equalTo("루터회관 흡연단속"))) + .andExpect(jsonPath("$.data[0].hashTags[0].id", equalTo(1))) + .andExpect(jsonPath("$.data[0].hashTags[0].name", equalTo("JAVA"))) + .andExpect(jsonPath("$.data[0].member.id", equalTo(1))) + .andExpect(jsonPath("$.data[0].member.name", equalTo("tester"))) + .andExpect(jsonPath("$.data[0].commentCount", equalTo(100))); + } + private Discussion createDiscussion() { HashTag hashTag = HashTagTestData.defaultHashTag() .withId(1L) diff --git a/backend/src/test/java/develup/application/discussion/DiscussionServiceTest.java b/backend/src/test/java/develup/application/discussion/DiscussionServiceTest.java index 48d1b0c3..29e116d5 100644 --- a/backend/src/test/java/develup/application/discussion/DiscussionServiceTest.java +++ b/backend/src/test/java/develup/application/discussion/DiscussionServiceTest.java @@ -179,6 +179,24 @@ void getById() { .hasMessage("존재하지 않는 디스커션입니다."); } + @Test + @DisplayName("나의 디스커션 리스트를 조회한다.") + @Transactional + void getDiscussionsByMemberId() { + Member member = memberRepository.save(MemberTestData.defaultMember().withId(1L).build()); + Mission mission = missionRepository.save(MissionTestData.defaultMission().build()); + HashTag hashTag = hashTagRepository.save(HashTagTestData.defaultHashTag().build()); + Discussion discussion = DiscussionTestData.defaultDiscussion() + .withMember(member) + .withMission(mission) + .withHashTags(List.of(hashTag)) + .build(); + + discussionRepository.save(discussion); + + assertThat(discussionService.getDiscussionsByMemberId(member.getId())).hasSize(1); + } + private void createDiscussion(Mission mission, HashTag hashTag) { Member member = memberRepository.save(MemberTestData.defaultMember().build()); diff --git a/backend/src/test/java/develup/domain/discussion/DiscussionRepositoryTest.java b/backend/src/test/java/develup/domain/discussion/DiscussionRepositoryTest.java index 01338685..065251ba 100644 --- a/backend/src/test/java/develup/domain/discussion/DiscussionRepositoryTest.java +++ b/backend/src/test/java/develup/domain/discussion/DiscussionRepositoryTest.java @@ -1,6 +1,7 @@ package develup.domain.discussion; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import java.util.List; import java.util.function.Function; @@ -44,7 +45,7 @@ void findAllDiscussion() { createDiscussion(mission, hashTag); createDiscussion(mission, hashTag); - List actual = discussionRepository.findByMissionAndHashTagName( + List actual = discussionRepository.findAllByMissionAndHashTagName( "all", "all" ); @@ -62,7 +63,7 @@ void findAllDiscussionByHashTag() { createDiscussion(mission, hashTag1); createDiscussion(mission, hashTag2); - List discussions = discussionRepository.findByMissionAndHashTagName( + List discussions = discussionRepository.findAllByMissionAndHashTagName( "all", "JAVA" ); @@ -84,7 +85,7 @@ void findAllDiscussionByMission() { createDiscussion(mission1, hashTag); createDiscussion(mission2, hashTag); - List discussions = discussionRepository.findByMissionAndHashTagName( + List discussions = discussionRepository.findAllByMissionAndHashTagName( "루터회관 흡연단속", "all" ); @@ -113,6 +114,38 @@ void findFetchById() { .hasValue(savedDiscussion.getId()); } + @Test + @DisplayName("멤버 식별자를 통해 디스커션을 조회한다.") + @Transactional + void findByMember_Id() { + Member member1 = memberRepository.save(MemberTestData.defaultMember().withId(1L).build()); + Member member2 = memberRepository.save(MemberTestData.defaultMember().withId(2L).build()); + Mission mission = missionRepository.save(MissionTestData.defaultMission().build()); + HashTag hashTag = hashTagRepository.save(HashTagTestData.defaultHashTag().build()); + + Discussion discussionByMember1 = DiscussionTestData.defaultDiscussion() + .withMission(mission) + .withMember(member1) + .withHashTags(List.of(hashTag)) + .build(); + Discussion discussionByMember2 = DiscussionTestData.defaultDiscussion() + .withMission(mission) + .withMember(member2) + .withHashTags(List.of(hashTag)) + .build(); + + discussionRepository.save(discussionByMember1); + discussionRepository.save(discussionByMember2); + + List discussionsByMember1 = discussionRepository.findAllByMember_Id(member1.getId()); + List discussionsByMember2 = discussionRepository.findAllByMember_Id(member2.getId()); + + assertAll( + () -> assertThat(discussionsByMember1).hasSize(1), + () -> assertThat(discussionsByMember2).hasSize(1) + ); + } + private void createDiscussion(Mission mission, HashTag hashTag) { Member member = memberRepository.save(MemberTestData.defaultMember().build()); From 61fb443832c03c1a4a0477acab41b785519aec18 Mon Sep 17 00:00:00 2001 From: Minji <121149171+chosim-dvlpr@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:41:50 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=EB=94=94=EC=8A=A4=EC=BB=A4=EC=85=98=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84=20(issue=20#43?= =?UTF-8?q?1)=20(#470)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 디스커션 리스트 구현 (디자인, mock 데이터) * refactor: 불필요한 guide 페이지 주석처리 및 Suspense 적용 * feat: header 디스커션 메뉴 추가 * design: 디스커션 리스트 margin 추가 * feat: 디스커션 리스트 API 연결 --------- Co-authored-by: JEON TAEHEON --- frontend/src/apis/discussionAPI.ts | 12 +++ frontend/src/apis/paths.ts | 1 + .../DiscussionList/DiscussionList.styled.ts | 63 ++++++++++++ .../DiscussionList/DiscussionListItem.tsx | 34 +++++++ .../DiscussionList/discussionsMock.json | 96 +++++++++++++++++++ .../src/components/DiscussionList/index.tsx | 25 +++++ .../src/components/Header/Header.styled.ts | 2 +- frontend/src/components/Header/index.tsx | 1 + frontend/src/hooks/queries/keys.ts | 4 + frontend/src/hooks/useDiscussions.ts | 13 +++ frontend/src/index.tsx | 43 ++++++--- .../DiscussionListPage.styled.ts | 14 +++ .../src/pages/DiscussionListPage/index.tsx | 11 +++ frontend/src/types/index.ts | 9 ++ 14 files changed, 315 insertions(+), 13 deletions(-) create mode 100644 frontend/src/apis/discussionAPI.ts create mode 100644 frontend/src/components/DiscussionList/DiscussionList.styled.ts create mode 100644 frontend/src/components/DiscussionList/DiscussionListItem.tsx create mode 100644 frontend/src/components/DiscussionList/discussionsMock.json create mode 100644 frontend/src/components/DiscussionList/index.tsx create mode 100644 frontend/src/hooks/useDiscussions.ts create mode 100644 frontend/src/pages/DiscussionListPage/DiscussionListPage.styled.ts create mode 100644 frontend/src/pages/DiscussionListPage/index.tsx diff --git a/frontend/src/apis/discussionAPI.ts b/frontend/src/apis/discussionAPI.ts new file mode 100644 index 00000000..dbffc275 --- /dev/null +++ b/frontend/src/apis/discussionAPI.ts @@ -0,0 +1,12 @@ +import type { Discussion } from '@/types'; +import { develupAPIClient } from './clients/develupClient'; +import { PATH } from './paths'; + +export const getDiscussions = async (mission = 'all', hashTag = 'all'): Promise => { + const { data } = await develupAPIClient.get<{ data: Discussion[] }>(`${PATH.discussions}`, { + mission, + hashTag, + }); + + return data; +}; diff --git a/frontend/src/apis/paths.ts b/frontend/src/apis/paths.ts index 477854ab..66cbd10b 100644 --- a/frontend/src/apis/paths.ts +++ b/frontend/src/apis/paths.ts @@ -15,6 +15,7 @@ export const PATH = { missionInProgress: '/missions/in-progress', solutions: '/solutions', mySolutions: '/solutions/mine', + discussions: '/discussions', }; export const PATH_FORMATTER = { diff --git a/frontend/src/components/DiscussionList/DiscussionList.styled.ts b/frontend/src/components/DiscussionList/DiscussionList.styled.ts new file mode 100644 index 00000000..9b4938f7 --- /dev/null +++ b/frontend/src/components/DiscussionList/DiscussionList.styled.ts @@ -0,0 +1,63 @@ +import styled from 'styled-components'; +import CommentCount from '@/assets/images/comment-count.svg'; + +export const DiscussionListContainer = styled.div` + display: flex; + flex-direction: column; + gap: 2.5rem; +`; + +export const DiscussionItemContainer = styled.div` + background-color: ${(props) => props.theme.colors.white}; + + display: flex; + justify-content: space-between; + align-items: center; + + padding: 1.4rem 3.4rem; + box-sizing: border-box; + box-shadow: ${(props) => props.theme.boxShadow.shadow04}; + border-radius: 2.8rem; +`; + +export const BadgeWrapper = styled.div` + display: flex; + gap: 0.7rem; +`; + +export const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 0.5rem; +`; + +export const Title = styled.p` + color: ${(props) => props.theme.colors.black}; + ${(props) => props.theme.font.body} +`; + +export const WriterImg = styled.img` + border-radius: 100%; + max-width: 4.2rem; + max-height: 4.2rem; +`; + +export const DiscussionRight = styled.div` + display: flex; + align-items: center; + gap: 1.7rem; + width: 10.5rem; + + color: ${(props) => props.theme.colors.grey400}; + ${(props) => props.theme.font.caption}; +`; + +export const CommentWrapper = styled.div` + display: flex; + gap: 0.7rem; +`; + +export const CommentCountIcon = styled(CommentCount)` + width: 1.4rem; + height: 1.4rem; +`; diff --git a/frontend/src/components/DiscussionList/DiscussionListItem.tsx b/frontend/src/components/DiscussionList/DiscussionListItem.tsx new file mode 100644 index 00000000..55df213e --- /dev/null +++ b/frontend/src/components/DiscussionList/DiscussionListItem.tsx @@ -0,0 +1,34 @@ +import type { Discussion } from '@/types'; +import * as S from './DiscussionList.styled'; +import Badge from '../common/Badge'; + +export default function DiscussionListItem({ + title, + mission, + hashTags, + member, + commentCount, +}: Omit) { + return ( + + + + {/* TODO: Badge 색상 변경 필요 @프룬 */} + + {hashTags.map((hashTag) => ( + + ))} + + {title} + + + + + + +

{commentCount}

+
+
+
+ ); +} diff --git a/frontend/src/components/DiscussionList/discussionsMock.json b/frontend/src/components/DiscussionList/discussionsMock.json new file mode 100644 index 00000000..bd5f2933 --- /dev/null +++ b/frontend/src/components/DiscussionList/discussionsMock.json @@ -0,0 +1,96 @@ +[ + { + "id": 1, + "title": "객체지향이 뭘까요?", + "content": "객체지향에 대해 토론해봅시다.", + "mission": { + "id": 1, + "title": "주문", + "thumbnail": "https://raw.githubusercontent.com/develup-mission/docs/main/image/java-order.png", + "summary": "배달 주문을 받아보자", + "url": "https://github.com/develup-mission/java-order", + "hashTags": [ + { + "id": 1, + "name": "JAVA" + }, + { + "id": 2, + "name": "객체지향" + }, + { + "id": 3, + "name": "클린코드" + } + ] + }, + "member": { + "id": 1, + "email": "test1@gmail.com", + "name": "구름", + "imageUrl": "https://avatars.githubusercontent.com/u/75781414?v=4" + }, + "hashTags": [ + { + "id": 1, + "name": "JAVA" + }, + { + "id": 2, + "name": "객체지향" + }, + { + "id": 3, + "name": "클린코드" + } + ], + "commentCount": 0 + }, + { + "id": 2, + "title": "여기까지가 50글자 입니다. 여기까지가 50글자 입니다. 여기까지가 50글자 입니다. 하이", + "content": "객체지향에 대해 토론해봅시다.", + "mission": { + "id": 1, + "title": "주문", + "thumbnail": "https://raw.githubusercontent.com/develup-mission/docs/main/image/java-order.png", + "summary": "배달 주문을 받아보자", + "url": "https://github.com/develup-mission/java-order", + "hashTags": [ + { + "id": 1, + "name": "JAVA" + }, + { + "id": 2, + "name": "객체지향" + }, + { + "id": 3, + "name": "클린코드" + } + ] + }, + "member": { + "id": 1, + "email": "test1@gmail.com", + "name": "구름", + "imageUrl": "https://avatars.githubusercontent.com/u/75781414?v=4" + }, + "hashTags": [ + { + "id": 1, + "name": "JAVA" + }, + { + "id": 2, + "name": "객체지향" + }, + { + "id": 3, + "name": "클린코드" + } + ], + "commentCount": 200 + } +] diff --git a/frontend/src/components/DiscussionList/index.tsx b/frontend/src/components/DiscussionList/index.tsx new file mode 100644 index 00000000..78f96600 --- /dev/null +++ b/frontend/src/components/DiscussionList/index.tsx @@ -0,0 +1,25 @@ +import * as S from './DiscussionList.styled'; +import useDiscussions from '@/hooks/useDiscussions'; +import DiscussionListItem from './DiscussionListItem'; +import { useState } from 'react'; + +export default function DiscussionList() { + const [mission, setMission] = useState('all'); + const [hashTag, setHashTag] = useState('all'); + + const { data: discussions } = useDiscussions(mission, hashTag); + return ( + + {discussions.map((discussion) => ( + + ))} + + ); +} diff --git a/frontend/src/components/Header/Header.styled.ts b/frontend/src/components/Header/Header.styled.ts index fb254686..c8f0a54f 100644 --- a/frontend/src/components/Header/Header.styled.ts +++ b/frontend/src/components/Header/Header.styled.ts @@ -63,7 +63,7 @@ export const LoginButton = styled.button` export const MenuWrapper = styled.div` display: flex; - gap: 8rem; + gap: 4.2rem; `; export const MenuText = styled.p<{ $isActive?: boolean }>` diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx index cda8f405..88467b0a 100644 --- a/frontend/src/components/Header/index.tsx +++ b/frontend/src/components/Header/index.tsx @@ -27,6 +27,7 @@ export default function Header() { + {/* 아직 알림이 mock data라서 주석처리 해놓겠습니다 @프룬 */} diff --git a/frontend/src/hooks/queries/keys.ts b/frontend/src/hooks/queries/keys.ts index 54356d2a..15b5070b 100644 --- a/frontend/src/hooks/queries/keys.ts +++ b/frontend/src/hooks/queries/keys.ts @@ -29,3 +29,7 @@ export const hashTagsKeys = { export const userKeys = { info: ['userInfo'], }; + +export const discussionsKeys = { + all: ['all'], +}; diff --git a/frontend/src/hooks/useDiscussions.ts b/frontend/src/hooks/useDiscussions.ts new file mode 100644 index 00000000..b0119f27 --- /dev/null +++ b/frontend/src/hooks/useDiscussions.ts @@ -0,0 +1,13 @@ +import { getDiscussions } from '@/apis/discussionAPI'; +import type { Discussion } from '@/types'; +import { useSuspenseQuery } from '@tanstack/react-query'; +import { discussionsKeys } from './queries/keys'; + +const useDiscussions = (mission: string = 'all', hashTag: string = 'all') => { + return useSuspenseQuery({ + queryKey: [...discussionsKeys.all, mission, hashTag], + queryFn: () => getDiscussions(mission, hashTag), + }); +}; + +export default useDiscussions; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 33093b57..f748ae27 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -12,6 +12,7 @@ import * as Sentry from '@sentry/react'; import { ThemeProvider } from 'styled-components'; import { theme } from './styles/theme'; import './styles/fonts.css'; +import DiscussionListPage from './pages/DiscussionListPage'; export const queryClient = new QueryClient({ defaultOptions: { @@ -34,7 +35,7 @@ const MissionDetailPage = lazy(() => import('./pages/MissionDetailPage')); const MainPage = lazy(() => import('./pages/MainPage')); const MissionSubmitPage = lazy(() => import('./pages/MissionSubmitPage')); const UserProfilePage = lazy(() => import('./pages/UserProfilePage')); -const GuidePage = lazy(() => import('./pages/GuidePage')); +// const GuidePage = lazy(() => import('./pages/GuidePage')); const ErrorPage = lazy(() => import('./pages/ErrorPage')); const AboutPage = lazy(() => import('./pages/AboutPage/AboutPage')); const DiscussionDetailPage = lazy(() => import('./pages/DiscussionDetailPage')); @@ -85,18 +86,22 @@ const routes = [ path: ROUTES.profile, element: ( - - - ), - }, - { - path: ROUTES.guide, - element: ( - - + }> + + ), }, + // { + // path: ROUTES.guide, + // element: ( + // + // }> + // + // + // + // ), + // }, { path: ROUTES.missionList, element: ( @@ -177,7 +182,9 @@ const routes = [ path: ROUTES.about, element: ( - + }> + + ), }, @@ -185,7 +192,19 @@ const routes = [ path: `${ROUTES.discussions}/:id`, element: ( - + }> + + + + ), + }, + { + path: `${ROUTES.discussions}`, + element: ( + + }> + + ), }, diff --git a/frontend/src/pages/DiscussionListPage/DiscussionListPage.styled.ts b/frontend/src/pages/DiscussionListPage/DiscussionListPage.styled.ts new file mode 100644 index 00000000..465ccdda --- /dev/null +++ b/frontend/src/pages/DiscussionListPage/DiscussionListPage.styled.ts @@ -0,0 +1,14 @@ +import styled from 'styled-components'; + +export const DiscussionListPageContainer = styled.div` + display: flex; + flex-direction: column; + gap: 4.5rem; + margin: 4.5rem auto 0; + width: 100%; + max-width: 100rem; +`; + +export const DiscussionListTitle = styled.h1` + ${(props) => props.theme.font.heading1} +`; diff --git a/frontend/src/pages/DiscussionListPage/index.tsx b/frontend/src/pages/DiscussionListPage/index.tsx new file mode 100644 index 00000000..964549e2 --- /dev/null +++ b/frontend/src/pages/DiscussionListPage/index.tsx @@ -0,0 +1,11 @@ +import DiscussionList from '@/components/DiscussionList'; +import * as S from './DiscussionListPage.styled'; + +export default function DiscussionListPage() { + return ( + + 💬 Discussion + + + ); +} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 0b6b959c..65e658d2 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -69,3 +69,12 @@ export interface MyComments { solutionTitle: string; solutionCommentCount: number; } + +export interface Discussion { + id: number; + title: string; + mission: string; + hashTags: HashTag[]; + member: Omit; + commentCount: number; +} From 2106dab337c89e8a33cc715786336b83f555df2f Mon Sep 17 00:00:00 2001 From: Minji <121149171+chosim-dvlpr@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:01:59 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=EB=94=94=EC=8A=A4=EC=BB=A4=EC=85=98=20?= =?UTF-8?q?=EC=A0=9C=EC=B6=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(issue=20#432)=20(#475)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: DiscussionSubmitPage 경로 추가 및 페이지 생성 * refactor: useDescription의 description 타입을 string으로 수정, isValidDescription 삼항연산값 변경 * feat: discussion 제출 API 추가 * feat: useSubmitDiscussionMutation 추가 * feat: useDiscussionTitle 추가 * feat: useSubmitDiscussion 추가 * feat: Discussion Submit 페이지 구현 * refactor: HashTagList, HashTagButton 리팩터링 및 MultipleSelect List 추가 - Hash 네이밍 제거 - 외부에서 tags 타입 주입받도록 변경 - 다중 선택 가능한 TagMultipleList 추가 * refactor: TagButton 색상 variant 추가 - variant 추가 - TagMultipleList 경로를 components/common 으로 이동 * fix: 미션리스트, 풀이리스트 selectedHashTag 타입 수정 - string 타입에서 객체 타입으로 변경 - TagList 컴포넌트의 클릭이벤트 수정 - 디스커션 제출 페이지 태그 디자인 수정 * refactor: getDiscussion api path, discussionTitle의 Input 컴포넌트 입력 방식 수정 * fix: 디스커션 제출 시 전송되는 데이터 missionId값을 optional로 지정 * refactor: selectedMissions 변수명을 단수로 변경 * refactor: route string 표현 방식 수정 * refactor: TagList에서 사용되는 selectedTag 기본값에 null 타입 추가 --- frontend/src/apis/discussionAPI.ts | 16 ++++- frontend/src/apis/missionAPI.ts | 2 +- frontend/src/apis/paths.ts | 1 + frontend/src/apis/solutions.ts | 2 +- .../DiscussionDescription.tsx | 16 +++++ .../DiscussionSubmit.style.ts | 37 ++++++++++ .../DiscussionSubmit/DiscussionTitle.tsx | 26 +++++++ .../src/components/DiscussionSubmit/index.tsx | 69 +++++++++++++++++++ frontend/src/components/HashTagList/index.tsx | 33 --------- .../MissionDetail/MissionDetailHeader.tsx | 4 +- .../SolutionSection/SolutionDetailHeader.tsx | 4 +- .../HashTagButton/HashTagButton.styled.ts | 25 ------- .../components/common/HashTagButton/index.tsx | 18 ----- .../common/TagButton/TagButton.styled.ts | 44 ++++++++++++ .../src/components/common/TagButton/index.tsx | 23 +++++++ .../TagList/TagList.styled.ts} | 2 +- .../src/components/common/TagList/index.tsx | 37 ++++++++++ .../TagMultipleList/TagMultipleList.styled.ts | 8 +++ .../common/TagMultipleList/index.tsx | 48 +++++++++++++ frontend/src/constants/routes.ts | 1 + frontend/src/constants/variants.ts | 5 ++ frontend/src/hooks/useDescription.ts | 4 +- frontend/src/hooks/useDiscussionTitle.ts | 27 ++++++++ frontend/src/hooks/useSubmitDiscussion.ts | 62 +++++++++++++++++ .../src/hooks/useSubmitDiscussionMutation.ts | 27 ++++++++ .../src/hooks/useSubmitSolutionMutation.ts | 2 +- frontend/src/index.tsx | 11 +++ .../DiscussionDetailHeader.tsx | 5 +- .../DiscussionSubmitPage.style.ts | 14 ++++ .../src/pages/DiscussionSubmitPage/index.tsx | 11 +++ frontend/src/pages/MissionListPage/index.tsx | 16 ++--- frontend/src/pages/SolutionListPage/index.tsx | 16 ++--- 32 files changed, 510 insertions(+), 106 deletions(-) create mode 100644 frontend/src/components/DiscussionSubmit/DiscussionDescription.tsx create mode 100644 frontend/src/components/DiscussionSubmit/DiscussionSubmit.style.ts create mode 100644 frontend/src/components/DiscussionSubmit/DiscussionTitle.tsx create mode 100644 frontend/src/components/DiscussionSubmit/index.tsx delete mode 100644 frontend/src/components/HashTagList/index.tsx delete mode 100644 frontend/src/components/common/HashTagButton/HashTagButton.styled.ts delete mode 100644 frontend/src/components/common/HashTagButton/index.tsx create mode 100644 frontend/src/components/common/TagButton/TagButton.styled.ts create mode 100644 frontend/src/components/common/TagButton/index.tsx rename frontend/src/components/{HashTagList/HashTagList.styled.ts => common/TagList/TagList.styled.ts} (71%) create mode 100644 frontend/src/components/common/TagList/index.tsx create mode 100644 frontend/src/components/common/TagMultipleList/TagMultipleList.styled.ts create mode 100644 frontend/src/components/common/TagMultipleList/index.tsx create mode 100644 frontend/src/hooks/useDiscussionTitle.ts create mode 100644 frontend/src/hooks/useSubmitDiscussion.ts create mode 100644 frontend/src/hooks/useSubmitDiscussionMutation.ts create mode 100644 frontend/src/pages/DiscussionSubmitPage/DiscussionSubmitPage.style.ts create mode 100644 frontend/src/pages/DiscussionSubmitPage/index.tsx diff --git a/frontend/src/apis/discussionAPI.ts b/frontend/src/apis/discussionAPI.ts index dbffc275..1168cf25 100644 --- a/frontend/src/apis/discussionAPI.ts +++ b/frontend/src/apis/discussionAPI.ts @@ -3,10 +3,24 @@ import { develupAPIClient } from './clients/develupClient'; import { PATH } from './paths'; export const getDiscussions = async (mission = 'all', hashTag = 'all'): Promise => { - const { data } = await develupAPIClient.get<{ data: Discussion[] }>(`${PATH.discussions}`, { + const { data } = await develupAPIClient.get<{ data: Discussion[] }>(PATH.discussions, { mission, hashTag, }); return data; }; + +export const postDiscussionSubmit = async (payload: { + title: string; + content: string; + missionId?: number; + hashTagIds: number[]; +}): Promise => { + const { data } = await develupAPIClient.post<{ data: Discussion }>( + PATH.submitDiscussion, + payload, + ); + + return data; +}; diff --git a/frontend/src/apis/missionAPI.ts b/frontend/src/apis/missionAPI.ts index 69d53723..74ac192d 100644 --- a/frontend/src/apis/missionAPI.ts +++ b/frontend/src/apis/missionAPI.ts @@ -24,7 +24,7 @@ interface getHashTagsResponse { } export const getMissions = async (filter: string = HASHTAGS.all): Promise => { - const { data } = await develupAPIClient.get(`${PATH.missionList}`, { + const { data } = await develupAPIClient.get(PATH.missionList, { hashTag: filter, }); diff --git a/frontend/src/apis/paths.ts b/frontend/src/apis/paths.ts index 66cbd10b..ce9a87a9 100644 --- a/frontend/src/apis/paths.ts +++ b/frontend/src/apis/paths.ts @@ -16,6 +16,7 @@ export const PATH = { solutions: '/solutions', mySolutions: '/solutions/mine', discussions: '/discussions', + submitDiscussion: '/discussions/submit', }; export const PATH_FORMATTER = { diff --git a/frontend/src/apis/solutions.ts b/frontend/src/apis/solutions.ts index 8e9bd268..6ba25a7d 100644 --- a/frontend/src/apis/solutions.ts +++ b/frontend/src/apis/solutions.ts @@ -21,7 +21,7 @@ export const getSolutionSummaries = async ( filter: string = HASHTAGS.all, ): Promise => { const { data } = await develupAPIClient.get( - `${PATH.solutionSummaries}`, + PATH.solutionSummaries, { hashTag: filter }, ); diff --git a/frontend/src/components/DiscussionSubmit/DiscussionDescription.tsx b/frontend/src/components/DiscussionSubmit/DiscussionDescription.tsx new file mode 100644 index 00000000..0178534f --- /dev/null +++ b/frontend/src/components/DiscussionSubmit/DiscussionDescription.tsx @@ -0,0 +1,16 @@ +import TextArea from '@/components/common/TextArea/TextArea'; +import * as S from './DiscussionSubmit.style'; +import type { TextareaHTMLAttributes } from 'react'; + +interface DiscussionDescriptionProps extends TextareaHTMLAttributes { + danger: boolean; +} + +export default function DiscussionDescription({ danger, ...props }: DiscussionDescriptionProps) { + return ( + + 내용 +