diff --git a/src/api/service/group-service/index.ts b/src/api/service/group-service/index.ts index ee833a80..1dd79a48 100644 --- a/src/api/service/group-service/index.ts +++ b/src/api/service/group-service/index.ts @@ -7,7 +7,7 @@ import { GetGroupsResponse, GetMyGroupsPayload, GetMyGroupsResponse, - GroupIdPayload, + GroupIdParams, PreUploadGroupImageResponse, } from '@/types/service/group'; @@ -43,20 +43,24 @@ export const groupServiceRemote = () => ({ return apiV2.post('/groups/create', payload); }, - getGroupDetails: (payload: GroupIdPayload) => { - return apiV2.get(`/groups/${payload.groupId}`); + editGroup: (params: GroupIdParams, payload: CreateGroupPayload) => { + return apiV2.patch(`/groups/${params.groupId}`, payload); }, - attendGroup: (payload: GroupIdPayload) => { - return apiV2.post(`/groups/${payload.groupId}/attend`); + getGroupDetails: (params: GroupIdParams) => { + return apiV2.get(`/groups/${params.groupId}`); }, - leaveGroup: (payload: GroupIdPayload) => { - return apiV2.post(`/groups/${payload.groupId}/left`); + attendGroup: (params: GroupIdParams) => { + return apiV2.post(`/groups/${params.groupId}/attend`); }, - deleteGroup: (payload: GroupIdPayload) => { - return apiV2.delete(`/groups/${payload.groupId}`); + leaveGroup: (params: GroupIdParams) => { + return apiV2.post(`/groups/${params.groupId}/left`); + }, + + deleteGroup: (params: GroupIdParams) => { + return apiV2.delete(`/groups/${params.groupId}`); }, uploadGroupImages: (payload: FormData) => { diff --git a/src/app/post-meetup/[groupId]/layout.tsx b/src/app/post-meetup/[groupId]/layout.tsx new file mode 100644 index 00000000..89257d12 --- /dev/null +++ b/src/app/post-meetup/[groupId]/layout.tsx @@ -0,0 +1,38 @@ +import { redirect } from 'next/navigation'; + +import { dehydrate, HydrationBoundary } from '@tanstack/react-query'; +import { QueryClient } from '@tanstack/react-query'; + +import { API } from '@/api'; +import { groupKeys } from '@/lib/query-key/query-key-group'; + +interface Props { + children: React.ReactNode; + params: Promise<{ groupId: string }>; +} + +const EditMeetupLayout = async ({ children, params }: Props) => { + const queryClient = new QueryClient(); + + const { groupId } = await params; + + const { userId: sessionUserId } = await API.userService.getMe(); + + const { createdBy, status } = await queryClient.fetchQuery({ + queryKey: groupKeys.detail(groupId), + queryFn: async () => API.groupService.getGroupDetails({ groupId }), + }); + + const isHost = sessionUserId === createdBy.userId; + const isEditable = status !== 'FINISHED'; + + if (!isHost || !isEditable) { + redirect('/post-meetup'); + } + + const dehydratedState = dehydrate(queryClient); + + return {children}; +}; + +export default EditMeetupLayout; diff --git a/src/app/post-meetup/[groupId]/page.tsx b/src/app/post-meetup/[groupId]/page.tsx new file mode 100644 index 00000000..dd77d8d2 --- /dev/null +++ b/src/app/post-meetup/[groupId]/page.tsx @@ -0,0 +1,117 @@ +'use client'; + +import { useRouter } from 'next/navigation'; + +import { use } from 'react'; + +import { useForm } from '@tanstack/react-form'; + +import { + MeetupCapField, + MeetupDateField, + MeetupDetailField, + MeetupImagesField, + MeetupLocationField, + MeetupSubmitButton, + MeetupTagsField, + MeetupTitleField, +} from '@/components/pages/post-meetup'; +import { useEditGroup } from '@/hooks/use-group/use-group-edit'; +import { useGetGroupDetails } from '@/hooks/use-group/use-group-get-details'; +import { CreateGroupFormValues, createGroupSchema } from '@/lib/schema/group'; +import { GetGroupDetailsResponse, PreUploadGroupImageResponse } from '@/types/service/group'; + +interface Props { + params: Promise<{ groupId: string }>; +} + +const EditMeetupPage = ({ params }: Props) => { + const { groupId } = use(params); + const { replace } = useRouter(); + const { data } = useGetGroupDetails({ groupId }); + const { mutateAsync: EditGroup } = useEditGroup({ groupId }); + + const { + title, + address: { location }, + startTime, + tags, + description, + maxParticipants, + images, + } = data as GetGroupDetailsResponse; + + const { defaultImages } = convertToDefaultImages(images); + + const form = useForm({ + defaultValues: { + title, + location, + startTime, + tags, + description, + maxParticipants, + images: defaultImages, + joinPolicy: 'FREE', + } as CreateGroupFormValues, + validators: { + onChange: createGroupSchema, + onSubmit: createGroupSchema, + }, + onSubmit: async ({ value }) => { + value.images = value.images?.map((image, idx) => { + return { ...image, sortOrder: idx }; + }); + + const res = await EditGroup(value); + + replace(`/meetup/${res.id}`); + }, + }); + + return ( +
+
+
+ } name='title' /> + } name='location' /> + } name='startTime' /> + } + name='maxParticipants' + /> + } name='images' /> + } + name='description' + /> + } name='tags' /> +
+ + ( + form.handleSubmit()} /> + )} + selector={(state) => state} + /> + +
+ ); +}; + +export default EditMeetupPage; + +const convertToDefaultImages = (images: GetGroupDetailsResponse['images']) => { + const defaultImages: PreUploadGroupImageResponse['images'] = []; + + images.forEach(({ imageKey, sortOrder, variants }) => { + defaultImages.push({ + imageKey, + sortOrder, + imageUrl100x100: variants[0].imageUrl, + imageUrl440x240: variants[1].imageUrl, + }); + }); + + return { defaultImages }; +}; diff --git a/src/app/post-meetup/page.tsx b/src/app/post-meetup/page.tsx index 6a16e467..e771ae90 100644 --- a/src/app/post-meetup/page.tsx +++ b/src/app/post-meetup/page.tsx @@ -30,6 +30,7 @@ const PostMeetupPage = () => { description: '', maxParticipants: 0, images: [], + joinPolicy: 'FREE', } as CreateGroupFormValues, validators: { onChange: createGroupSchema, @@ -40,7 +41,7 @@ const PostMeetupPage = () => { return { ...image, sortOrder: idx }; }); - const res = await createGroup({ ...value }); + const res = await createGroup(value); replace(`/meetup/${res.id}`); }, diff --git a/src/hooks/use-group/use-group-attend/index.ts b/src/hooks/use-group/use-group-attend/index.ts index 112729e9..3962ba48 100644 --- a/src/hooks/use-group/use-group-attend/index.ts +++ b/src/hooks/use-group/use-group-attend/index.ts @@ -2,15 +2,15 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { API } from '@/api'; import { groupKeys } from '@/lib/query-key/query-key-group'; -import { GroupIdPayload } from '@/types/service/group'; +import { GroupIdParams } from '@/types/service/group'; -export const useAttendGroup = (payload: GroupIdPayload, callback: () => void) => { +export const useAttendGroup = (params: GroupIdParams, callback: () => void) => { const queryClient = useQueryClient(); const query = useMutation({ - mutationFn: () => API.groupService.attendGroup(payload), + mutationFn: () => API.groupService.attendGroup(params), onSuccess: async () => { - await queryClient.invalidateQueries({ queryKey: groupKeys.detail(payload.groupId) }); + await queryClient.invalidateQueries({ queryKey: groupKeys.detail(params.groupId) }); callback(); console.log('모임 참여 성공.'); }, diff --git a/src/hooks/use-group/use-group-delete/index.ts b/src/hooks/use-group/use-group-delete/index.ts index 224b5d9a..c8f4efb2 100644 --- a/src/hooks/use-group/use-group-delete/index.ts +++ b/src/hooks/use-group/use-group-delete/index.ts @@ -1,11 +1,11 @@ import { useMutation } from '@tanstack/react-query'; import { API } from '@/api'; -import { GroupIdPayload } from '@/types/service/group'; +import { GroupIdParams } from '@/types/service/group'; -export const useDeleteGroup = (payload: GroupIdPayload, callback: () => void) => { +export const useDeleteGroup = (params: GroupIdParams, callback: () => void) => { const query = useMutation({ - mutationFn: () => API.groupService.deleteGroup(payload), + mutationFn: () => API.groupService.deleteGroup(params), onSuccess: async () => { console.log('모임 삭제 성공.'); callback(); diff --git a/src/hooks/use-group/use-group-edit/index.ts b/src/hooks/use-group/use-group-edit/index.ts new file mode 100644 index 00000000..62a5219b --- /dev/null +++ b/src/hooks/use-group/use-group-edit/index.ts @@ -0,0 +1,22 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { API } from '@/api'; +import { groupKeys } from '@/lib/query-key/query-key-group'; +import { CreateGroupPayload, GroupIdParams } from '@/types/service/group'; + +export const useEditGroup = (params: GroupIdParams) => { + const queryClient = useQueryClient(); + + const query = useMutation({ + mutationFn: (payload: CreateGroupPayload) => API.groupService.editGroup(params, payload), + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: groupKeys.detail(params.groupId) }); + + console.log('모임 생성 성공.'); + }, + onError: () => { + console.log('모임 생성 실패.'); + }, + }); + return query; +}; diff --git a/src/hooks/use-group/use-group-get-details/index.ts b/src/hooks/use-group/use-group-get-details/index.ts index 6499b965..e48b33c6 100644 --- a/src/hooks/use-group/use-group-get-details/index.ts +++ b/src/hooks/use-group/use-group-get-details/index.ts @@ -2,12 +2,12 @@ import { useQuery } from '@tanstack/react-query'; import { API } from '@/api'; import { groupKeys } from '@/lib/query-key/query-key-group'; -import { GroupIdPayload } from '@/types/service/group'; +import { GroupIdParams } from '@/types/service/group'; -export const useGetGroupDetails = (payload: GroupIdPayload) => { +export const useGetGroupDetails = (params: GroupIdParams) => { const query = useQuery({ - queryKey: groupKeys.detail(payload.groupId), - queryFn: () => API.groupService.getGroupDetails(payload), + queryKey: groupKeys.detail(params.groupId), + queryFn: () => API.groupService.getGroupDetails(params), }); return query; }; diff --git a/src/hooks/use-group/use-group-leave/index.ts b/src/hooks/use-group/use-group-leave/index.ts index 1d1bb2ed..0bb88fcd 100644 --- a/src/hooks/use-group/use-group-leave/index.ts +++ b/src/hooks/use-group/use-group-leave/index.ts @@ -2,15 +2,15 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { API } from '@/api'; import { groupKeys } from '@/lib/query-key/query-key-group'; -import { GroupIdPayload } from '@/types/service/group'; +import { GroupIdParams } from '@/types/service/group'; -export const useLeaveGroup = (payload: GroupIdPayload, callback: () => void) => { +export const useLeaveGroup = (params: GroupIdParams, callback: () => void) => { const queryClient = useQueryClient(); const query = useMutation({ - mutationFn: () => API.groupService.leaveGroup(payload), + mutationFn: () => API.groupService.leaveGroup(params), onSuccess: async () => { - await queryClient.invalidateQueries({ queryKey: groupKeys.detail(payload.groupId) }); + await queryClient.invalidateQueries({ queryKey: groupKeys.detail(params.groupId) }); callback(); console.log('모임 탈퇴 성공.'); }, diff --git a/src/lib/schema/group.ts b/src/lib/schema/group.ts index a0cd3acf..afe778dd 100644 --- a/src/lib/schema/group.ts +++ b/src/lib/schema/group.ts @@ -25,6 +25,7 @@ export const createGroupSchema = z.object({ }), ) .optional(), + joinPolicy: z.union([z.literal('FREE'), z.literal('APPROVE')]), }); export type CreateGroupFormValues = z.infer; diff --git a/src/types/service/group.ts b/src/types/service/group.ts index 3a1aab39..331c11cf 100644 --- a/src/types/service/group.ts +++ b/src/types/service/group.ts @@ -101,6 +101,7 @@ export interface CreateGroupPayload { imageKey: string; sortOrder: number; }[]; + joinPolicy: 'FREE' | 'APPROVE'; } export interface CreateGroupResponse { @@ -200,6 +201,6 @@ export interface GetGroupDetailsResponse { }[]; } -export interface GroupIdPayload { +export interface GroupIdParams { groupId: string; }