-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 모임 수정 페이지 작업 #231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat] 모임 수정 페이지 작업 #231
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <HydrationBoundary state={dehydratedState}>{children}</HydrationBoundary>; | ||
| }; | ||
|
|
||
| export default EditMeetupLayout; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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', | ||
HopeFullee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } 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 ( | ||
| <div> | ||
| <form> | ||
| <section className='px-4'> | ||
| <form.Field children={(field) => <MeetupTitleField field={field} />} name='title' /> | ||
| <form.Field children={(field) => <MeetupLocationField field={field} />} name='location' /> | ||
| <form.Field children={(field) => <MeetupDateField field={field} />} name='startTime' /> | ||
| <form.Field | ||
| children={(field) => <MeetupCapField field={field} />} | ||
| name='maxParticipants' | ||
| /> | ||
| <form.Field children={(field) => <MeetupImagesField field={field} />} name='images' /> | ||
| <form.Field | ||
| children={(field) => <MeetupDetailField field={field} />} | ||
| name='description' | ||
| /> | ||
| <form.Field children={(field) => <MeetupTagsField field={field} />} name='tags' /> | ||
| </section> | ||
|
|
||
| <form.Subscribe | ||
| children={(state) => ( | ||
| <MeetupSubmitButton state={state} onSubmitClick={() => form.handleSubmit()} /> | ||
| )} | ||
| selector={(state) => state} | ||
| /> | ||
| </form> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| 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 }; | ||
| }; | ||
HopeFullee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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('모임 생성 실패.'); | ||
|
Comment on lines
+15
to
+18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 콘솔 메시지가 편집이 아닌 생성을 참조하고 있습니다. 성공 및 오류 콘솔 로그에 "모임 생성"이 표시되지만 이 훅은 모임을 편집하는 것입니다. 올바른 메시지로 수정해주세요. 🔎 제안된 수정 onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: groupKeys.detail(params.groupId) });
- console.log('모임 생성 성공.');
+ console.log('모임 수정 성공.');
},
onError: () => {
- console.log('모임 생성 실패.');
+ console.log('모임 수정 실패.');
},🤖 Prompt for AI Agents
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 올 ㅋ create hook 복사해와서 수정한건데 이걸 잡네
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @HopeFullee 복사-붙여넣기할 때 이런 실수는 흔하죠! 😄 수정해주시면 감사하겠습니다! |
||
| }, | ||
| }); | ||
| return query; | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
data가 undefined일 경우 런타임 에러 발생 가능useGetGroupDetails가 로딩 중이거나 에러 상태일 때data가undefined일 수 있습니다. 타입 단언(as GetGroupDetailsResponse)으로 이를 무시하면 구조 분해 할당 시 런타임 에러가 발생합니다.레이아웃에서 prefetch와 권한 체크를 수행한다고 해도, 클라이언트 컴포넌트에서는 데이터 로딩 상태를 명시적으로 처리하는 것이 안전합니다.
🔎 제안된 수정 사항
const { data } = useGetGroupDetails({ groupId }); - const { mutateAsync: EditGroup } = useEditGroup({ groupId }); - const { - title, - address: { location }, - startTime, - tags, - description, - maxParticipants, - images, - } = data as GetGroupDetailsResponse; + const { mutateAsync: EditGroup } = useEditGroup({ groupId }); + + if (!data) { + return null; // 또는 로딩 스피너 + } + + const { + title, + address: { location }, + startTime, + tags, + description, + maxParticipants, + images, + } = data;📝 Committable suggestion
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
근데 진지하게 수정 페이지에서 data가 undefined 이면 그냥 터지는게 맞는게 아닐까?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@HopeFullee, 좋은 지적이십니다. 타당한 관점입니다.
레이아웃에서 prefetch와 권한 검증을 통해 데이터가 보장된다면,
data가undefined인 상황은 정상적인 로딩 상태가 아니라 시스템 오류를 의미합니다. 이 경우 "fail fast" 원칙에 따라 일찍 터져서 문제를 명확히 드러내는 것이 오히려 더 나은 선택일 수 있습니다.이 접근의 장점:
고려사항:
하지만 아키텍처 설계 의도를 고려하면 현재 코드가 맞습니다. 제안을 무시하셔도 됩니다. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그나저나 @claudia99503 님은 왜 자꾸 너를 부르는걸까?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.