Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: 0 additions & 17 deletions src/_apis/detail/get-gathering-detail.ts

This file was deleted.

67 changes: 0 additions & 67 deletions src/_apis/detail/get-review-list.ts

This file was deleted.

54 changes: 54 additions & 0 deletions src/_apis/gathering/gathering-detail-apis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { fetchApi } from '@/src/utils/api';
import { GatheringDetailType } from '@/src/types/gathering-data';
Comment on lines +1 to +2
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 μ—λŸ¬ 처리 κ°œμ„  ν•„μš”

fetchApi μœ ν‹Έλ¦¬ν‹°λ₯Ό μ‚¬μš©ν•  λ•Œ λ„€νŠΈμ›Œν¬ 였λ₯˜λ‚˜ μ„œλ²„ 였λ₯˜μ— λŒ€ν•œ μ²˜λ¦¬κ°€ λˆ„λ½λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. 각 API ν•¨μˆ˜μ—μ„œ try-catchλ₯Ό μ‚¬μš©ν•˜μ—¬ μ—λŸ¬λ₯Ό 적절히 μ²˜λ¦¬ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μ—λŸ¬ νƒ€μž…μ„ μ •μ˜ν•˜κ³  μ²˜λ¦¬ν•˜λŠ” 것을 μΆ”μ²œλ“œλ¦½λ‹ˆλ‹€:

interface ApiError {
  message: string;
  status: number;
}

// 각 ν•¨μˆ˜μ—μ„œ μ‚¬μš©ν•  μ—λŸ¬ 처리 μ˜ˆμ‹œ
try {
  const response = await fetchApi<{ data: GatheringDetailType }>(url, options);
  return response.data;
} catch (error) {
  throw new Error(`λͺ¨μž„ 상세 정보λ₯Ό κ°€μ Έμ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€: ${error.message}`);
}


// NOTE: 약속 λ””ν…ŒμΌ 뢈러였기
export async function GetGatheringDetail(
crewId: number,
gatheringId: number,
): Promise<GatheringDetailType> {
const url = `/api/crews/${crewId}/gatherings/${gatheringId}`;

const response = await fetchApi<{ data: GatheringDetailType }>(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
return response.data;
}
Comment on lines +4 to +18
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μ—μ„œ μ–ΈκΈ‰λœ 둜그인 토큰 λ¬Έμ œμ™€ κ΄€λ ¨ν•˜μ—¬, Authorization 헀더가 λˆ„λ½λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. 인증이 ν•„μš”ν•œ API ν˜ΈμΆœμ—μ„œλŠ” λ°˜λ“œμ‹œ 토큰을 포함해야 ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•΄μ£Όμ„Έμš”:

  const response = await fetchApi<{ data: GatheringDetailType }>(url, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
+     'Authorization': `Bearer ${getToken()}`,
    },
  });

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


