Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
eb3e27a
LIN-75 refactor: 테마 토글버튼 플로팅버튼으로 병합
10000jaeN Sep 10, 2025
f6cabe9
LIN-75 chore: 영화 추가버튼 툴팁 메세지 변경,,,
10000jaeN Sep 10, 2025
50dcfb7
LIN-75 chore: 텍스트에리어 라이트모드 적용
10000jaeN Sep 10, 2025
bb4ab93
LIN-75 refactor: 넥스트 컨피그에 도메인 추가
10000jaeN Sep 11, 2025
9b3eb39
LIN-75 refactor: 이미지 최적화
10000jaeN Sep 11, 2025
50edc9d
LIN-75 refactor: 리뷰 서버액션 no-store -> no-cache 로 변경
10000jaeN Sep 11, 2025
e03d8be
LIN-75 refactor: 파일인풋 사이즈 원복 및 폼 여백 재조정
10000jaeN Sep 11, 2025
70fb49d
LIN-75 refactor: 리뷰 이미지 클릭시 프리뷰 제공
10000jaeN Sep 11, 2025
ed6cd85
LIN-75 refactor: 리뷰 이미지 클릭시 프리뷰 제공 사이즈 조정
10000jaeN Sep 11, 2025
a2e7d95
LIN-75 refactor: 플로팅버튼 호버 색상 적용
10000jaeN Sep 11, 2025
5a4fad9
LIN-75 chore: 의존성배열 린트 경고 무시 주석 추가
10000jaeN Sep 11, 2025
3914d1e
LIN-75 chore: productPost 에러상태 핸들링 추가
10000jaeN Sep 11, 2025
fcfbbbb
LIN-75 chore: 플로팅버튼 원복
10000jaeN Sep 11, 2025
dbbbed6
LIN-75 chore: 헤더 로고사이즈 변경
10000jaeN Sep 11, 2025
3e7c823
LIN-75 chore: 라이트모드 제거
10000jaeN Sep 11, 2025
3c58aaa
LIN-75 chore: 라이트모드 제거
10000jaeN Sep 11, 2025
f930af4
LIN-75 chore: 레이아웃 충돌 해결
10000jaeN Sep 11, 2025
769b21e
Merge branch 'dev' into refactor/LIN-75-상세페이지-LCP-최적화
10000jaeN Sep 11, 2025
dfd8e55
Update layout.tsx
10000jaeN Sep 11, 2025
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
4 changes: 4 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ const nextConfig: NextConfig = {
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'picsum.photos',
},
],
domains: ['mogazoa-api.vercel.app', 'example.com'],
},
Expand Down
6 changes: 3 additions & 3 deletions src/actions/review/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const postReview = async ({
'Content-Type': 'application/json',
},
body: JSON.stringify(newReview),
cache: 'no-store',
cache: 'no-cache',
});

revalidatePath(`/products/${productId}`);
Expand All @@ -84,7 +84,7 @@ export const patchReview = async ({ rating, content, images, reviewId }: ReviewP
'Content-Type': 'application/json',
},
body: JSON.stringify(newReview),
cache: 'no-store',
cache: 'no-cache',
});

revalidateTag('reviews');
Expand All @@ -101,7 +101,7 @@ export const deleteReview = async (reviewId: number, productId: number) => {
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
cache: 'no-store',
cache: 'no-cache',
},
});

Expand Down
18 changes: 10 additions & 8 deletions src/app/_components/ProductPost/ProductForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client';

import { useState } from 'react';

import { zodResolver } from '@hookform/resolvers/zod';
import { useErrorBoundary } from 'react-error-boundary';
import { Controller, useForm } from 'react-hook-form';

import { patchProduct, postProduct } from '@/actions/productDetail';
Expand All @@ -16,15 +17,15 @@ import { productSchema } from '@/types/product/productSchema';
import { ProductDetail, ProductFormValue } from '@/types/product/productType';

