Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/_apis/gathering/gathering-detail-apis.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fetchApi } from '@/src/utils/api';
import { GatheringDetailType } from '@/src/types/gathering-data';
import { CreateGatheringRequestTypes, GatheringDetailType } from '@/src/types/gathering-data';

// NOTE: 약속 디테일 불러오기
export async function GetGatheringDetail(
Expand Down Expand Up @@ -52,3 +52,18 @@ export async function LeaveGathering(crewId: number, gatheringId: number): Promi
},
});
}

export async function createGathering(id: number, data: CreateGatheringRequestTypes) {
try {
await fetchApi(`/api/crews/${id}/gatherings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // Include authentication credentials
body: JSON.stringify(data),
});
} catch (error) {
throw error;
}
}
Comment on lines +56 to +69
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

API 구현에 대한 몇 가지 개선사항이 있습니다.

다음 사항들을 개선하면 좋을 것 같습니다:

  1. 에러 처리가 너무 일반적입니다. 구체적인 에러 케이스를 처리해주세요.
  2. 함수의 반환 타입이 명시되어 있지 않습니다.
  3. id 매개변수의 이름이 모호합니다. crewId로 변경하면 좋겠습니다.
  4. 입력값 유효성 검사가 없습니다.

다음과 같이 수정하는 것을 제안드립니다:

-export async function createGathering(id: number, data: CreateGatheringRequestTypes) {
+export async function createGathering(
+  crewId: number,
+  data: CreateGatheringRequestTypes
+): Promise<void> {
+  if (!crewId || crewId <= 0) {
+    throw new Error('유효하지 않은 크루 ID입니다.');
+  }
+
   try {
-    await fetchApi(`/api/crews/${id}/gatherings`, {
+    await fetchApi(`/api/crews/${crewId}/gatherings`, {
       method: 'POST',
       headers: {
         'Content-Type': 'application/json',
       },
       credentials: 'include',
       body: JSON.stringify(data),
     });
   } catch (error) {
-    throw error;
+    if (error instanceof Error) {
+      throw new Error(`모임 생성 중 오류 발생: ${error.message}`);
+    }
+    throw new Error('알 수 없는 오류가 발생했습니다.');
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function createGathering(id: number, data: CreateGatheringRequestTypes) {
try {
await fetchApi(`/api/crews/${id}/gatherings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // Include authentication credentials
body: JSON.stringify(data),
});
} catch (error) {
throw error;
}
}
export async function createGathering(
crewId: number,
data: CreateGatheringRequestTypes
): Promise<void> {
if (!crewId || crewId <= 0) {
throw new Error('유효하지 않은 크루 ID입니다.');
}
try {
await fetchApi(`/api/crews/${crewId}/gatherings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(data),
});
} catch (error) {
if (error instanceof Error) {
throw new Error(`모임 생성 중 오류 발생: ${error.message}`);
}
throw new Error('알 수 없는 오류가 발생했습니다.');
}
}

24 changes: 22 additions & 2 deletions src/_queries/gathering/gathering-detail-queries.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import { useQuery } from '@tanstack/react-query';
import { GetGatheringDetail } from '@/src/_apis/gathering/gathering-detail-apis';
import { toast } from 'react-toastify';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { GetGatheringDetail, createGathering } from '@/src/_apis/gathering/gathering-detail-apis';
import { CreateGatheringRequestTypes } from '@/src/types/gathering-data';

export function useGetGatheringDetailQuery(crewId: number, gatheringId: number) {
return useQuery({
queryKey: ['gatheringDetail', crewId, gatheringId],
queryFn: () => GetGatheringDetail(crewId, gatheringId),
});
}

export function useCreateGatheringQuery(crewId: number) {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (data: CreateGatheringRequestTypes) => createGathering(crewId, data),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['gatheringList', crewId],
refetchType: 'all',
});
toast.success('크루가 생성되었습니다.');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

성공 메시지의 용어를 수정해야 합니다.

성공 메시지에서 "크루가 생성되었습니다"라고 되어 있는데, PR의 목적에 따르면 이는 "모임"을 생성하는 기능입니다.

다음과 같이 수정하는 것을 제안드립니다:

-      toast.success('크루가 생성되었습니다.');
+      toast.success('모임이 생성되었습니다.');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
toast.success('크루가 생성되었습니다.');
toast.success('모임이 생성되었습니다.');

},
onError: (error) => {
toast.error(error.message);
},
});
}

This file was deleted.

2 changes: 1 addition & 1 deletion src/app/(crew)/crew/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Loader } from '@mantine/core';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createCrew } from '@/src/_apis/crew/crew';
import { getImageUrl } from '@/src/_apis/image/get-image-url';
import CreateCrewForm from '@/src/app/(crew)/crew/_components/create-crew-form';
import CreateCrewForm from '@/src/app/(crew)/crew/create/_components/create-crew-form';
import { CreateCrewFormTypes, CreateCrewRequestTypes } from '@/src/types/create-crew';
import IcoCreateCrew from '@/public/assets/icons/ic-create-crew.svg';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { Controller, useForm, useWatch } from 'react-hook-form';
import { NumberInput } from '@mantine/core';
import { getImageUrl } from '@/src/_apis/image/get-image-url';
import Button from '@/src/components/common/input/button';
import DateTimePicker from '@/src/components/common/input/date-time-picker';
import FileInputWrap from '@/src/components/common/input/file-input-wrap';
Expand Down Expand Up @@ -38,6 +39,16 @@ export default function CreateGatheringForm({
const location = useWatch({ control, name: 'location' });
const introduce = useWatch({ control, name: 'introduce' });

const handleFileChange = async (
file: File | string | null,
onChange: (value: string | File) => void,
) => {
if (file instanceof File) {
const imgResponse = await getImageUrl(file, 'CREW');
onChange(imgResponse?.imageUrl || '');
}
};

return (
<form onSubmit={isEdit ? handleSubmit(onEdit) : handleSubmit(onSubmit)}>
<div className="flex flex-col gap-6">
Expand Down Expand Up @@ -103,6 +114,7 @@ export default function CreateGatheringForm({
{...field}
sample={ImgGatheringSampleUrls}
onChange={(newValue) => {
handleFileChange(newValue, field.onChange);
field.onChange(newValue);
trigger('imageUrl');
Comment on lines +117 to 119
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

onChange 핸들러의 중복 호출 문제를 수정해주세요.

현재 코드에서는 handleFileChangefield.onChange가 연속적으로 호출되어 잠재적인 상태 불일치 문제가 발생할 수 있습니다.

다음과 같이 수정하는 것을 추천드립니다:

-handleFileChange(newValue, field.onChange);
-field.onChange(newValue);
-trigger('imageUrl');
+handleFileChange(newValue, (value) => {
+  field.onChange(value);
+  trigger('imageUrl');
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
handleFileChange(newValue, field.onChange);
field.onChange(newValue);
trigger('imageUrl');
handleFileChange(newValue, (value) => {
field.onChange(value);
trigger('imageUrl');
});

}}
Expand Down Expand Up @@ -162,8 +174,7 @@ export default function CreateGatheringForm({
{...field}
fullDate={new Date()}
onChange={(date) => {
const formattedDate = date.toLocaleString();
onChange(formattedDate);
onChange(date);
trigger('dateTime'); // 유효성 검사 실행
}}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use client';

import { Loader } from '@mantine/core';
import { useCreateGatheringQuery } from '@/src/_queries/gathering/gathering-detail-queries';
import { CreateGatheringFormTypes, CreateGatheringRequestTypes } from '@/src/types/gathering-data';
import CreateGatheringModalPresenter from './presenter';

export interface CreateGatheringModalContainerProps {
crewId: number;
opened: boolean;
close: () => void;
data: CreateGatheringFormTypes;
}

export default function CreateGatheringModalContainer({
crewId,
opened,
close,
data,
}: CreateGatheringModalContainerProps) {
const { isPending, mutate } = useCreateGatheringQuery(crewId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

에러 처리 추가 필요

useCreateGatheringQuery의 에러 상태를 처리하는 로직이 없습니다. 사용자에게 적절한 에러 메시지를 표시하는 것이 좋을 것 같습니다.

- const { isPending, mutate } = useCreateGatheringQuery(crewId);
+ const { isPending, error, mutate } = useCreateGatheringQuery(crewId);
+
+ useEffect(() => {
+   if (error) {
+     notifications.show({
+       title: '오류 발생',
+       message: '모임을 생성하는 중 문제가 발생했습니다.',
+       color: 'red',
+     });
+   }
+ }, [error]);

Committable suggestion skipped: line range outside the PR's diff.


const handleSubmit = async (createdData: CreateGatheringFormTypes) => {
const newData: CreateGatheringRequestTypes = {
title: createdData.title,
imageUrl: (createdData.imageUrl as string) ?? '',
dateTime: createdData.dateTime,
location: createdData.location,
totalCount: createdData.totalCount,
introduce: createdData.introduce,
};

mutate(newData);
close();
};
const handleEdit = () => {
// TODO : 약속 수정하기 API 연결
close();
};

if (isPending)
return (
<div className="fixed inset-0 z-10 flex items-center justify-center">
<Loader size="sm" />
</div>
);

return (
<CreateGatheringModalPresenter
data={data}
opened={opened}
onClose={close}
onEdit={handleEdit}
onSubmit={handleSubmit}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Modal, ScrollArea } from '@mantine/core';
import CreateGatheringForm from '@/src/app/(crew)/crew/_components/create-gathering-form';
import CreateGatheringForm from '@/src/app/(crew)/crew/detail/[id]/_components/create-gathering/create-gathering-form';
import { CreateGatheringFormTypes } from '@/src/types/gathering-data';

export interface GatheringDetailModalProps {
opened: boolean;
onClose: () => void;
onSubmit: () => void;
onSubmit: (data: CreateGatheringFormTypes) => void;
onEdit: () => void;
data: CreateGatheringFormTypes;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import { useRouter } from 'next/navigation';
import { useDisclosure } from '@mantine/hooks';
import { useAuthStore } from '@/src/store/use-auth-store';
import CreateGatheringModalContainer from '@/src/app/(crew)/crew/_components/create-gathering-modal/container';
import CreateGatheringModalContainer from '@/src/app/(crew)/crew/detail/[id]/_components/create-gathering/create-gathering-modal/container';
import Button from '@/src/components/common/input/button';
import { CreateGatheringFormTypes } from '@/src/types/gathering-data';

export default function CreateGathering() {
export default function CreateGathering({ crewId }: { crewId: number }) {
const { isAuth } = useAuthStore();
const router = useRouter();
const [opened, { open, close }] = useDisclosure(false);
Expand All @@ -34,7 +34,12 @@ export default function CreateGathering() {
<Button type="button" className="btn-filled px-4" onClick={handleButtonClick}>
약속 만들기
</Button>
<CreateGatheringModalContainer opened={opened} close={close} data={initialValue} />
<CreateGatheringModalContainer
crewId={crewId}
opened={opened}
close={close}
data={initialValue}
/>
</>
);
}
2 changes: 1 addition & 1 deletion src/app/(crew)/crew/detail/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Image from 'next/image';
import { useParams } from 'next/navigation';
import { useGetCrewDetailQuery } from '@/src/_queries/crew/crew-detail-queries';
import CreateCrewForm from '@/src/app/(crew)/crew/_components/create-crew-form';
import CreateCrewForm from '@/src/app/(crew)/crew/create/_components/create-crew-form';
import { CreateCrewFormTypes } from '@/src/types/create-crew';
import IcoCreateCrew from '@/public/assets/icons/ic-create-crew.svg';

Expand Down
2 changes: 1 addition & 1 deletion src/app/(crew)/crew/detail/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default async function CrewDetailPage({ params }: CrewDetailPageProps) {
<article className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-semibold">크루 약속</h2>
{/* <CreateGathering /> */}
<CreateGathering crewId={Number(params.id)} />
</div>
<div className="flex w-full">
<GatheringListSection id={id} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/gathering-card/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { toast } from 'react-toastify';
import { useDisclosure } from '@mantine/hooks';
import { useGetGatheringDetailQuery } from '@/src/_queries/gathering/gathering-detail-queries';
import { ApiError } from '@/src/utils/api';
import GatheringDetailModalContainer from '@/src/app/(crew)/crew/_components/gathering-detail-modal/container';
import GatheringDetailModalContainer from '@/src/app/(crew)/crew/detail/[id]/_components/gathering-detail-modal/container';
import { GatheringType } from '@/src/types/gathering-data';
import GatheringCardPresenter from './presenter';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useState } from 'react';
import GatheringDetailModalContainer from '@/src/app/(crew)/crew/_components/gathering-detail-modal/container';
import GatheringDetailModalContainer from '@/src/app/(crew)/crew/detail/[id]/_components/gathering-detail-modal/container';
import ScheduledGatheringCardPresenter from './presenter';

interface ScheduledGatheringCardContainerProps {
Expand Down
1 change: 0 additions & 1 deletion src/components/common/input/date-time-picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export default function DateTimePicker({ fullDate, onChange }: DateTimePickerPro

if (isSelected) {
setSelected(date);
onChange(date);
}
};

Expand Down
Loading