-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/74 체험 상세페이지 리뷰 페이지네이션 제외 api 연결 #78
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
Changes from all commits
96d8702
0277194
da0e894
d9cdc53
333b5ce
80871ac
1bc22f6
63c7ed9
cc036ef
c07729c
533b7cb
812c057
4a6b524
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,124 @@ | ||||||||||
| 'use client'; | ||||||||||
|
|
||||||||||
| import { useParams } from 'next/navigation'; | ||||||||||
| import Title from './Title'; | ||||||||||
| import ImageGrid from './ImageGrid'; | ||||||||||
| import BookingInterface from '@/components/FloatingBox/BookingInterface'; | ||||||||||
| import LocationMap from '@/components/LocationMap'; | ||||||||||
| import ReviewTitle from './ReviewTitle'; | ||||||||||
| import { useQuery } from '@tanstack/react-query'; | ||||||||||
| import { privateInstance } from '@/apis/privateInstance'; | ||||||||||
| import { useState, useEffect } from 'react'; | ||||||||||
| import useUserStore from '@/stores/authStore'; | ||||||||||
| import { padMonth } from '../utils/MonthFormatChange'; | ||||||||||
|
|
||||||||||
| export default function ActivityDetailForm() { | ||||||||||
| const [year, setYear] = useState(2025); | ||||||||||
| const [month, setMonth] = useState(7); | ||||||||||
|
Comment on lines
+16
to
+17
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. 하드코딩된 년도와 월을 현재 날짜로 초기화하세요. 2025년 7월로 하드코딩되어 있어, 다른 시기에 사용할 때 문제가 될 수 있습니다. - const [year, setYear] = useState(2025);
- const [month, setMonth] = useState(7);
+ const [year, setYear] = useState(new Date().getFullYear());
+ const [month, setMonth] = useState(new Date().getMonth() + 1);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| const [isOwner, setIsOwner] = useState(false); | ||||||||||
|
|
||||||||||
| const { id } = useParams(); | ||||||||||
|
|
||||||||||
| const { data: activityData, isLoading } = useQuery({ | ||||||||||
| queryKey: ['activity', id], | ||||||||||
| queryFn: async () => { | ||||||||||
| return privateInstance.get(`/activities/${id}`); | ||||||||||
| }, | ||||||||||
| select: (response) => response.data, | ||||||||||
| enabled: !!id, | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| const userId = activityData?.userId; | ||||||||||
|
|
||||||||||
| const currentUserId = useUserStore((state) => | ||||||||||
| state.user ? state.user.id : null, | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| useEffect(() => { | ||||||||||
| if (currentUserId && currentUserId === userId) { | ||||||||||
| setIsOwner(true); | ||||||||||
| console.log('니가 작성한 체험임'); | ||||||||||
|
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. 🧹 Nitpick (assertive) 디버그용 console.log를 제거하세요. 프로덕션 코드에서 디버그 로그는 제거해야 합니다. - console.log('니가 작성한 체험임');📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| } else { | ||||||||||
| setIsOwner(false); | ||||||||||
| } | ||||||||||
| }, [currentUserId, userId]); | ||||||||||
|
|
||||||||||
| const { data: schedulesData } = useQuery({ | ||||||||||
| queryKey: ['available-schedule', id, year, month], | ||||||||||
| queryFn: async () => { | ||||||||||
| const prevMonth = month === 1 ? 12 : month - 1; | ||||||||||
| const prevYear = month === 1 ? year - 1 : year; | ||||||||||
| const nextMonth = month === 12 ? 1 : month + 1; | ||||||||||
| const nextYear = month === 12 ? year + 1 : year; | ||||||||||
|
|
||||||||||
| const results = await Promise.allSettled([ | ||||||||||
| privateInstance.get( | ||||||||||
| `/activities/${id}/available-schedule?year=${prevYear}&month=${padMonth(prevMonth)}`, | ||||||||||
| ), | ||||||||||
| privateInstance.get( | ||||||||||
| `/activities/${id}/available-schedule?year=${year}&month=${padMonth(month)}`, | ||||||||||
| ), | ||||||||||
| privateInstance.get( | ||||||||||
| `/activities/${id}/available-schedule?year=${nextYear}&month=${padMonth(nextMonth)}`, | ||||||||||
| ), | ||||||||||
| ]); | ||||||||||
| // 성공한 것만 합치기 | ||||||||||
| const data = results | ||||||||||
| .filter((r) => r.status === 'fulfilled') | ||||||||||
| .flatMap((r) => (r.status === 'fulfilled' ? r.value.data : [])); | ||||||||||
| return data; | ||||||||||
| }, | ||||||||||
| enabled: !!id && !!year && !!month, | ||||||||||
| }); | ||||||||||
|
Comment on lines
+46
to
+72
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. 🧹 Nitpick (assertive) 복잡한 스케줄 조회 로직을 별도 함수로 분리하는 것을 고려해보세요. 3개월치 스케줄을 조회하는 로직이 복잡합니다. 가독성과 재사용성을 위해 유틸 함수로 분리하는 것을 권장합니다. 별도 유틸 함수 예시: // utils/scheduleUtils.ts
export const fetchMultipleMonthSchedules = async (
id: string,
year: number,
month: number
) => {
const prevMonth = month === 1 ? 12 : month - 1;
const prevYear = month === 1 ? year - 1 : year;
const nextMonth = month === 12 ? 1 : month + 1;
const nextYear = month === 12 ? year + 1 : year;
const results = await Promise.allSettled([
privateInstance.get(`/activities/${id}/available-schedule?year=${prevYear}&month=${padMonth(prevMonth)}`),
privateInstance.get(`/activities/${id}/available-schedule?year=${year}&month=${padMonth(month)}`),
privateInstance.get(`/activities/${id}/available-schedule?year=${nextYear}&month=${padMonth(nextMonth)}`),
]);
return results
.filter((r) => r.status === 'fulfilled')
.flatMap((r) => (r.status === 'fulfilled' ? r.value.data : []));
};🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| if (isLoading || !activityData) { | ||||||||||
| return <div>로딩 중...</div>; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const subImageUrls = activityData.subImages.map( | ||||||||||
| (image: { imageUrl: string }) => image.imageUrl, | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| return ( | ||||||||||
| <div className='mx-auto max-w-1200 p-4 sm:px-20 lg:p-8'> | ||||||||||
| <Title {...activityData} isOwner={isOwner} /> | ||||||||||
| <ImageGrid | ||||||||||
| mainImage={activityData.bannerImageUrl} | ||||||||||
| subImages={subImageUrls} | ||||||||||
| /> | ||||||||||
|
|
||||||||||
| <div | ||||||||||
| className={`mt-86 grid gap-15 ${ | ||||||||||
| isOwner ? 'md:grid-cols-2' : 'md:grid-cols-3' | ||||||||||
| } grid-cols-1`} | ||||||||||
| > | ||||||||||
| <div className={`${isOwner ? 'md:col-span-2' : 'md:col-span-2'}`}> | ||||||||||
| <h2 className='mb-4 pb-2 text-2xl font-bold'>체험 설명</h2> | ||||||||||
| <p className='whitespace-pre-line'>{activityData.description}</p> | ||||||||||
| </div> | ||||||||||
|
|
||||||||||
| {!isOwner && ( | ||||||||||
| <div className='md:row-span-2'> | ||||||||||
| <BookingInterface | ||||||||||
| schedules={schedulesData ?? []} | ||||||||||
| onMonthChange={(year, month) => { | ||||||||||
| setTimeout(() => { | ||||||||||
| setYear(year); | ||||||||||
| setMonth(month); | ||||||||||
| }, 0); | ||||||||||
| }} | ||||||||||
| isOwner={isOwner} | ||||||||||
| price={activityData.price} | ||||||||||
| /> | ||||||||||
| </div> | ||||||||||
| )} | ||||||||||
|
|
||||||||||
| <div className={`${isOwner ? 'md:col-span-4' : 'md:col-span-2'}`}> | ||||||||||
| <h2 className='mb-4 pb-2 text-2xl font-bold'>체험 장소</h2> | ||||||||||
| <LocationMap address={activityData.address} /> | ||||||||||
| <ReviewTitle /> | ||||||||||
| </div> | ||||||||||
| </div> | ||||||||||
| </div> | ||||||||||
| ); | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
|
|
||
| export function Skeleton({ className = '' }: { className?: string }) { | ||
| return <div className={`animate-pulse bg-gray-200 ${className}`} />; | ||
| } | ||
|
Comment on lines
+2
to
+4
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. 🧹 Nitpick (assertive) 기본적인 스켈레톤 컴포넌트가 잘 구현되었습니다. 로딩 상태를 위한 간단하고 효과적인 구현입니다. 더 나은 className 처리와 유연성을 위해 다음과 같은 개선을 고려해보세요: import { cn } from '@/lib/utils'; // or use clsx
export function Skeleton({
className = '',
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse bg-gray-200", className)}
{...props}
/>
);
}이렇게 하면 더 안전한 className 병합과 추가 HTML 속성 지원이 가능합니다. 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,16 +1,20 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import IconDropdown from '@assets/svg/dropdown'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Star from '@assets/svg/star'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TitleProps } from '@/types/activityDetailType'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ActivityDetail } from '@/types/activityDetailType'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ActivityDropdown from '@/components/ActivityDropdown'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Menu from '@/components/ActivityDropdown/menu'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Item from '@/components/ActivityDropdown/Item'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Trigger from '@/components/ActivityDropdown/trigger'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+4
to
+8
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. 🧹 Nitpick (assertive) Import 구조 개선 가능 모든 ActivityDropdown 관련 컴포넌트들을 개별적으로 import하는 대신, 더 깔끔한 구조로 정리할 수 있습니다. -import { ActivityDetail } from '@/types/activityDetailType';
-import ActivityDropdown from '@/components/ActivityDropdown';
-import Menu from '@/components/ActivityDropdown/menu';
-import Item from '@/components/ActivityDropdown/Item';
-import Trigger from '@/components/ActivityDropdown/trigger';
+import { ActivityDetail } from '@/types/activityDetailType';
+import ActivityDropdown from '@/components/ActivityDropdown';그리고 사용 시에는: - <ActivityDropdown>
- <Trigger>
+ <ActivityDropdown>
+ <ActivityDropdown.Trigger>
<IconDropdown />
- </Trigger>
- <Menu>
- <Item onClick={() => alert('수정')}>수정하기</Item>
- <Item onClick={() => alert('삭제')}>삭제하기</Item>
- </Menu>
+ </ActivityDropdown.Trigger>
+ <ActivityDropdown.Menu>
+ <ActivityDropdown.Item onClick={() => alert('수정')}>수정하기</ActivityDropdown.Item>
+ <ActivityDropdown.Item onClick={() => alert('삭제')}>삭제하기</ActivityDropdown.Item>
+ </ActivityDropdown.Menu>
</ActivityDropdown>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function Title({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| category, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rating, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reviewCount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| address, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isDropDown, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: TitleProps) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isOwner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: ActivityDetail) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='mb-6 flex items-start justify-between'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='flex flex-col gap-8'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -29,10 +33,16 @@ export default function Title({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isDropDown && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='mt-30 flex items-center gap-1'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <IconDropdown /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isOwner && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ActivityDropdown> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Trigger> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <IconDropdown /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Trigger> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Menu> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Item onClick={() => alert('수정')}>수정하기</Item> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Item onClick={() => alert('삭제')}>삭제하기</Item> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+43
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. 🧹 Nitpick (assertive) 임시 alert 핸들러를 실제 구현으로 교체 필요 현재 수정/삭제 기능이 alert로만 구현되어 있습니다. 실제 기능 구현이 필요합니다. 실제 수정/삭제 API 호출 로직을 구현하시겠습니까? 다음과 같은 구조를 제안드립니다: const handleEdit = () => {
// 수정 페이지로 라우팅 또는 모달 열기
router.push(`/activities/${activityId}/edit`);
};
const handleDelete = async () => {
if (confirm('정말 삭제하시겠습니까?')) {
try {
await deleteActivity(activityId);
router.push('/activities');
} catch (error) {
// 에러 처리
}
}
};🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Menu> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </ActivityDropdown> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import ActivityDetailForm from './components/ActivityDetailForm'; | ||
|
|
||
| export default function ActivityDetailPage() { | ||
| return <ActivityDetailForm />; | ||
| } | ||
|
Comment on lines
+1
to
+5
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. 🧹 Nitpick (assertive) 깔끔한 페이지 구조가 잘 구현되었습니다. Next.js 페이지 컴포넌트 패턴을 잘 따르고 있으며, 실제 렌더링 로직을 별도 컴포넌트로 분리한 것이 좋습니다. 향후 고려사항으로, ActivityDetailForm에서 발생할 수 있는 에러를 처리하기 위해 Error Boundary를 추가하는 것을 검토해보세요: import { Suspense } from 'react';
import ActivityDetailForm from './components/ActivityDetailForm';
import { Skeleton } from './components/Skeleton';
export default function ActivityDetailPage() {
return (
<Suspense fallback={<Skeleton className="h-screen" />}>
<ActivityDetailForm />
</Suspense>
);
}🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,3 @@ | ||||||||||||||||||||
| export function padMonth(m: number): string { | ||||||||||||||||||||
| return m.toString().padStart(2, '0'); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+1
to
+3
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. 🧹 Nitpick (assertive) 월 포맷팅 유틸리티 함수 개선 제안 기본 로직은 올바르지만 개선할 부분이 있습니다. 함수명과 입력 검증을 개선해보세요: -export function padMonth(m: number): string {
- return m.toString().padStart(2, '0');
-}
+export function padMonthWithZero(month: number): string {
+ if (month < 1 || month > 12) {
+ throw new Error('Month must be between 1 and 12');
+ }
+ return month.toString().padStart(2, '0');
+}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import axios, { AxiosError } from 'axios'; | ||
| import { cookies } from 'next/headers'; | ||
| import { NextRequest, NextResponse } from 'next/server'; | ||
|
|
||
| interface ErrorResponse { | ||
| error?: string; | ||
| message?: string; | ||
| } | ||
|
|
||
| export async function GET(request: NextRequest) { | ||
| const cookieStore = await cookies(); | ||
| const accessToken = cookieStore.get('accessToken')?.value; | ||
|
|
||
| const { searchParams, pathname } = request.nextUrl; | ||
|
|
||
| const year = searchParams.get('year'); | ||
| const month = searchParams.get('month'); | ||
|
|
||
| const segments = pathname.split('/'); | ||
| const id = segments[segments.indexOf('activities') + 1]; | ||
|
|
||
| if (!id) { | ||
| return NextResponse.json( | ||
| { error: '유효하지 않은 요청입니다.' }, | ||
| { status: 400 }, | ||
| ); | ||
| } | ||
|
|
||
| try { | ||
| const response = await axios.get( | ||
| `${process.env.NEXT_PUBLIC_API_SERVER_URL}/activities/${id}/available-schedule?year=${year}&month=${month}`, | ||
| { | ||
| headers: { | ||
| Authorization: accessToken ? `Bearer ${accessToken}` : undefined, | ||
| }, | ||
| }, | ||
| ); | ||
| return NextResponse.json(response.data); | ||
| } catch (err) { | ||
| const error = err as AxiosError<ErrorResponse>; | ||
|
|
||
| const message = | ||
| error.response?.data?.error || | ||
| error.response?.data?.message || | ||
| '스케줄 데이터 조회 실패'; | ||
|
|
||
| const status = error.response?.status || 500; | ||
|
|
||
| return NextResponse.json({ error: message }, { status }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import axios, { AxiosError } from 'axios'; | ||
| import { cookies } from 'next/headers'; | ||
| import { NextRequest, NextResponse } from 'next/server'; | ||
|
|
||
| interface ErrorResponse { | ||
| error?: string; | ||
| message?: string; | ||
| } | ||
|
|
||
| export async function POST(request: NextRequest) { | ||
| const cookieStore = await cookies(); | ||
| const accessToken = cookieStore.get('accessToken')?.value; | ||
|
|
||
| const { selectedTimeId, participants } = await request.json(); | ||
|
|
||
| const pathname = request.nextUrl.pathname; | ||
| const segments = pathname.split('/'); | ||
| const id = segments[segments.indexOf('activities') + 1]; | ||
|
|
||
| if (!id) { | ||
| return NextResponse.json( | ||
| { error: '유효하지 않은 요청입니다.' }, | ||
| { status: 400 }, | ||
| ); | ||
| } | ||
|
|
||
| try { | ||
| const response = await axios.post( | ||
| `${process.env.NEXT_PUBLIC_API_SERVER_URL}/activities/${id}/reservations`, | ||
| { | ||
| scheduleId: Number(selectedTimeId), | ||
| headCount: Number(participants), | ||
| }, | ||
| { | ||
| headers: { | ||
| Authorization: accessToken ? `Bearer ${accessToken}` : undefined, | ||
| }, | ||
| }, | ||
| ); | ||
| return NextResponse.json(response.data); | ||
| } catch (err) { | ||
| const error = err as AxiosError<ErrorResponse>; | ||
|
|
||
| const message = | ||
| error.response?.data?.error || | ||
| error.response?.data?.message || | ||
| '예약 실패'; | ||
|
|
||
| const status = error.response?.status || 500; | ||
| return NextResponse.json({ error: message }, { status }); | ||
| } | ||
| } |
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.
🧹 Nitpick (assertive)
외부 이미지 도메인 설정 승인 및 보안 고려사항
S3 버킷의 이미지 최적화를 위한 올바른 설정입니다.
보안과 환경 분리를 위해 환경변수 사용을 고려해보세요:
images: { - domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'], + domains: [process.env.NEXT_PUBLIC_IMAGE_DOMAIN || 'sprint-fe-project.s3.ap-northeast-2.amazonaws.com'], },📝 Committable suggestion
🤖 Prompt for AI Agents