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
12 changes: 9 additions & 3 deletions src/actions/productDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ export const getProductDetail = async (productId: number): Promise<ProductDetail
const session = await auth();
const accessToken = session?.accessToken;

const headers: Record<string, string> = {
'Content-Type': 'application/json',
};

if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`;
}

const productDetail = await fetcher(`${BASE_URL}/${TEAM_ID}/products/${productId}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
},
headers,
next: { revalidate: 300, tags: [`products-${productId}`] },
});
return productDetail;
Expand Down
13 changes: 9 additions & 4 deletions src/actions/review/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ export const getProductReviews = async (
const session = await auth();
const accessToken = session?.accessToken;

const headers: Record<string, string> = {
'Content-Type': 'application/json',
};

if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`;
}

const productReviews: ReviewResponse = await fetcher(
`${BASE_URL}/${TEAM_ID}/products/${productId}/reviews?order=${option}&cursor=${cursorId}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
headers,
next: { revalidate: 300, tags: [`reviews`] },
cache: 'force-cache',
},
Expand Down
3 changes: 2 additions & 1 deletion src/app/_components/ProductPost/ProductForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import FileInput from '@/components/common/FileInput';
import Input from '@/components/common/Input';
import Textbox from '@/components/common/Textbox';
import Button from '@/components/ui/Buttons';
import { cn } from '@/lib/utils';
import { useModalStore } from '@/store/modalStore';
import { productSchema } from '@/types/product/productSchema';
import { ProductDetail, ProductFormValue } from '@/types/product/productType';
Expand Down Expand Up @@ -96,7 +97,7 @@ const ProductForm = ({ product, mode }: { product: ProductDetail; mode: 'create'
/>
<Button
variant='primary'
className='mt-6'
className={cn('mt-6', isError ? 'bg-black-353542 !cursor-default' : '')}
disabled={!isValid || isLoading || isError || (mode === 'edit' && !isDirty)}
>
{isError ? '존재하는 영화 제목 입니다.' : mode === 'create' ? '추가하기' : '수정하기'}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import { useSession } from 'next-auth/react';
import { toast } from 'sonner';

import CompareConfirmModal from '@/app/compare/components/CompareConfirmModal';
Expand All @@ -18,10 +19,18 @@ const AddToCompareButton = ({ product, className }: AddToCompareButtonProps) =>
const { compareList, addProduct } = useCompareStore();
const { openModal } = useModalStore();

const session = useSession();

const handleAddToCompare = () => {
const currentCount = compareList.length;
const result = addProduct(product.id);

// 로그아웃 상태면 로그인 유도 토스트
if (!session.data) {
toast.error('로그인이 필요합니다.');
return;
}

// 이미 담긴 상품인지 확인
if (result.isDuplicate) {
toast.info('이미 저장된 항목입니다. "비교하기"에서 확인해주세요.');
Expand Down
10 changes: 9 additions & 1 deletion src/app/products/[productId]/components/ProductTriggers.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { useSession } from 'next-auth/react';
import { toast } from 'sonner';

import ProductModal from '@/app/_components/ProductPost/ProductModal';
import Button from '@/components/ui/Buttons';
Expand All @@ -17,10 +18,17 @@ const ProductTriggers = ({ product }: { product: ProductDetail }) => {
const { data } = useSession();
const userId = Number(data?.user.id);

const session = useSession();

setProduct(product);

const handleClickPostReviewModal = () => {
return openModal({ component: ReviewPostModal, props: { mode: 'create' } });
console.log(session);
if (!session.data) {
return toast.error('로그인이 필요합니다');
} else {
return openModal({ component: ReviewPostModal, props: { mode: 'create' } });
}
};

const handleClickPatchProductModal = () => {
Expand Down
10 changes: 8 additions & 2 deletions src/app/products/[productId]/components/ReviewPostForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { postReview } from '@/actions/review/review';
import FileInput from '@/components/common/FileInput';
import Textbox from '@/components/common/Textbox';
import Button from '@/components/ui/Buttons';
import { cn } from '@/lib/utils';
import { useModalStore } from '@/store/modalStore';
import { triggerStore } from '@/store/triggerStore';
import { ReviewFormValue } from '@/types/review/review';
Expand All @@ -22,7 +23,7 @@ const ReviewPostForm = ({ productId }: { productId: number }) => {
register,
handleSubmit,
control,
formState: { isSubmitting, errors },
formState: { isLoading, errors },
} = useForm<ReviewFormValue>({
resolver: zodResolver(postReviewSchema),
mode: 'onChange',
Expand Down Expand Up @@ -71,7 +72,12 @@ const ReviewPostForm = ({ productId }: { productId: number }) => {
)}
/>
</div>
<Button variant='primary' type='submit' disabled={isSubmitting} className='mt-[15px]'>
<Button
variant='primary'
type='submit'
disabled={isLoading}
className={cn('mt-[15px]', errors.rating?.message ? 'bg-black-353542 !cursor-default' : '')}
>
{(errors && (errors.content?.message ?? errors.rating?.message)) || '리뷰 등록하기'}
</Button>
</form>
Expand Down
5 changes: 0 additions & 5 deletions src/app/products/[productId]/components/ReviewSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const ReviewSection = ({
const {
items: reviews,
triggerRef,
hasMore,
reset,
} = useInfiniteScroll({
initialData: initialReviews,
Expand Down Expand Up @@ -85,10 +84,6 @@ const ReviewSection = ({
</div>
)} */}
<div ref={triggerRef} className='h-20'></div>

{!hasMore && reviews.length > 0 && (
<p className='text-gray-9fa6b2 mb-10 text-center'>모든 리뷰를 불러왔습니다.</p>
)}
</section>
);
};
Expand Down
7 changes: 7 additions & 0 deletions src/assets/icon/Icon-scrollTop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/components/common/Footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Footer = () => {
if (hideFooter) return null;

return (
<footer className='text-mogazoa-14px-400 bg-black-1c1c22 text-white-f1f1f5/60 border-black-2e2e3a relative flex flex-col justify-between border-t-2 px-5 py-10 shadow-[0_-1px_2px_0_rgba(0,0,0,1)] xl:px-[140px] 2xl:flex-row'>
<footer className='text-mogazoa-14px-400 bg-black-1c1c22 text-white-f1f1f5/60 border-black-2e2e3a relative flex flex-col justify-between border-t-2 px-5 py-10 shadow-[0_-1px_2px_0_rgba(0,0,0,1)] md:pb-20 xl:px-[140px] 2xl:flex-row'>
{/* shadow-[0_-1px_2px_0_rgba(0,0,0,1)] */}
{/* gnb 그림자 -> 아래쪽에만 생김 shadow-sm shadow-black*/}
<div className='mr-20'>
Expand Down Expand Up @@ -59,7 +59,7 @@ const Footer = () => {
<li className='2xl:text-center'>&copy; 2025 by 3team All rights reserved.</li>
</ul>

<ul className='text-white-f1f1f5/60 relative flex items-end gap-5 md:absolute md:right-5 md:bottom-15 md:justify-end xl:right-30'>
<ul className='text-white-f1f1f5/60 relative flex items-end gap-5 md:absolute md:right-5 md:bottom-15 md:justify-end md:pb-10 xl:right-30'>
{/* md:absolute md:right-5 md:bottom-15 xl:right-30 */}
<li className='hover:text-white-f1f1f5'>
<a
Expand Down
59 changes: 44 additions & 15 deletions src/components/ui/FloatingButton.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,66 @@
'use client';

import { useEffect, useState } from 'react';

import { useSession } from 'next-auth/react';

import ProductModal from '@/app/_components/ProductPost/ProductModal';
import AddIcon from '@/assets/icon/Icon-floating.svg';
import ScrollTopIcon from '@/assets/icon/Icon-scrollTop.svg';
import { useModalStore } from '@/store/modalStore';

const FloatingButton = () => {
const [isVisible, setIsVisible] = useState<boolean>(false);
const { data: session } = useSession();
const openModal = useModalStore((state) => state.openModal);

const handleClickModal = () => {
return openModal({ component: ProductModal, props: { mode: 'create' } });
};

if (!session) return null;
useEffect(() => {
const handleScroll = () => {
if (window.scrollY > 300) {
setIsVisible(true);
} else {
setIsVisible(false);
}
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);

const scrollTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};

return (
<div className='fixed right-10 bottom-10 z-50'>
<div className='group relative'>
<div className='bg-white-f1f1f5 absolute -left-32 ml-auto hidden max-w-xs rounded-2xl px-3 py-2 text-gray-900 shadow group-hover:block'>
영화 추가하기
<span className='border-l-white-f1f1f5 absolute top-1/2 -right-[7px] -mt-1 h-0 w-0 border-y-8 border-b-0 border-l-8 border-y-transparent'></span>
</div>
<button
type='button'
aria-label='영화 추가하기'
className='to-main-indigo hover:indigo-300 mr-[-20px] flex h-15 w-15 cursor-pointer items-center justify-center rounded-full bg-gradient-to-r from-sky-500 via-blue-500 text-8xl hover:bg-gradient-to-r hover:from-sky-400 hover:via-blue-400'
onClick={handleClickModal}
<div className='fixed right-10 bottom-10 z-50 flex flex-col transition-all xl:right-20 xl:bottom-20'>
{isVisible && (
<div
onClick={scrollTop}
className='bg-gray-9fa6b2 hover:bg-gray-6e6e82 mb-4 flex h-15 w-15 cursor-pointer items-center justify-center rounded-full pt-1 transition-colors duration-200 hover:opacity-100'
>
<AddIcon />
</button>
</div>
<ScrollTopIcon className='h-10 w-10' />
</div>
)}

{session && (
<div className='group relative h-15 w-15'>
<button
type='button'
aria-label='영화 추가하기'
className='to-main-indigo hover:indigo-300 mr-[-20px] flex h-15 w-15 cursor-pointer items-center justify-center rounded-full bg-gradient-to-r from-sky-500 via-blue-500 text-8xl hover:bg-gradient-to-r hover:from-sky-400 hover:via-blue-400'
onClick={handleClickModal}
>
<AddIcon />
</button>
<div className='bg-white-f1f1f5 absolute top-2 -left-32 ml-auto hidden max-w-xs rounded-2xl px-3 py-2 text-gray-900 shadow group-hover:block'>
영화 추가하기
<span className='border-l-white-f1f1f5 absolute top-1/2 -right-[7px] -mt-1 h-0 w-0 border-y-8 border-b-0 border-l-8 border-y-transparent'></span>
</div>
</div>
)}
</div>
);
};
Expand Down
10 changes: 10 additions & 0 deletions src/hooks/useOptimisticToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { useEffect, useRef, useState } from 'react';

import { useSession } from 'next-auth/react';
import { toast } from 'sonner';

// 세션 존재해야 사용 가능합니다!!
const useOptimisticToggle = ({
initialCount = 0,
initialState,
Expand All @@ -13,8 +17,14 @@ const useOptimisticToggle = ({
const [optimisticCount, setOptimisticCount] = useState(initialCount);
const clickCountRef = useRef(0);
const timerRef = useRef<NodeJS.Timeout>(null);
const session = useSession();

const handleToggle = () => {
if (!session.data) {
toast.error('로그인이 필요합니다.');
return;
}

const newToggleState = !isToggled;
const newCount = newToggleState ? optimisticCount + 1 : optimisticCount - 1;

Expand Down