diff --git a/frontend/src/apis/deletes.ts b/frontend/src/apis/deletes.ts new file mode 100644 index 000000000..15fd5fa37 --- /dev/null +++ b/frontend/src/apis/deletes.ts @@ -0,0 +1,9 @@ +import ApiClient from './apiClient'; +import { checkStatus } from './apiconfig'; + +export const deleteCancelChamyo = async (moimId: number) => { + const response = await ApiClient.deleteWithAuth(`moim/${moimId}/comment`, { + moimId, + }); + await checkStatus(response); +}; diff --git a/frontend/src/apis/endPoints.ts b/frontend/src/apis/endPoints.ts index 6689713d5..2fd49897b 100644 --- a/frontend/src/apis/endPoints.ts +++ b/frontend/src/apis/endPoints.ts @@ -6,5 +6,7 @@ const ENDPOINTS = { moim: getEndpoint('v1/moim'), moims: getEndpoint('v1/moim'), auth: getEndpoint('v1/auth'), + chamyo: getEndpoint('v1/chamyo'), + zzim: getEndpoint('v1/zzim'), }; export default ENDPOINTS; diff --git a/frontend/src/apis/fetchs.ts b/frontend/src/apis/fetchs.ts new file mode 100644 index 000000000..68061dcb3 --- /dev/null +++ b/frontend/src/apis/fetchs.ts @@ -0,0 +1,35 @@ +import { TempMoimInputInfo } from '@_types/index'; +import ApiClient from './apiClient'; +import { checkStatus } from './apiconfig'; + +export const fetchCompleteMoin = async (moimId: number) => { + const response = await ApiClient.patchWithAuth(`moim/${moimId}/complete`, { + moimId, + }); + await checkStatus(response); +}; + +export const fetchCancelMoin = async (moimId: number) => { + const response = await ApiClient.patchWithAuth(`moim/${moimId}/cancel`, { + moimId, + }); + await checkStatus(response); +}; + +export const fetchModifyMoin = async ( + moimId: number, + state: TempMoimInputInfo, +) => { + const response = await ApiClient.patchWithAuth(`moim`, { + moimId, + ...state, + }); + await checkStatus(response); +}; + +export const fetchReopenMoin = async (moimId: number) => { + const response = await ApiClient.patchWithAuth(`moim/${moimId}/reopen`, { + moimId, + }); + await checkStatus(response); +}; diff --git a/frontend/src/apis/gets.ts b/frontend/src/apis/gets.ts index 6f42a8c51..8b146f408 100644 --- a/frontend/src/apis/gets.ts +++ b/frontend/src/apis/gets.ts @@ -1,7 +1,13 @@ // ./src/apis/gets.ts -import { MoimInfo } from '@_types/index'; +import { MoimInfo, Participation } from '@_types/index'; import ApiClient from './apiClient'; -import { GetMoim, GetMoims } from './responseTypes'; +import { + GetChamyoAll, + GetChamyoMine, + GetMoim, + GetMoims, + GetZzimMine, +} from './responseTypes'; import { checkStatus } from './apiconfig'; export const getMoims = async (): Promise => { @@ -19,3 +25,31 @@ export const getMoim = async (moimId: number): Promise => { const json: GetMoim = await response.json(); return json.data; }; + +export const getChamyoMine = async ( + moimId: number, +): Promise<'MOIMER' | 'MOIMEE' | 'NON_MOIMEE'> => { + const response = await ApiClient.getWithAuth(`chamyo/mine?moimId=${moimId}`); + checkStatus(response); + + const json: GetChamyoMine = await response.json(); + return json.data.role; +}; + +export const getZzimMine = async (moimId: number): Promise => { + const response = await ApiClient.getWithAuth(`zzim/mine?moimId=${moimId}`); + checkStatus(response); + + const json: GetZzimMine = await response.json(); + return json.data.isZzimed; +}; + +export const getChamyoAll = async ( + moimId: number, +): Promise => { + const response = await ApiClient.getWithAuth(`chamyo/all?moimId=${moimId}`); + checkStatus(response); + + const json: GetChamyoAll = await response.json(); + return json.data.chamyos; +}; diff --git a/frontend/src/apis/posts.ts b/frontend/src/apis/posts.ts index b5ae12be3..d066a3204 100644 --- a/frontend/src/apis/posts.ts +++ b/frontend/src/apis/posts.ts @@ -12,10 +12,22 @@ export const postMoim = async (moim: MoimInputInfo): Promise => { return json.data; }; -export const postJoinMoim = async (moimId: number, nickname: string) => { - const response = await ApiClient.postWithAuth('moim/join', { +export const postJoinMoim = async (moimId: number) => { + const response = await ApiClient.postWithAuth('chamyo', { + moimId, + }); + await checkStatus(response); +}; + +export const postChangeZzim = async (moimId: number) => { + const response = await ApiClient.postWithAuth('zzim', { + moimId, + }); + await checkStatus(response); +}; +export const postWriteComment = async (moimId: number) => { + const response = await ApiClient.postWithAuth(`moim/${moimId}/comment`, { moimId, - nickname, }); await checkStatus(response); }; diff --git a/frontend/src/apis/responseTypes.ts b/frontend/src/apis/responseTypes.ts index 58b63a36c..c2c5a4596 100644 --- a/frontend/src/apis/responseTypes.ts +++ b/frontend/src/apis/responseTypes.ts @@ -1,4 +1,4 @@ -import { MoimInfo } from '../types'; +import { MoimInfo, Participation } from '../types'; export interface GetMoims { data: { moims: MoimInfo[] }; @@ -8,6 +8,24 @@ export interface GetMoim { data: MoimInfo; } +export interface GetChamyoMine { + data: { + role: 'MOIMER' | 'MOIMEE' | 'NON_MOIMEE'; + }; +} + +export interface GetChamyoAll { + data: { + chamyos: Participation[]; + }; +} + +export interface GetZzimMine { + data: { + isZzimed: boolean; + }; +} + export interface PostMoim { data: number; } diff --git a/frontend/src/common/theme/colorPalette.ts b/frontend/src/common/theme/colorPalette.ts index cdf4519fc..e3fbd8749 100644 --- a/frontend/src/common/theme/colorPalette.ts +++ b/frontend/src/common/theme/colorPalette.ts @@ -58,7 +58,7 @@ const colorPalette: Colors = { 300: '#FF9538', 200: '#FFA557', 100: '#FFB87A', - 50: '#FFCB9E', + 50: '#FFCB9E4D', }, yellow: { 900: '#CAA802', diff --git a/frontend/src/components/CommentCard/CommentCard.stories.tsx b/frontend/src/components/CommentCard/CommentCard.stories.tsx index 25ef34294..3a5c9d2c5 100644 --- a/frontend/src/components/CommentCard/CommentCard.stories.tsx +++ b/frontend/src/components/CommentCard/CommentCard.stories.tsx @@ -13,18 +13,18 @@ type Story = StoryObj; export const Default: Story = { args: { comment: { - id: 0, + commentId: 0, nickname: 'nickname', content: 'content', dateTime: '2023-04-04 14:00', - src: '', + profile: '', child: [ { - id: 0, + commentId: 0, nickname: 'nickname', content: 'content', dateTime: '2023-04-04 14:00', - src: '', + profile: '', child: [], }, ], diff --git a/frontend/src/components/CommentCard/CommentCard.tsx b/frontend/src/components/CommentCard/CommentCard.tsx index bbcb16474..cd9e5210a 100644 --- a/frontend/src/components/CommentCard/CommentCard.tsx +++ b/frontend/src/components/CommentCard/CommentCard.tsx @@ -7,35 +7,40 @@ import { HTMLProps } from 'react'; export interface CommentCardProps extends HTMLProps { comment: Comment; + + onWriteClick?: () => void; } export default function CommentCard(props: CommentCardProps) { const theme = useTheme(); - const { comment } = props; + const { + comment: { profile, nickname, dateTime, content, child }, + onWriteClick, + } = props; return (
- +
-
{comment.nickname}
-
{comment.dateTime}
+
{nickname}
+
{dateTime}
- +
-
{comment.content}
+
{content}
- {comment.child && comment.child.length > 0 && ( + {child && child.length > 0 && (
- {comment.child.map((childComment) => ( - + {child.map((childComment) => ( + ))}
)} diff --git a/frontend/src/components/CommentList/ComentList.tsx b/frontend/src/components/CommentList/ComentList.tsx deleted file mode 100644 index c8fdad142..000000000 --- a/frontend/src/components/CommentList/ComentList.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import CommentCard from '@_components/CommentCard/CommentCard'; -import * as S from '@_components/CommentList/ComentList.style'; -import { Comment } from '@_types/index'; -import { HTMLProps } from 'react'; - -interface CommentListProps extends HTMLProps { - comments: Comment[]; -} - -export default function ComentList(props: CommentListProps) { - const { comments } = props; - - return ( -
- {comments.map((comment) => { - return ; - })} -
- ); -} diff --git a/frontend/src/components/CommentList/CommentList.stories.tsx b/frontend/src/components/CommentList/CommentList.stories.tsx index c1a1b27ba..2ac387ffb 100644 --- a/frontend/src/components/CommentList/CommentList.stories.tsx +++ b/frontend/src/components/CommentList/CommentList.stories.tsx @@ -14,40 +14,41 @@ export const Default: Story = { args: { comments: [ { - id: 0, + commentId: 0, nickname: 'nickname', content: 'content', dateTime: '2023-04-04 14:00', - src: '', + profile: '', child: [ { - id: 0, + commentId: 0, nickname: 'nickname', content: 'content', dateTime: '2023-04-04 14:00', - src: '', + profile: '', child: [], }, ], }, { - id: 3, + commentId: 3, nickname: 'nickname', content: 'content', dateTime: '2023-04-04 14:00', - src: '', + profile: '', child: [ { - id: 4, + commentId: 4, nickname: 'nickname', content: 'content', dateTime: '2023-04-04 14:00', - src: '', + profile: '', child: [], }, ], }, ], + onWriteClick: () => {}, }, render: (args) => , }; diff --git a/frontend/src/components/CommentList/CommentList.tsx b/frontend/src/components/CommentList/CommentList.tsx index 2664b1a6b..8df56cd62 100644 --- a/frontend/src/components/CommentList/CommentList.tsx +++ b/frontend/src/components/CommentList/CommentList.tsx @@ -5,15 +5,22 @@ import { HTMLProps } from 'react'; interface CommentListProps extends HTMLProps { comments: Comment[]; + onWriteClick: () => void; } export default function CommentList(props: CommentListProps) { - const { comments } = props; + const { comments, onWriteClick } = props; return (
{comments.map((comment) => { - return ; + return ( + + ); })}
); diff --git a/frontend/src/components/Input/MoimInput.tsx b/frontend/src/components/Input/MoimInput.tsx index 0b9799285..e9934a5d9 100644 --- a/frontend/src/components/Input/MoimInput.tsx +++ b/frontend/src/components/Input/MoimInput.tsx @@ -7,7 +7,7 @@ export interface LabeledInputProps extends HTMLProps { } export default function LabeledInput(props: LabeledInputProps) { - const { name, title, type, placeholder, required, onChange } = props; + const { name, title, type, placeholder, required, onChange, ...args } = props; return ( ); diff --git a/frontend/src/components/Profile/ProfileCard.stories.tsx b/frontend/src/components/Profile/ProfileCard.stories.tsx index 0cc0aba4c..3396fdc1c 100644 --- a/frontend/src/components/Profile/ProfileCard.stories.tsx +++ b/frontend/src/components/Profile/ProfileCard.stories.tsx @@ -14,9 +14,9 @@ type Story = StoryObj; export const Default: Story = { args: { - profile: { + info: { nickname: '치코', - src: EmptyProfile, + profile: EmptyProfile, role: 'moimee', }, }, @@ -25,9 +25,9 @@ export const Default: Story = { export const WithCustomImage: Story = { args: { - profile: { + info: { nickname: '치코', - src: Plus, + profile: Plus, role: 'moimee', }, }, @@ -36,9 +36,9 @@ export const WithCustomImage: Story = { export const WithErrorHandling: Story = { args: { - profile: { + info: { nickname: '치코', - src: 'invalid-url.jpg', // 오류를 발생시키기 위한 잘못된 URL + profile: 'invalid-url.jpg', // 오류를 발생시키기 위한 잘못된 URL role: 'moimee', }, }, diff --git a/frontend/src/components/Profile/ProfileCard.tsx b/frontend/src/components/Profile/ProfileCard.tsx index 2d4eb2262..23f851300 100644 --- a/frontend/src/components/Profile/ProfileCard.tsx +++ b/frontend/src/components/Profile/ProfileCard.tsx @@ -4,20 +4,15 @@ import { useTheme } from '@emotion/react'; import { Participation } from '@_types/index'; interface ProfileCardProps { - profile: Participation; + info: Participation; } export default function ProfileCard(props: ProfileCardProps) { - const { profile } = props; + const { info } = props; const theme = useTheme(); return (
- -
{profile.nickname}
+ +
{info.nickname}
); } diff --git a/frontend/src/components/ProfileList/ProfileList.stories.tsx b/frontend/src/components/ProfileList/ProfileList.stories.tsx index 6dce559ec..927849a78 100644 --- a/frontend/src/components/ProfileList/ProfileList.stories.tsx +++ b/frontend/src/components/ProfileList/ProfileList.stories.tsx @@ -15,42 +15,42 @@ export const Default: Story = { participants: [ { nickname: '치코', - src: '', + profile: '', role: 'moimer', }, { nickname: '치코', - src: '', + profile: '', role: 'moimee', }, { nickname: '치코', - src: '', + profile: '', role: 'moimer', }, { nickname: '치코', - src: '', + profile: '', role: 'moimer', }, { nickname: '치코', - src: '', + profile: '', role: 'moimer', }, { nickname: '치코', - src: '', + profile: '', role: 'moimer', }, { nickname: '치코', - src: '', + profile: '', role: 'moimer', }, { nickname: '치코', - src: '', + profile: '', role: 'moimer', }, ], diff --git a/frontend/src/components/ProfileList/ProfileList.tsx b/frontend/src/components/ProfileList/ProfileList.tsx index 769e69b15..cadc062a9 100644 --- a/frontend/src/components/ProfileList/ProfileList.tsx +++ b/frontend/src/components/ProfileList/ProfileList.tsx @@ -17,9 +17,7 @@ export default function ProfileList(props: ProfileListProps) {
참여자
{participants.map((participant) => { - return ( - - ); + return ; })}
diff --git a/frontend/src/constants/queryKeys.ts b/frontend/src/constants/queryKeys.ts index 66a1a5096..b26573309 100644 --- a/frontend/src/constants/queryKeys.ts +++ b/frontend/src/constants/queryKeys.ts @@ -1,3 +1,11 @@ -const QUERY_KEYS = { moims: 'moims', moim: 'moim', userKey: 'userKey' }; +const QUERY_KEYS = { + moims: 'moims', + moim: 'moim', + userKey: 'userKey', + chamyoMine: 'chamyoMine', + ZzimMine: 'ZzimMine', + chamyoAll: 'chamyoAll', + comment: 'comment', +}; export default QUERY_KEYS; diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index d938c50e8..4e8f2196d 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -4,6 +4,7 @@ const ROUTES = { detail: '/moim/:moimId', participationComplete: '/moim/participation-complete', login: '/login', + modify: '/modify/:moimId', chat: '/chat', chattingRoom: '/chatting-room/:moimId', }; diff --git a/frontend/src/hooks/mutaions/useCancelChamyo.ts b/frontend/src/hooks/mutaions/useCancelChamyo.ts new file mode 100644 index 000000000..34869da5c --- /dev/null +++ b/frontend/src/hooks/mutaions/useCancelChamyo.ts @@ -0,0 +1,18 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import QUERY_KEYS from '@_constants/queryKeys'; + +import { deleteCancelChamyo } from '@_apis/deletes'; + +export default function useCancelChamyo() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: deleteCancelChamyo, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.chamyoMine], + }); + }, + }); +} diff --git a/frontend/src/hooks/mutaions/useCancelMoim.ts b/frontend/src/hooks/mutaions/useCancelMoim.ts new file mode 100644 index 000000000..6ddacf43b --- /dev/null +++ b/frontend/src/hooks/mutaions/useCancelMoim.ts @@ -0,0 +1,18 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import QUERY_KEYS from '@_constants/queryKeys'; + +import { fetchCancelMoin } from '@_apis/fetchs'; + +export default function useCancelMoim() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: fetchCancelMoin, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.moim], + }); + }, + }); +} diff --git a/frontend/src/hooks/mutaions/useChangeZzim.ts b/frontend/src/hooks/mutaions/useChangeZzim.ts new file mode 100644 index 000000000..4970d32e8 --- /dev/null +++ b/frontend/src/hooks/mutaions/useChangeZzim.ts @@ -0,0 +1,17 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import QUERY_KEYS from '@_constants/queryKeys'; +import { postChangeZzim } from '@_apis/posts'; + +export default function useChangeZzim() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: postChangeZzim, + onSuccess: (_, moimId) => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.ZzimMine, moimId], + }); + }, + }); +} diff --git a/frontend/src/hooks/mutaions/useCompleteMoin.ts b/frontend/src/hooks/mutaions/useCompleteMoin.ts new file mode 100644 index 000000000..4b50de8f5 --- /dev/null +++ b/frontend/src/hooks/mutaions/useCompleteMoin.ts @@ -0,0 +1,18 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import QUERY_KEYS from '@_constants/queryKeys'; + +import { fetchCompleteMoin } from '@_apis/fetchs'; + +export default function useCompleteMoin() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: fetchCompleteMoin, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.moim], + }); + }, + }); +} diff --git a/frontend/src/hooks/mutaions/useJoinMoim.ts b/frontend/src/hooks/mutaions/useJoinMoim.ts index 12fb22220..ced396a73 100644 --- a/frontend/src/hooks/mutaions/useJoinMoim.ts +++ b/frontend/src/hooks/mutaions/useJoinMoim.ts @@ -1,14 +1,14 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import QUERY_KEYS from '@_constants/queryKeys'; + import { postJoinMoim } from '@_apis/posts'; export default function useJoinMoim(onSuccess: () => void) { const queryClient = useQueryClient(); return useMutation({ - mutationFn: ({ moimId, nickname }: { moimId: number; nickname: string }) => - postJoinMoim(moimId, nickname), + mutationFn: postJoinMoim, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.moim], diff --git a/frontend/src/hooks/mutaions/useModifyMoim.ts b/frontend/src/hooks/mutaions/useModifyMoim.ts new file mode 100644 index 000000000..00a8dcd2b --- /dev/null +++ b/frontend/src/hooks/mutaions/useModifyMoim.ts @@ -0,0 +1,24 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import QUERY_KEYS from '@_constants/queryKeys'; +import { fetchModifyMoin } from '@_apis/fetchs'; +import { TempMoimInputInfo } from '@_types/index'; + +interface ModifyMoimParams { + moimId: number; + state: TempMoimInputInfo; +} + +export default function useModifyMoim(onSuccess: (moimId: number) => void) { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ moimId, state }: ModifyMoimParams) => + fetchModifyMoin(moimId, state), + onSuccess: (_, { moimId }) => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.moim], + }); + onSuccess(moimId); + }, + }); +} diff --git a/frontend/src/hooks/mutaions/useReopenMoim.ts b/frontend/src/hooks/mutaions/useReopenMoim.ts new file mode 100644 index 000000000..c0d741ecb --- /dev/null +++ b/frontend/src/hooks/mutaions/useReopenMoim.ts @@ -0,0 +1,18 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import QUERY_KEYS from '@_constants/queryKeys'; + +import { fetchReopenMoin } from '@_apis/fetchs'; + +export default function useReopenMoim() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: fetchReopenMoin, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.moim], + }); + }, + }); +} diff --git a/frontend/src/hooks/mutaions/useWriteComment.ts b/frontend/src/hooks/mutaions/useWriteComment.ts new file mode 100644 index 000000000..fbf3d567f --- /dev/null +++ b/frontend/src/hooks/mutaions/useWriteComment.ts @@ -0,0 +1,17 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import QUERY_KEYS from '@_constants/queryKeys'; +import { postWriteComment } from '@_apis/posts'; + +export default function useWriteComment() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: postWriteComment, + onSuccess: (_, moimId) => { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.comment, moimId], + }); + }, + }); +} diff --git a/frontend/src/hooks/queries/useChamyoAll.ts b/frontend/src/hooks/queries/useChamyoAll.ts new file mode 100644 index 000000000..a58988616 --- /dev/null +++ b/frontend/src/hooks/queries/useChamyoAll.ts @@ -0,0 +1,12 @@ +import QUERY_KEYS from '@_constants/queryKeys'; +import { getChamyoAll } from '@_apis/gets'; +import { useQuery } from '@tanstack/react-query'; + +export default function useChamyoAll(moimId: number) { + const { data: participants, isLoading: chamyoAllIsLoading } = useQuery({ + queryKey: [QUERY_KEYS.chamyoAll, moimId], + queryFn: () => getChamyoAll(moimId), + }); + + return { participants, chamyoAllIsLoading }; +} diff --git a/frontend/src/hooks/queries/useChamyoMine.ts b/frontend/src/hooks/queries/useChamyoMine.ts new file mode 100644 index 000000000..4ab5dad67 --- /dev/null +++ b/frontend/src/hooks/queries/useChamyoMine.ts @@ -0,0 +1,12 @@ +import QUERY_KEYS from '@_constants/queryKeys'; +import { getChamyoMine } from '@_apis/gets'; +import { useQuery } from '@tanstack/react-query'; + +export default function useChamyoMine(moimId: number) { + const { data: role, isLoading: isChamyoMineLoading } = useQuery({ + queryKey: [QUERY_KEYS.chamyoMine, moimId], + queryFn: () => getChamyoMine(moimId), + }); + + return { role, isChamyoMineLoading }; +} diff --git a/frontend/src/hooks/queries/useZzimMine.ts b/frontend/src/hooks/queries/useZzimMine.ts new file mode 100644 index 000000000..5ad9d2144 --- /dev/null +++ b/frontend/src/hooks/queries/useZzimMine.ts @@ -0,0 +1,12 @@ +import QUERY_KEYS from '@_constants/queryKeys'; +import { getZzimMine } from '@_apis/gets'; +import { useQuery } from '@tanstack/react-query'; + +export default function useZzimMine(moimId: number) { + const { data: isZzimed, isLoading: isZzimMineLoading } = useQuery({ + queryKey: [QUERY_KEYS.ZzimMine, moimId], + queryFn: () => getZzimMine(moimId), + }); + + return { isZzimed, isZzimMineLoading }; +} diff --git a/frontend/src/mocks/handlers/moimHandler.ts b/frontend/src/mocks/handlers/moimHandler.ts index 774ee7c9a..59068bdfc 100644 --- a/frontend/src/mocks/handlers/moimHandler.ts +++ b/frontend/src/mocks/handlers/moimHandler.ts @@ -90,4 +90,41 @@ export const moimHandler = [ }, }); }), + http.get(`${ENDPOINTS.chamyo}/all?moimId=1`, () => { + return HttpResponse.json({ + data: { + chamyos: [ + { + nickname: '치코', + src: '', + role: 'moimer', + }, + { + nickname: '치코', + src: '', + role: 'moimee', + }, + { + nickname: '치코', + src: '', + role: 'moimer', + }, + ], + }, + }); + }), + http.get(`${ENDPOINTS.zzim}/mine?moimId=1`, () => { + return HttpResponse.json({ + data: { + isZzimed: false, + }, + }); + }), + http.get(`${ENDPOINTS.chamyo}/mine?moimId=1`, () => { + return HttpResponse.json({ + data: { + role: 'MOIMER', + }, + }); + }), ]; diff --git a/frontend/src/pages/MoimDetailPage/MoimDetailPage.tsx b/frontend/src/pages/MoimDetailPage/MoimDetailPage.tsx index fe59f3570..5902dfb63 100644 --- a/frontend/src/pages/MoimDetailPage/MoimDetailPage.tsx +++ b/frontend/src/pages/MoimDetailPage/MoimDetailPage.tsx @@ -1,10 +1,8 @@ -import { ChangeEvent, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import BackLogo from '@_common/assets/back.svg'; import Button from '@_components/Button/Button'; import InformationLayout from '@_layouts/InformationLayout/InformationLayout'; -import LabeledInput from '@_components/Input/MoimInput'; import MoimDescription from '@_components/MoimDescription/MoimDescription'; import MoimInformation from '@_components/MoimInformation/MoimInformation'; import MoimSummary from '@_components/MoimSummary/MoimSummary'; @@ -15,23 +13,46 @@ import ProfileList from '@_components/ProfileList/ProfileList'; import CommentList from '@_components/CommentList/CommentList'; import KebabMenu from '@_components/KebabMenu/KebabMenu'; import ZzimButton from '@_components/Zzim/ZzimButton'; - +import useChamyoMine from '@_hooks/queries/useChamyoMine'; +import useZzimMine from '@_hooks/queries/useZzimMine'; +import useChangeZzim from '@_hooks/mutaions/useChangeZzim'; +import useWriteComment from '@_hooks/mutaions/useWriteComment'; +import useCancelMoim from '@_hooks/mutaions/useCancelMoim'; +import useReopenMoim from '@_hooks/mutaions/useReopenMoim'; +import useCompleteMoin from '@_hooks/mutaions/useCompleteMoin'; +import useChamyoAll from '@_hooks/queries/useChamyoAll'; +import useCancelChamyo from '@_hooks/mutaions/useCancelChamyo'; export default function MoimDetailPage() { const navigate = useNavigate(); const params = useParams(); - const [nickname, setNickname] = useState(''); const moimId = Number(params.moimId); const { moim, isLoading } = useMoim(moimId); + const { role, isChamyoMineLoading } = useChamyoMine(moimId); + const { isZzimed, isZzimMineLoading } = useZzimMine(moimId); + const { participants, chamyoAllIsLoading } = useChamyoAll(moimId); + const { mutate: changZzim } = useChangeZzim(); const { mutate } = useJoinMoim(() => { navigate(ROUTES.participationComplete); }); + const { mutate: writeComment } = useWriteComment(); + const { mutate: cancelMoim } = useCancelMoim(); + + const { mutate: ReopenMoim } = useReopenMoim(); + const { mutate: completeMoim } = useCompleteMoin(); + const { mutate: cancelChamyo } = useCancelChamyo(); + console.log(moim, role, participants); - if (isLoading) { + if ( + isLoading || + isChamyoMineLoading || + isZzimMineLoading || + chamyoAllIsLoading + ) { return
Loading...
; } - if (!moim) { + if (!moim || isZzimed === undefined || !participants) { return
No data found
; } return ( @@ -43,18 +64,35 @@ export default function MoimDetailPage() {
- {}} /> - {} }, - { name: '모임 삭제하기', onClick: () => {} }, - ]} - /> + changZzim(moimId)} /> + {role === 'MOIMER' ? ( + + navigate(`/modify/${moimId}`, { + state: { + ...moim, + }, + }), + }, + { name: '모임 삭제하기', onClick: () => cancelMoim(moimId) }, + { name: '모임 다시 열기', onClick: () => ReopenMoim(moimId) }, + ]} + /> + ) : ( + cancelChamyo(moimId) }, + ]} + /> + )} - + {moim.description && ( @@ -63,25 +101,51 @@ export default function MoimDetailPage() { )} {moim.comments && ( - + writeComment(moimId)} + /> )} - ) => - setNickname(e.target.value) - } - /> - + {role === 'MOIMER' ? ( + moim.status === 'MOIMING' ? ( + + ) : ( + + ) + ) : role === 'NON_MOIMEE' ? ( + moim.status === 'MOIMING' ? ( + + ) : moim.status === 'COMPLETE' ? ( + + ) : ( + + ) + ) : moim.status === 'MOIMING' ? ( + + ) : ( + + )} ); diff --git a/frontend/src/pages/MoimModifyPage/MoimModifyPage.constant.ts b/frontend/src/pages/MoimModifyPage/MoimModifyPage.constant.ts new file mode 100644 index 000000000..62798064e --- /dev/null +++ b/frontend/src/pages/MoimModifyPage/MoimModifyPage.constant.ts @@ -0,0 +1,54 @@ +interface LabeledInputInfo { + name: string; + title: string; + type: string; + placeholder: string; + required: boolean; +} + +const MOIM_INPUT_INFOS: LabeledInputInfo[] = [ + { + name: 'title', + title: '제목', + type: 'text', + placeholder: '없음', + required: true, + }, + { + name: 'date', + title: '날짜', + type: 'date', + placeholder: '없음', + required: true, + }, + { + name: 'time', + title: '시간', + type: 'time', + placeholder: '없음', + required: true, + }, + { + name: 'place', + title: '장소', + type: 'text', + placeholder: '없음', + required: true, + }, + { + name: 'maxPeople', + title: '최대인원수', + type: 'number', + placeholder: '0명', + required: true, + }, + { + name: 'description', + title: '상세 내용 작성', + type: 'textarea', + placeholder: '어떤 모임인지 작성해주세요!', + required: false, + }, +] as const; + +export default MOIM_INPUT_INFOS; diff --git a/frontend/src/pages/MoimModifyPage/MoimModifyPage.hook.ts b/frontend/src/pages/MoimModifyPage/MoimModifyPage.hook.ts new file mode 100644 index 000000000..f0d903f63 --- /dev/null +++ b/frontend/src/pages/MoimModifyPage/MoimModifyPage.hook.ts @@ -0,0 +1,43 @@ +import { ChangeEvent, useState } from 'react'; +import { + validateDate, + validateMaxPeople, + validatePlace, + validateTime, + validateTitle, +} from './MoimModifyPage.util'; + +import { TempMoimInputInfo } from '../../types'; + +const useMoimInfoInput = (state: TempMoimInputInfo) => { + const [inputData, setInputData] = useState({ + title: state.title, + date: state.date, + time: state.time, + place: state.place, + maxPeople: state.maxPeople, + description: state.description, + }); + + const handleChange = (e: ChangeEvent) => { + setInputData({ + ...inputData, + [e.target.name]: e.target.value, + }); + }; + + const isValidMoimInfoInput = + validateTitle(inputData.title) && + validateDate(inputData.date) && + validateTime(inputData.time) && + validatePlace(inputData.place) && + validateMaxPeople(inputData.maxPeople); + + return { + inputData, + handleChange, + isValidMoimInfoInput, + }; +}; + +export default useMoimInfoInput; diff --git a/frontend/src/pages/MoimModifyPage/MoimModifyPage.tsx b/frontend/src/pages/MoimModifyPage/MoimModifyPage.tsx new file mode 100644 index 000000000..4bd288a61 --- /dev/null +++ b/frontend/src/pages/MoimModifyPage/MoimModifyPage.tsx @@ -0,0 +1,61 @@ +import Button from '@_components/Button/Button'; +import FormLayout from '@_layouts/FormLayout/FormLayout'; +import LabeledInput from '@_components/Input/MoimInput'; +import MOIM_INPUT_INFOS from './MoimModifyPage.constant'; +import useMoimInfoInput from './MoimModifyPage.hook'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { useState } from 'react'; +import useModifyMoim from '@_hooks/mutaions/useModifyMoim'; +import { MoimInfo, TempMoimInputInfo } from '@_types/index'; + +export default function MoimModifyPage() { + const navigate = useNavigate(); + const location = useLocation(); + const state = location.state as MoimInfo; + const { mutate: modifyMoim } = useModifyMoim((moimId: number) => { + navigate(`/moim/${moimId}`); + }); + const [isSubmitted, setIsSubmitted] = useState(false); + + const { inputData, handleChange, isValidMoimInfoInput } = + useMoimInfoInput(state); + + const handleRegisterButtonClick = async () => { + if (!isValidMoimInfoInput) { + return; + } + if (isSubmitted) return; + setIsSubmitted(true); + modifyMoim({ moimId: state.moimId, state: inputData }); + }; + + return ( + + navigate(-1)}> + 모임 수정하기 + + + + {MOIM_INPUT_INFOS.map((info) => ( + + ))} + + + + + i + + + ); +} diff --git a/frontend/src/pages/MoimModifyPage/MoimModifyPage.util.ts b/frontend/src/pages/MoimModifyPage/MoimModifyPage.util.ts new file mode 100644 index 000000000..f1f078ba0 --- /dev/null +++ b/frontend/src/pages/MoimModifyPage/MoimModifyPage.util.ts @@ -0,0 +1,27 @@ +// validation.js + +import POLICIES from '@_constants/poclies'; + +export const validateTitle = (title: string) => + POLICIES.minimumTitleLength <= title.length && + title.length <= POLICIES.maximumTitleLength; + +export const validateDate = (date: string) => { + const nowDate = new Date(); + const nowDateYyyymmdd = `${nowDate.getFullYear()}-${(nowDate.getMonth() + 1).toString().padStart(2, '00')}-${nowDate.getDate().toString().padStart(2, '00')}`; + return date >= nowDateYyyymmdd && POLICIES.yyyymmddDashRegex.test(date); +}; + +export const validateTime = (time: string) => POLICIES.hhmmRegex.test(time); + +export const validatePlace = (place: string) => + POLICIES.minimumPlaceLength <= place.length && + place.length <= POLICIES.maximumPlaceLength; + +export const validateMaxPeople = (maxPeople: number) => + POLICIES.minimumMaxPeople <= maxPeople && + maxPeople <= POLICIES.maximumMaxPeople; + +export const validateAuthorNickname = (authorNickname: string) => + POLICIES.minimumAuthorNicknameLength <= authorNickname.length && + authorNickname.length <= POLICIES.maximumAuthorNicknameLength; diff --git a/frontend/src/routes/router.tsx b/frontend/src/routes/router.tsx index 2fa85997c..9068befd0 100644 --- a/frontend/src/routes/router.tsx +++ b/frontend/src/routes/router.tsx @@ -6,6 +6,7 @@ import MoimCreationPage from '@_pages/MoimCreationPage/MoimCreationPage'; import MoimDetailPage from '@_pages/MoimDetailPage/MoimDetailPage'; import ParticipationCompletePage from '@_pages/ParticipationCompletePage/ParticipationCompletePage'; import ProtectedRoute from './ProtectedRoute'; +import MoimModifyPage from '@_pages/MoimModifyPage/MoimModifyPage'; import ROUTES from '@_constants/routes'; import { createBrowserRouter } from 'react-router-dom'; @@ -45,6 +46,11 @@ const routesConfig = [ element: , requiresAuth: false, }, + { + path: ROUTES.modify, + element: , + requiresAuth: false, + }, ]; const router = createBrowserRouter( diff --git a/frontend/src/types/index.d.ts b/frontend/src/types/index.d.ts index 0f8b02b24..4c35b498c 100644 --- a/frontend/src/types/index.d.ts +++ b/frontend/src/types/index.d.ts @@ -15,21 +15,31 @@ export interface MoimInfo { export interface Participation { nickname: string; - src: string; + profile: string; role: Role; } export type Role = 'moimer' | 'moimee'; export interface Comment { - id: number; + commentId: number; nickname: string; content: string; dateTime: string; - src: string; + profile: string; child: Comment[]; } export type MoimInputInfo = Omit< MoimInfo, 'moimId' | 'currentPeople' | 'participants' | 'status' | 'comments' >; + +export type TempMoimInputInfo = Omit< + MoimInfo, + | 'moimId' + | 'currentPeople' + | 'participants' + | 'status' + | 'comments' + | 'authorNickname' +>;