From c13b7e4a750926ce3e484ca57be7b52aea5ef6b0 Mon Sep 17 00:00:00 2001 From: "chanki.kim" Date: Tue, 11 Feb 2025 11:52:48 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20:=20dashboa?= =?UTF-8?q?rd=20api=20=EB=82=B4=20userSchema=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/dashboards/types.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/apis/dashboards/types.ts b/src/apis/dashboards/types.ts index b012cc8..f4018e7 100644 --- a/src/apis/dashboards/types.ts +++ b/src/apis/dashboards/types.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { DASHBOARD_FORM_ERROR_MESSAGE, DASHBOARD_FORM_VALID_LENGTH } from '@/constants/dashboard'; import { DEFAULT_COLORS } from '@/constants/colors'; +import { userSchema } from '@/apis/users/types'; // base pagination params 타입 (필요시 공용으로 추출) export type BasePaginationParams = { @@ -45,13 +46,6 @@ export const dashboardFormSchema = z.object({ }); export type DashboardFormType = z.infer; -// TODO : 임시 유저 스키마(추후 /apis/auth 쪽에서 작성된 schema 임포트 필요) -export const userSchema = z.object({ - id: z.number(), - email: z.string().email(), - nickname: z.string(), -}); - export const invitationUserSchema = userSchema.pick({ id: true, email: true, From ef5fd107990a3908a3bec7b8078b61537778fc61 Mon Sep 17 00:00:00 2001 From: "chanki.kim" Date: Tue, 11 Feb 2025 13:24:46 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=E2=9C=A8=20feat=20:=20members=20api=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(api=20+=20queries=20+=20types)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/members/index.ts | 23 +++++++++++++++++++++++ src/apis/members/quries.ts | 31 +++++++++++++++++++++++++++++++ src/apis/members/types.ts | 20 ++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 src/apis/members/index.ts create mode 100644 src/apis/members/quries.ts create mode 100644 src/apis/members/types.ts diff --git a/src/apis/members/index.ts b/src/apis/members/index.ts new file mode 100644 index 0000000..c0404d7 --- /dev/null +++ b/src/apis/members/index.ts @@ -0,0 +1,23 @@ +import axiosClientHelper from '@/utils/network/axiosClientHelper'; +import { MembersParams, MembersRespons, membersResponseSchema } from './types'; + +export const getMembers = async ({ page, size, dashboardId }: MembersParams) => { + const response = await axiosClientHelper.get('/members', { + params: { + size: size || 20, + page: page || 1, + dashboardId, + }, + }); + + const result = membersResponseSchema.safeParse(response.data); + if (!result.success) { + throw new Error('서버에서 받은 데이터가 예상과 다릅니다.'); + } + return result.data; +}; + +export const deleteMember = async (memberId: number) => { + const response = await axiosClientHelper.delete(`/members/${memberId}`); + return response.data; +}; diff --git a/src/apis/members/quries.ts b/src/apis/members/quries.ts new file mode 100644 index 0000000..2a6ae49 --- /dev/null +++ b/src/apis/members/quries.ts @@ -0,0 +1,31 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { deleteMember, getMembers } from '.'; + +export const useMembersQuery = (page: number, size: number, dashboardId: number) => { + return useQuery({ + queryKey: ['members', dashboardId, page, size], + queryFn: () => + getMembers({ + page, + size, + dashboardId, + }), + }); +}; + +export const useMembersMutation = () => { + const queryClient = useQueryClient(); + + const remove = useMutation({ + mutationFn: ({ memberId }: { memberId: number; dashboardId: number }) => { + return deleteMember(memberId); + }, + onSuccess: (_, { dashboardId }) => { + queryClient.invalidateQueries({ queryKey: ['members', dashboardId] }); + }, + }); + + return { + remove: remove.mutateAsync, + }; +}; diff --git a/src/apis/members/types.ts b/src/apis/members/types.ts new file mode 100644 index 0000000..63b0525 --- /dev/null +++ b/src/apis/members/types.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; +import { userSchema } from '../users/types'; +import { BasePaginationParams } from '../dashboards/types'; + +export type MembersParams = BasePaginationParams & { + dashboardId: number; +}; + +export const memberSchema = userSchema.extend({ + userId: z.number(), + isOwner: z.boolean(), +}); +export type Member = z.infer; + +export const membersResponseSchema = z.object({ + totalCount: z.number(), + members: z.array(memberSchema), +}); + +export type MembersRespons = z.infer; From f169c22411f039194fde107c2b2b759e876dfd90 Mon Sep 17 00:00:00 2001 From: "chanki.kim" Date: Tue, 11 Feb 2025 13:25:26 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=E2=9C=A8=20feat=20:=20=EA=B5=AC=EC=84=B1?= =?UTF-8?q?=EC=9B=90=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/dashboard/DetailMembers.tsx | 94 +++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/src/components/dashboard/DetailMembers.tsx b/src/components/dashboard/DetailMembers.tsx index 22bc52f..ece0529 100644 --- a/src/components/dashboard/DetailMembers.tsx +++ b/src/components/dashboard/DetailMembers.tsx @@ -1,10 +1,100 @@ +'use client'; + import { Card, CardTitle } from '@/components/ui/Card/Card'; +import { useState } from 'react'; +import { useParams } from 'next/navigation'; +import useAlert from '@/hooks/useAlert'; +import PaginationWithCounter from '@/components/pagination/PaginationWithCounter'; +import { useMembersMutation, useMembersQuery } from '@/apis/members/quries'; +import { Table, TableBody, TableCell, TableCol, TableColGroup, TableHead, TableHeadCell, TableRow } from '@/components/ui/Table/Table'; +import { isAxiosError } from 'axios'; +import Button from '../ui/Button/Button'; +import { getErrorMessage } from '@/utils/errorMessage'; +import Avatar from '../ui/Avatar/Avatar'; + +const PAGE_SIZE = 5; export default function DetailMembers() { + const { id } = useParams<{ id: string }>(); + const [page, setPage] = useState(1); + const { data, error, isLoading } = useMembersQuery(page, PAGE_SIZE, Number(id)); + const { remove } = useMembersMutation(); + const alert = useAlert(); + + const removeMember = async (memberId: number) => { + try { + await remove({ memberId, dashboardId: Number(id) }); + alert('맴버를 삭제했습니다.'); + } catch (error) { + const message = getErrorMessage(error); + alert(message); + } + }; + const notAllowed = isAxiosError(error) && error.status === 403; + return ( - 구성원 -
구성원 리스트(작업필요)
+ + 구성원 +
+ +
+
+ + + + + + + + 이름 + + + + + {isLoading && ( + + +
구성원을 가져오는 중입니다.
+
+
+ )} + {notAllowed && ( + + +
권한이 없습니다.
+
+
+ )} + {data?.members.length === 0 && ( + + +
초대 내역이 없습니다.
+
+
+ )} + {data?.members.map((item) => ( + + +
+ + {item.nickname} +
+
+ + + +
+ ))} +
+
); } From 039790d64cf52fbba77660b363cc66a8739d8d60 Mon Sep 17 00:00:00 2001 From: "chanki.kim" Date: Tue, 11 Feb 2025 13:25:58 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20:=20?= =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20use=20client=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/dashboard/DetailDelete.tsx | 31 ++++++++++++++++++++++ src/components/dashboard/DetailInvited.tsx | 2 ++ src/components/dashboard/DetailModify.tsx | 2 ++ 3 files changed, 35 insertions(+) create mode 100644 src/components/dashboard/DetailDelete.tsx diff --git a/src/components/dashboard/DetailDelete.tsx b/src/components/dashboard/DetailDelete.tsx new file mode 100644 index 0000000..11b17e2 --- /dev/null +++ b/src/components/dashboard/DetailDelete.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { useParams, useRouter } from 'next/navigation'; +import { useDashboardMutation } from '@/apis/dashboards/queries'; +import Button from '@/components/ui/Button/Button'; +import useAlert from '@/hooks/useAlert'; +import { getErrorMessage } from '@/utils/errorMessage'; + +export default function DetailDelete() { + const { id } = useParams<{ id: string }>(); + const router = useRouter(); + const alert = useAlert(); + const { remove } = useDashboardMutation(); + + const handleDelete = async () => { + try { + await remove(Number(id)); + alert('삭제했습니다.'); + router.push(`/mydashboard`); + } catch (error) { + const message = getErrorMessage(error); + alert(message); + } + }; + + return ( + + ); +} diff --git a/src/components/dashboard/DetailInvited.tsx b/src/components/dashboard/DetailInvited.tsx index 6bf5d04..7f8dc96 100644 --- a/src/components/dashboard/DetailInvited.tsx +++ b/src/components/dashboard/DetailInvited.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useRef, useState } from 'react'; import Button from '@/components/ui/Button/Button'; import { Card, CardTitle } from '@/components/ui/Card/Card'; diff --git a/src/components/dashboard/DetailModify.tsx b/src/components/dashboard/DetailModify.tsx index 292547a..97ac682 100644 --- a/src/components/dashboard/DetailModify.tsx +++ b/src/components/dashboard/DetailModify.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useParams } from 'next/navigation'; import { Controller, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; From 704a3c9c95ea0e5982cb6011746c6011928a0666 Mon Sep 17 00:00:00 2001 From: "chanki.kim" Date: Tue, 11 Feb 2025 13:26:36 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=E2=9C=A8=20feat=20:=20=EB=8C=80=EC=8B=9C?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=EC=88=98=EC=A0=95=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B0=9C=EC=84=A0=20-=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20-=20=EC=9E=91=EC=84=B1=EC=9E=90=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/[id]/edit/page.tsx | 59 ++++++++----------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/src/app/(after-login)/dashboard/[id]/edit/page.tsx b/src/app/(after-login)/dashboard/[id]/edit/page.tsx index 73cba66..fcf49fb 100644 --- a/src/app/(after-login)/dashboard/[id]/edit/page.tsx +++ b/src/app/(after-login)/dashboard/[id]/edit/page.tsx @@ -1,52 +1,43 @@ -'use client'; - -import { useParams, useRouter } from 'next/navigation'; -import { useDashboardMutation } from '@/apis/dashboards/queries'; -import Button from '@/components/ui/Button/Button'; -import useAlert from '@/hooks/useAlert'; -import { getErrorMessage } from '@/utils/errorMessage'; import DetailModify from '@/components/dashboard/DetailModify'; import DetailMembers from '@/components/dashboard/DetailMembers'; import DetailInvited from '@/components/dashboard/DetailInvited'; import GoBackLink from '@/components/ui/Link/GoBackLink'; -export default function DashboardEditPage() { - const router = useRouter(); - const alert = useAlert(); - const { id } = useParams<{ id: string }>(); - const { remove } = useDashboardMutation(); +import DetailDelete from '@/components/dashboard/DetailDelete'; +import { Suspense } from 'react'; +import axiosServerHelper from '@/utils/network/axiosServerHelper'; +import { redirect } from 'next/navigation'; +import { Dashboard } from '@/apis/dashboards/types'; + +export default async function DashboardEditPage({ params }: { params: Promise<{ id: string }> }) { + const id = (await params).id; + const response = await axiosServerHelper(`/dashboards/${id}`); + const { createdByMe } = response.data; - const handleDelete = async () => { - try { - await remove(Number(id)); - alert('삭제했습니다.'); - router.push(`/mydashboard`); - } catch (error) { - const message = getErrorMessage(error); - alert(message); - } - }; + if (!createdByMe) { + redirect('/mydashboard'); + } return (
-
- {/* 대시보드 정보 */} - + loading...
}> +
+ {/* 대시보드 정보 */} + - {/* 구성원 리스트 */} - + {/* 구성원 리스트 */} + - {/* 초대내역 */} - + {/* 초대내역 */} + - {/* 대시보드 삭제 */} - -
+ {/* 대시보드 삭제 */} + +
+ ); } From 19d5d26e21086991e2d2596e0434d6086311fe52 Mon Sep 17 00:00:00 2001 From: "chanki.kim" Date: Tue, 11 Feb 2025 13:31:34 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=90=9B=20fix=20:=20=EB=AC=B8=EA=B5=AC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/dashboard/DetailMembers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dashboard/DetailMembers.tsx b/src/components/dashboard/DetailMembers.tsx index ece0529..fa421ff 100644 --- a/src/components/dashboard/DetailMembers.tsx +++ b/src/components/dashboard/DetailMembers.tsx @@ -74,7 +74,7 @@ export default function DetailMembers() { {data?.members.length === 0 && ( -
초대 내역이 없습니다.
+
구성원이 없습니다.
)} From 97e45f7f15f886165807f71960799e79fc9dc6e7 Mon Sep 17 00:00:00 2001 From: "chanki.kim" Date: Tue, 11 Feb 2025 13:57:48 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20:=20?= =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=20=EC=A0=95=EB=B3=B4=20ssr?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A1=9C=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/[id]/edit/page.tsx | 2 +- src/components/dashboard/DetailModify.tsx | 83 +++++++------------ 2 files changed, 33 insertions(+), 52 deletions(-) diff --git a/src/app/(after-login)/dashboard/[id]/edit/page.tsx b/src/app/(after-login)/dashboard/[id]/edit/page.tsx index fcf49fb..b2d7195 100644 --- a/src/app/(after-login)/dashboard/[id]/edit/page.tsx +++ b/src/app/(after-login)/dashboard/[id]/edit/page.tsx @@ -26,7 +26,7 @@ export default async function DashboardEditPage({ params }: { params: Promise<{ loading...}>
{/* 대시보드 정보 */} - + {/* 구성원 리스트 */} diff --git a/src/components/dashboard/DetailModify.tsx b/src/components/dashboard/DetailModify.tsx index 97ac682..70ea64e 100644 --- a/src/components/dashboard/DetailModify.tsx +++ b/src/components/dashboard/DetailModify.tsx @@ -1,24 +1,17 @@ 'use client'; -import { useParams } from 'next/navigation'; import { Controller, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useDashboardMutation, useDashboardQuery } from '@/apis/dashboards/queries'; +import { useDashboardMutation } from '@/apis/dashboards/queries'; import { Card, CardTitle } from '@/components/ui/Card/Card'; import { Input } from '@/components/ui/Field'; import ColorPicker from '@/components/ui/Chip/ColorPicker'; -import { dashboardFormSchema, DashboardFormType } from '@/apis/dashboards/types'; +import { Dashboard, dashboardFormSchema, DashboardFormType } from '@/apis/dashboards/types'; import { getErrorMessage } from '@/utils/errorMessage'; import Button from '@/components/ui/Button/Button'; -import { useEffect } from 'react'; -import { DEFAULT_COLORS } from '@/constants/colors'; import useAlert from '@/hooks/useAlert'; -export default function DetailModify() { - const { id } = useParams<{ id: string }>(); - const { data, isLoading } = useDashboardQuery(Number(id)); - const alert = useAlert(); - +export default function DetailModify({ data }: { data: Dashboard }) { const { handleSubmit, register, @@ -29,26 +22,20 @@ export default function DetailModify() { resolver: zodResolver(dashboardFormSchema), mode: 'onBlur', defaultValues: { - title: '', - color: DEFAULT_COLORS[0], + title: data.title, + color: data.color, }, }); const { update } = useDashboardMutation(); - - useEffect(() => { - //react query의 데이터가 오기전에 rhf의 기본값이 셋팅되어서 effect를 통해 재설정 - if (data) { - reset({ - title: data.title, - color: data.color, - }); - } - }, [data, reset]); + const alert = useAlert(); const onSubmit = async (formData: DashboardFormType) => { try { - await update({ id: Number(id), ...formData }); - reset(); + await update({ id: Number(data.id), ...formData }); + reset({ + title: formData.title, + color: formData.color, + }); alert('수정했습니다.'); } catch (error) { const message = getErrorMessage(error); @@ -60,33 +47,27 @@ export default function DetailModify() { return ( - {isLoading ? ( -
대시보드 정보를 가져오는 중입니다.
- ) : ( - <> - {data?.title} -
-
- - { - return field.onChange(value)} />; - }} - /> - -
-
- - )} + {data.title} +
+
+ + { + return field.onChange(value)} />; + }} + /> + +
+
); }