// NOTE: 약속 μ°Έμ—¬
export async function JoinGathering(crewId: number, gatheringId: number): Promise<void> {
const url = `/api/crews/${crewId}/gatherings/${gatheringId}/join`;

await fetchApi(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
}

// NOTE: 약속 μ·¨μ†Œ(host)
export async function CancelGathering(crewId: number, gatheringId: number): Promise<void> {
const url = `/api/crews/${crewId}/gatherings/${gatheringId}`;

await fetchApi(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
}

// NOTE: 약속 μ°Έμ—¬ μ·¨μ†Œ(μ°Έμ—¬μž)
export async function LeaveGathering(crewId: number, gatheringId: number): Promise<void> {
const url = `/api/crews/${crewId}/gatherings/${gatheringId}/leave`;

await fetchApi(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
}
Comment on lines +20 to +54
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 ν•¨μˆ˜λ“€μ˜ 일관성 및 μ•ˆμ •μ„± κ°œμ„  ν•„μš”

λͺ¨λ“  API ν•¨μˆ˜λ“€μ—μ„œ λ‹€μŒ 사항듀이 κ°œμ„ λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€:

  1. μž…λ ₯κ°’ μœ νš¨μ„± 검사가 λˆ„λ½λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€
  2. 인증 헀더가 λͺ¨λ“  μš”μ²­μ— ν•„μš”ν•©λ‹ˆλ‹€
  3. 응닡 νƒ€μž…μ΄ λͺ…μ‹œμ μ΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€

λ‹€μŒκ³Ό 같은 κ°œμ„ μ„ μ œμ•ˆλ“œλ¦½λ‹ˆλ‹€:

// 곡톡 νƒ€μž… μ •μ˜
interface ApiResponse<T> {
  data: T;
  message: string;
  status: number;
}

// μž…λ ₯κ°’ 검증 μœ ν‹Έλ¦¬ν‹°
function validateIds(crewId: number, gatheringId: number) {
  if (!Number.isInteger(crewId) || crewId <= 0) {
    throw new Error('μœ νš¨ν•˜μ§€ μ•Šμ€ 크루 IDμž…λ‹ˆλ‹€');
  }
  if (!Number.isInteger(gatheringId) || gatheringId <= 0) {
    throw new Error('μœ νš¨ν•˜μ§€ μ•Šμ€ λͺ¨μž„ IDμž…λ‹ˆλ‹€');
  }
}

// μ˜ˆμ‹œ ν•¨μˆ˜ κ΅¬ν˜„
export async function JoinGathering(crewId: number, gatheringId: number): Promise<ApiResponse<void>> {
  validateIds(crewId, gatheringId);
  const url = `/api/crews/${crewId}/gatherings/${gatheringId}/join`;

  try {
    return await fetchApi<ApiResponse<void>>(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${getToken()}`,
      },
    });
  } catch (error) {
    throw new Error(`λͺ¨μž„ 참여에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€: ${error.message}`);
  }
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { getCrewDetail } from '@/src/_apis/detail/get-crew-detail';
import { getCrewDetail } from '@/src/_apis/crew/crew-detail-apis';

export function useGetCrewDetailQuery(id: number) {
return useQuery({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { getGatheringList } from '@/src/_apis/detail/get-gathering-list';
import { getGatheringList } from '@/src/_apis/crew/crew-gathering-list-apis';
import { GatheringType } from '@/src/types/gathering-data';

export function useGetGatheringListQuery(id: number) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { GetGatheringDetail } from '@/src/_apis/detail/get-gathering-detail';
import { GetGatheringDetail } from '@/src/_apis/gathering/gathering-detail-apis';

export function useGetGatheringDetailQuery(crewId: number, gatheringId: number) {
return useQuery({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
'use client';

import {
CancelGathering,
JoinGathering,
LeaveGathering,
} from '@/src/_apis/gathering/gathering-detail-apis';
import { ApiError } from '@/src/utils/api';
import Toast from '@/src/components/common/toast';
import { GatheringDetailType } from '@/src/types/gathering-data';
import GatheringDetailModalPresenter from './presenter';

Expand All @@ -9,22 +16,50 @@ export interface GatheringDetailModalContainerProps {
data: GatheringDetailType;
}

// NOTE: ν…ŒμŠ€νŠΈλŠ” 둜그인 ν›„ 토큰이 μ•ˆλ‹΄κ²¨μ„œ μΆ”ν›„ μ§„ν–‰ν•˜κ² μŠ΅λ‹ˆλ‹€!

export default function GatheringDetailModalContainer({
opened,
close,
data,
}: GatheringDetailModalContainerProps) {
const handleJoin = () => {
// TODO : λͺ¨μž„ μ°Έμ—¬ν•˜κΈ° API μ—°κ²°
close();
const showToast = (message: string, type: 'success' | 'error' | 'warning') => {
Toast({ message, type });
};
const handleExit = () => {
// TODO : λͺ¨μž„ νƒˆν‡΄ν•˜κΈ° API μ—°κ²°
close();

const handleJoin = async () => {
try {
await JoinGathering(data.crewId, data.id);
showToast('약속에 μ°Έμ—¬ν–ˆμŠ΅λ‹ˆλ‹€.', 'success');
close();
} catch (error) {
if (error instanceof ApiError) {
showToast(`μ°Έμ—¬ 쀑 μ—λŸ¬ λ°œμƒ: ${error.message}`, 'error');
}
}
};
const handleDelete = () => {
// TODO : λͺ¨μž„ μ‚­μ œν•˜κΈ° API μ—°κ²°
close();

const handleExit = async () => {
try {
await LeaveGathering(data.crewId, data.id);
close();
} catch (error) {
if (error instanceof ApiError) {
showToast(`μ°Έμ—¬ μ·¨μ†Œ 쀑 μ—λŸ¬ λ°œμƒ: ${error.message}`, 'error');
}
}
};

const handleDelete = async () => {
try {
await CancelGathering(data.crewId, data.id);
showToast('약속을 μ‚­μ œν–ˆμŠ΅λ‹ˆλ‹€.', 'success');
close();
} catch (error) {
if (error instanceof ApiError) {
showToast(`약속 μ‚­μ œ 쀑 μ—λŸ¬ λ°œμƒ: ${error.message}`, 'error');
}
}
Comment on lines +30 to +62
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. handleExit ν•¨μˆ˜μ— 성곡 λ©”μ‹œμ§€κ°€ λˆ„λ½λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
  2. μ—λŸ¬ μ²˜λ¦¬κ°€ 각 ν•Έλ“€λŸ¬μ—μ„œ 반볡되고 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같은 κ°œμ„ μ‚¬ν•­μ„ μ œμ•ˆλ“œλ¦½λ‹ˆλ‹€:

   const handleExit = async () => {
     try {
       await LeaveGathering(data.crewId, data.id);
+      showToast('약속 μ°Έμ—¬λ₯Ό μ·¨μ†Œν–ˆμŠ΅λ‹ˆλ‹€.', 'success');
       close();
     } catch (error) {

μ—λŸ¬ 처리λ₯Ό μœ„ν•œ μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜λ₯Ό μΆ”κ°€ν•˜λ©΄ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€:

const handleApiError = (error: unknown, context: string) => {
  if (error instanceof ApiError) {
    showToast(`${context} 쀑 μ—λŸ¬ λ°œμƒ: ${error.message}`, 'error');
  }
};

이λ₯Ό ν™œμš©ν•˜μ—¬ 각 ν•Έλ“€λŸ¬μ˜ μ—λŸ¬ 처리λ₯Ό λ‹¨μˆœν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

   } catch (error) {
-    if (error instanceof ApiError) {
-      showToast(`약속 μ‚­μ œ 쀑 μ—λŸ¬ λ°œμƒ: ${error.message}`, 'error');
-    }
+    handleApiError(error, '약속 μ‚­μ œ');
   }

};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const ModalWithUser = Template.bind({});
ModalWithUser.args = {
opened: false,
data: {
crewId: 1,
id: 1,
title: 'μ‹ λ‚˜λŠ” μš΄λ™...즐거운..μ½”λ”©..',
introduce: 'κ³΅μ§€μ‚¬ν•­μž…λ‹ˆλ‹€. λ‹€λ“€ 이번 약속 μžŠμ§€ μ•ŠμœΌμ…¨μ£ ? κΌ­ μ°Έμ—¬ λΆ€νƒλ“œλ¦½λ‹ˆλ‹€~',
Expand Down Expand Up @@ -97,6 +98,7 @@ export const ModalWithCaptain = Template.bind({});
ModalWithCaptain.args = {
opened: false,
data: {
crewId: 1,
id: 2,
title: 'μ‹ λ‚˜λŠ” μš΄λ™...즐거운..μ½”λ”©..',
introduce: 'κ³΅μ§€μ‚¬ν•­μž…λ‹ˆλ‹€. λ‹€λ“€ 이번 약속 μžŠμ§€ μ•ŠμœΌμ…¨μ£ ? κΌ­ μ°Έμ—¬ λΆ€νƒλ“œλ¦½λ‹ˆλ‹€~',
Expand Down Expand Up @@ -139,6 +141,7 @@ export const ModalWithCrew = Template.bind({});
ModalWithCrew.args = {
opened: false,
data: {
crewId: 1,
id: 3,
title: 'μ•„μΉ¨ νƒ€μž„ μ—λ„ˆμ§€ μš”κ°€',
introduce: 'κ³΅μ§€μ‚¬ν•­μž…λ‹ˆλ‹€. λ‹€λ“€ 이번 약속 μžŠμ§€ μ•ŠμœΌμ…¨μ£ ? κΌ­ μ°Έμ—¬ λΆ€νƒλ“œλ¦½λ‹ˆλ‹€~',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useGetCrewDetailQuery } from '@/src/_queries/detail/crew-detail-queries';
import { useGetCrewDetailQuery } from '@/src/_queries/crew/crew-detail-queries';
import { ApiError } from '@/src/utils/api';
import DetailCrewCard from '@/src/components/common/crew-list/detail-crew-card';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useGetGatheringListQuery } from '@/src/_queries/detail/gathering-list-queries';
import { useGetGatheringListQuery } from '@/src/_queries/crew/gathering-list-queries';
import GatheringCardCarousel from '@/src/components/gathering-list/gathering-card-carousel';

interface GatheringListSectionProps {
Expand Down
23 changes: 4 additions & 19 deletions src/app/(crew)/crew/detail/[id]/_components/review-section.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,23 @@
'use client';

import { useEffect, useState } from 'react';
import { ReviewListData, getReviewList } from '@/src/_apis/detail/get-review-list';
import CrewReviewList from './crew-review-list';
import RatingDisplay from './rating-display';

export default function CrewReviewSection() {
const [reviewData, setReviewData] = useState<ReviewListData | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const limit = 5;

useEffect(() => {
async function fetchReviewData() {
const data = await getReviewList(currentPage, limit);
setReviewData(data);
}
fetchReviewData();
}, [currentPage]);

const handlePageChange = (page: number) => setCurrentPage(page);

if (!reviewData) return <div>Loading...</div>;
// TODO: review μΆ”ν›„ μΆ”κ°€

return (
<div className="space-y-6 rounded-lg bg-white">
<div className="mx-4 flex justify-center py-11">
<RatingDisplay reviewRateInfo={reviewData.info} />
{/* <RatingDisplay reviewRateInfo={reviewData.info} /> */}
</div>
<CrewReviewList
{/* <CrewReviewList
reviews={reviewData.data}
totalPages={reviewData.totalPages}
currentPage={currentPage}
onPageChange={handlePageChange}
/>
/> */}
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/(crew)/crew/detail/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getGatheringList } from '@/src/_apis/detail/get-gathering-list';
import { getGatheringList } from '@/src/_apis/crew/crew-gathering-list-apis';
import CreateGathering from './_components/create-gathering';
import DetailCrewSection from './_components/detail-crew-section';
import GatheringListSection from './_components/gathering-list-section';
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 @@ -2,7 +2,7 @@

import { useEffect, useState } from 'react';
import { useDisclosure } from '@mantine/hooks';
import { useGetGatheringDetailQuery } from '@/src/_queries/detail/gathering-detail-queries';
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 Toast from '@/src/components/common/toast';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default function ScheduledGatheringCardContainer({
// TODO: modalData μ—°κ²°
// const { data: modalData } = useQuery<GatheringDetailType>(useGetGatheringQuery());
const dummyModalData = {
crewId: 7,
id: 1,
title: 'μ‹ λ‚˜λŠ” μš΄λ™...즐거운..μ½”λ”©..',
introduce: 'κ³΅μ§€μ‚¬ν•­μž…λ‹ˆλ‹€. λ‹€λ“€ 이번 약속 μžŠμ§€ μ•ŠμœΌμ…¨μ£ ? κΌ­ μ°Έμ—¬ λΆ€νƒλ“œλ¦½λ‹ˆλ‹€~',
Expand Down
1 change: 1 addition & 0 deletions src/types/gathering-data.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface GatheringType {
liked: boolean;
}
export interface GatheringDetailType extends GatheringType {
crewId: number;
introduce: string;
gatheringCaptain: boolean;
participant: boolean;
Expand Down