const ProductForm = ({ product, mode }: { product: ProductDetail; mode: 'create' | 'edit' }) => {
const { showBoundary } = useErrorBoundary();
const [isError, setIsError] = useState(false);
const closeModal = useModalStore((state) => state.closeModal);
const openModal = useModalStore((state) => state.openModal);

const {
register,
handleSubmit,
control,
formState: { errors, isValid, isSubmitting, isDirty },
formState: { errors, isValid, isLoading, isDirty },
} = useForm<ProductFormValue>({
resolver: zodResolver(productSchema),
mode: 'all',
Expand Down Expand Up @@ -53,8 +54,8 @@ const ProductForm = ({ product, mode }: { product: ProductDetail; mode: 'create'
await patchProduct({ productId: product.id, data });
}
closeModal();
} catch (err) {
showBoundary(err);
} catch {
setIsError(true);
}
};

Expand All @@ -72,11 +73,12 @@ const ProductForm = ({ product, mode }: { product: ProductDetail; mode: 'create'
<FileInput value={field.value ?? []} onChange={field.onChange} maxFiles={1} />
)}
/>
<div className='flex flex-col gap-[10px] md:flex-1 md:gap-3'>
<div className='flex flex-col gap-[10px] md:flex-1 md:gap-4'>
<Input
placeholder='작품 제목'
{...register('name')}
errorMessage={errors.name?.message}
setError={setIsError}
/>
<Controller
name='categoryId'
Expand All @@ -95,9 +97,9 @@ const ProductForm = ({ product, mode }: { product: ProductDetail; mode: 'create'
<Button
variant='primary'
className='mt-6'
disabled={!isValid || isSubmitting || (mode === 'edit' && !isDirty)}
disabled={!isValid || isLoading || isError || (mode === 'edit' && !isDirty)}
>
{mode === 'create' ? '추가하기' : '수정하기'}
{isError ? '존재하는 영화 제목 입니다.' : mode === 'create' ? '추가하기' : '수정하기'}
</Button>
{mode === 'edit' && (
<Button
Expand Down
2 changes: 1 addition & 1 deletion src/app/_components/reviewer/ReviewerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const ReviewerCard = ({
<div className='flex gap-[7px]'>
<RankingChip idx={rankIdx} />
<h3
className='text-mogazoa-14px-400 xl:text-mogazoa-16px-400 text-white-f1f1f5 light:text-gray-6e6e82 max-w-20 truncate xl:max-w-24'
className='text-mogazoa-14px-400 xl:text-mogazoa-16px-400 text-white-f1f1f5 max-w-20 truncate xl:max-w-24'
title={nickname}
>
{nickname}
Expand Down
2 changes: 0 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import FooterLazy from '@/components/common/Footer/FooterLazy';
import GlobalNav from '@/components/common/gnb/GlobalNav';
import ModalContainer from '@/components/common/ModalContainer';
import SonnerToast from '@/components/common/SonnerToast';
import ThemeToggle from '@/components/common/ThemeToggle';
import FloatingButton from '@/components/ui/FloatingButton';

import pretendard from '../lib/utils/fonts/pretendard';
Expand Down Expand Up @@ -35,7 +34,6 @@ export default async function RootLayout({ children }: Readonly<{ children: Reac
<GlobalNav />
{children}
<FooterLazy />
<ThemeToggle />
<FloatingButton />
<SonnerToast />
<SpeedInsights />
Expand Down
2 changes: 1 addition & 1 deletion src/app/products/[productId]/components/MetricCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const MetricCard = ({ variant, product }: MetricCardProps) => {
const comparisonResult = `${comparisonCount && Math.abs(comparisonCount)}${unit}`;

return (
<div className='bg-black-252530 light:bg-white border-black-353542 flex h-[82px] w-full flex-col gap-[5px] rounded-[12px] border-[1px] p-5 transition-normal duration-300 md:h-[169px] md:items-center md:justify-center md:gap-[15px] xl:h-[190px]'>
<div className='bg-black-252530 border-black-353542 flex h-[82px] w-full flex-col gap-[5px] rounded-[12px] border-[1px] p-5 transition-normal duration-300 md:h-[169px] md:items-center md:justify-center md:gap-[15px] xl:h-[190px]'>
<div className='flex gap-[10px] md:flex-col md:items-center'>
<h3 className='text-mogazoa-14px-500 md:text-mogazoa-16px-500 xl:text-mogazoa-18px-500'>
{title}
Expand Down
28 changes: 28 additions & 0 deletions src/app/products/[productId]/components/PreviewModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import Image from 'next/image';

import Modal from '@/components/common/ModalUi';
import { DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';

const PreviewModal = ({ image }: { image: string }) => {
return (
<Modal className='px-7 pb-7'>
<DialogHeader>
<DialogTitle>Preview</DialogTitle>
</DialogHeader>
<DialogDescription>클릭한 리뷰 이미지를 확대해서 보여주는 모달입니다.</DialogDescription>
<div className='flex h-full w-full justify-center'>
<Image
src={image}
alt='리뷰 이미지'
width={295}
height={295}
className='h-auto w-2/3 object-center'
/>
</div>
</Modal>
);
};

export default PreviewModal;
14 changes: 10 additions & 4 deletions src/app/products/[productId]/components/ReviewAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import Image from 'next/image';
import Link from 'next/link';

import RatingIcon from '@/assets/icon/Icon-star.svg';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Avatar } from '@/components/ui/avatar';
import { userType } from '@/types/review/review';

const ReviewAvatar = ({ user, rating }: { user: userType; rating: number }) => {
const maxRating = 5;

return (
<Link href={`/user/${user.id}`} className='flex w-60 gap-[10px]'>
<Avatar className='h-9 w-9 transition-normal duration-300 xl:h-11 xl:w-11'>
<AvatarImage src={user.image ?? undefined} alt={`profile image for ${user.nickname}`} />
<AvatarFallback>{user.nickname.charAt(0)}</AvatarFallback>
<Avatar className='bg-gray-9fa6b2 relative h-9 w-9 transition-normal duration-300 xl:h-11 xl:w-11'>
<Image
src={user.image ?? '/images/default-profile.png'}
alt={`profile image for ${user.nickname}`}
width={36}
height={36}
className='h-auto w-full object-cover'
/>
</Avatar>
<div className='flex shrink-0 flex-col gap-0.5'>
{user.nickname.slice(0, 10)}
Expand Down
17 changes: 14 additions & 3 deletions src/app/products/[productId]/components/ReviewCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ThumbChip from '@/components/ui/chips/ThumbChip';
import { useModalStore } from '@/store/modalStore';
import { ReviewCardProps } from '@/types/review/review';

import PreviewModal from './PreviewModal';
import ReviewAvatar from './ReviewAvatar';
import ReviewDeleteMessageModal from './ReviewDeleteMessageModal';
import ReviewModal from './ReviewModal';
Expand Down Expand Up @@ -39,8 +40,12 @@ const ReviewCard = ({ review }: ReviewCardProps) => {
return openModal({ component: ReviewDeleteMessageModal, props: { reviewId: review.id } });
};

const handleClickPreviewModal = (imageSrc: string) => {
return openModal({ component: PreviewModal, props: { image: imageSrc } });
};

return (
<div className='bg-black-252530 light:bg-white border-black-353542 flex w-full flex-col gap-5 rounded-[8px] border-[1px] p-5 transition-normal duration-300 md:flex-row'>
<div className='bg-black-252530 border-black-353542 flex w-full flex-col gap-5 rounded-[8px] border-[1px] p-5 transition-normal duration-300 md:flex-row'>
<ReviewAvatar user={review.user} rating={review.rating} />
<div className='flex w-full flex-col gap-5'>
<div className='text-mogazoa-12px-400'>{review.content}</div>
Expand All @@ -49,9 +54,15 @@ const ReviewCard = ({ review }: ReviewCardProps) => {
{filteredImages.map((ri) => (
<div
key={ri.id}
className='relative h-15 w-15 overflow-hidden rounded-[12px] md:h-20 md:w-20 xl:h-25 xl:w-25'
onClick={() => handleClickPreviewModal(ri.source as string)}
className='relative h-15 w-15 cursor-pointer overflow-hidden rounded-[12px] md:h-20 md:w-20 xl:h-25 xl:w-25'
>
<Image src={ri.source as string} alt='리뷰 이미지' fill className='object-cover' />
<Image
src={(ri.source as string) ?? '/images/noImage.png'}
alt='리뷰 이미지'
fill
className='object-cover'
/>
</div>
))}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/products/[productId]/components/ReviewPostForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const ReviewPostForm = ({ productId }: { productId: number }) => {
<StarRating
value={field.value}
onChange={field.onChange}
className={errors.rating ? 'border-red-ff0000 animate-shake border-[1px]' : ''}
className={errors.rating ? 'border-red-ff0000 animate-shake' : ''}
/>
)}
/>
Expand Down
4 changes: 3 additions & 1 deletion src/app/products/[productId]/components/ReviewSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const ReviewSection = ({

useEffect(() => {
reset();
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [productId, trigger]);

useEffect(() => {
Expand All @@ -56,11 +57,12 @@ const ReviewSection = ({
const y = sectionRef.current!.getBoundingClientRect().top + window.pageYOffset - topOffset;

window.scrollTo({ top: y, behavior: 'smooth' });
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [option]);

return (
<section ref={sectionRef}>
<div className='bg-black-1c1c22 sticky top-17 z-5 flex justify-between py-5 shadow-lg md:top-20 xl:top-24'>
<div className='bg-black-1c1c22 sticky top-17 z-5 flex justify-between py-5 md:top-20 xl:top-24'>
<h2 className='text-mogazoa-18px-600 xl:text-mogazoa-20px-600'>상품리뷰</h2>
<SortDropdown variant='review' onChange={handleChangeOption} option={option} />
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/products/[productId]/components/ShareButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const ShareButton = ({
};

const baseClassName =
'bg-black-252530 light:bg-white flex h-6 w-6 items-center justify-center rounded-[6px] md:h-7 md:w-7 xl:h-8 xl:w-8';
'bg-black-252530 flex h-6 w-6 items-center justify-center rounded-[6px] md:h-7 md:w-7 xl:h-8 xl:w-8';

return (
<>
Expand Down
10 changes: 9 additions & 1 deletion src/app/products/[productId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,15 @@ const ProductIdPage = async ({ params }: ProductIdPageProps) => {
<div className='mx-auto max-w-250 px-5 py-10'>
<header className='flex w-full flex-col md:max-h-[350px] md:flex-row'>
<div className='relative mx-auto aspect-[5/7] w-full shrink-0 transition-normal duration-300 md:max-w-[250px]'>
<Image src={posterImage} alt='영화 포스터' fill priority className='object-cover' />
<Image
src={posterImage}
alt='영화 포스터'
width={500}
height={700}
priority
fetchPriority='high'
className='h-full w-full object-cover'
/>
</div>
<div className='mt-5 flex w-full flex-col gap-[10px] md:mt-0 md:pl-5'>
<div className='flex justify-between'>
Expand Down
3 changes: 2 additions & 1 deletion src/components/common/FileInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ const FileInput = ({ maxFiles = 1, value, onChange }: FileInputProps) => {
'flex shrink-0 cursor-pointer items-center justify-center',
'border-black-353542 bg-black-252530 border',
'aspect-square w-30 rounded-[8px]',
'md:w-40',
'md:w-[135px]',
'xl:w-40',
)}
onClick={() => fileInputRef.current?.click()}
>
Expand Down
7 changes: 6 additions & 1 deletion src/components/common/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@
errorMessage?: string;
hintMessage?: string;
maxLength?: number;
setError?: (state: boolean) => void;
}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, id, label, errorMessage, hintMessage, maxLength, ...props }, ref) => {
(
{ className, type, id, label, errorMessage, hintMessage, maxLength, setError, ...props },
ref,
) => {
const generatedId = React.useId();
const inputId = id || generatedId;
const hasError = !!errorMessage;
Expand Down Expand Up @@ -52,6 +56,7 @@
if (maxLength) e.target.value = truncated(e.target.value, maxLength);
return e.target.value.length;
});
setError && setError(false);

Check warning on line 59 in src/components/common/Input.tsx

View workflow job for this annotation

GitHub Actions / build-and-test

Expected an assignment or function call and instead saw an expression
props.onChange?.(e);
}}
/>
Expand Down
51 changes: 0 additions & 51 deletions src/components/common/ThemeToggle.tsx

This file was deleted.

Loading