Skip to content
Open
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
19 changes: 19 additions & 0 deletions components/common/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

const LoadingSpinner = () => {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="flex flex-col items-center gap-4">
{/* 스피너 */}
<div className="relative h-16 w-16">
<div className="absolute h-16 w-16 animate-spin rounded-full border-4 border-gray-200"></div>
<div className="absolute h-16 w-16 animate-spin rounded-full border-4 border-transparent border-t-orange-500"></div>
</div>
{/* 로딩 텍스트 */}
<p className="text-lg font-medium text-gray-600">로딩중...</p>
</div>
</div>
);
};

export default LoadingSpinner;
36 changes: 23 additions & 13 deletions pages/owner/shops/[shopId]/notice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import Button from '@/components/common/Button';
import Input from '@/components/common/Input';
import AlertModal from '@/components/common/modal/AlertModal';
import BadgeClose from '@/components/icons/BadgeClose';
import LoadingSpinner from '@/components/common/LoadingSpinner';

const PostNotice = () => {
const [hourlyPay, setHourlyPay] = useState('');
const [startsAt, setStartsAt] = useState('');
const [workhour, setWorkhour] = useState('');
const [description, setDescription] = useState('');
const [loading, setLoading] = useState(true);
const [originalPay, setOriginalPay] = useState<number>(0);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const router = useRouter();
Expand All @@ -25,41 +27,49 @@ const PostNotice = () => {
const [errorModalOpen, setErrorModalOpen] = useState(false);
const [errorMessage, setErrorMessage] = useState('');

// 로그인 체크 및 shopId 확인
// 로그인 체크 및 가게 정보 불러오기
useEffect(() => {
const checkAuth = () => {
const initializePage = async () => {
// 1. 로그인 체크
const userId = localStorage.getItem('userId');
if (!userId) {
router.push('/login');
return;
}
};
checkAuth();
}, [router]);

// 가게 정보 불러오기 (originalHourlyPay 가져오기)
useEffect(() => {
const fetchShopData = async () => {
if (!shopId || typeof shopId !== 'string') return;
// 2. shopId 확인
if (!shopId || typeof shopId !== 'string') {
return;
}

// 3. 가게 정보 불러오기
try {
const shopRes = await shops.getShop(shopId);
if (shopRes?.item?.originalHourlyPay) {
setOriginalPay(shopRes.item.originalHourlyPay);
}
} catch (error) {
console.error('가게 정보 불러오기 실패:', error);
setErrorMessage('가게 정보를 불러오는데 실패했습니다.');
setErrorModalOpen(true);
} finally {
setLoading(false);
}
};

fetchShopData();
}, [shopId]);
initializePage();
}, [router, shopId]);
Comment on lines 31 to +61
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

useEffect 내의 로직에 문제가 있어 페이지가 무한 로딩 상태에 빠질 수 있습니다. shopIdrouter.query로부터 아직 제공되지 않았을 때 initializePage 함수가 return되면서 setLoading(false)가 호출되지 않기 때문입니다. 이로 인해 사용자는 계속 로딩 스피너만 보게 됩니다. router.isReady를 사용하여 라우터가 준비되었는지 확인하고, 준비되었음에도 shopId가 없는 경우에만 로딩을 중단하도록 로직을 수정해야 합니다.

  useEffect(() => {
    const initializePage = async () => {
      // 1. 로그인 체크
      const userId = localStorage.getItem('userId');
      if (!userId) {
        router.push('/login');
        return;
      }

      // 2. shopId가 준비될 때까지 기다리거나, 없으면 로딩 종료
      if (!shopId || typeof shopId !== 'string') {
        // 라우터가 준비되었지만 shopId가 없는 경우, 로딩을 중단합니다.
        if (router.isReady) {
          setLoading(false);
        }
        return;
      }

      // 3. 가게 정보 불러오기
      try {
        const shopRes = await shops.getShop(shopId);
        if (shopRes?.item?.originalHourlyPay) {
          setOriginalPay(shopRes.item.originalHourlyPay);
        }
      } catch (error) {
        console.error('가게 정보 불러오기 실패:', error);
        setErrorMessage('가게 정보를 불러오는데 실패했습니다.');
        setErrorModalOpen(true);
      } finally {
        setLoading(false);
      }
    };

    initializePage();
  }, [router, shopId]);


// 로딩 중일 때
if (loading) {
return <LoadingSpinner />;
}

// shopId가 없으면 로딩 상태 표시
// shopId가 없을 때
if (!shopId) {
return (
<div className="flex min-h-screen items-center justify-center">
로딩중...
<p className="text-lg text-gray-600">잘못된 접근입니다.</p>
</div>
);
}
Expand Down
8 changes: 6 additions & 2 deletions pages/owner/shops/[shopId]/notices/[noticeId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import PostBanner from '@/components/owner/PostBanner';
import { transformApplicationData } from '@/lib/utils/transformTableData';
import { calculatePercentage } from '@/utils/transformNotice';
import AlertModal from '@/components/common/modal/AlertModal';
import LoadingSpinner from '@/components/common/LoadingSpinner';

const NoticeDetail = () => {
const router = useRouter();
Expand All @@ -19,7 +20,7 @@ const NoticeDetail = () => {
>(null);
const [shop, setShop] = useState<(ShopRequest & { id: string }) | null>(null);
const [applicationList, setApplicationList] = useState<ApplicationItem[]>([]);
const [_loading, setLoading] = useState(true);
const [loading, setLoading] = useState(true);
const [_actionLoading, setActionLoading] = useState<string | null>(null);

// 에러 모달 상태
Expand Down Expand Up @@ -50,6 +51,7 @@ const NoticeDetail = () => {
const fetchNotice = async () => {
try {
setLoading(true);
if (loading) return <LoadingSpinner />;
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

useEffect 내부의 비동기 함수 fetchNotice에서 JSX 컴포넌트(LoadingSpinner)를 반환하고 있습니다. useEffect 내부에서 호출되는 함수는 렌더링을 위한 함수가 아니므로 JSX를 반환할 수 없으며, 이는 타입 에러를 발생시킵니다. 또한, 바로 앞 줄에서 setLoading(true)를 호출하고 있어 이 조건문은 항상 참이 됩니다. 로딩 상태에 따른 UI 분기는 컴포넌트의 최상위 return 문에서 처리해야 합니다. 이미 173번째 줄에서 올바르게 처리하고 있으므로, 이 줄은 삭제해야 합니다.


// 1. 공고 상세 조회
const noticeRes = await notices.getShopNotice(
Expand All @@ -58,6 +60,7 @@ const NoticeDetail = () => {
);

const noticeData = noticeRes.item;

setNotice({
id: noticeData.id,
hourlyPay: noticeData.hourlyPay,
Expand Down Expand Up @@ -166,7 +169,8 @@ const NoticeDetail = () => {
setActionLoading(null);
}
};

// 로딩 중일 때
if (loading) return <LoadingSpinner />;
if (!notice) return <div className="p-6">공고를 찾을 수 없습니다.</div>;

return (
Expand Down
3 changes: 2 additions & 1 deletion pages/owner/shops/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';

import Button from '@/components/common/Button';
import LoadingSpinner from '@/components/common/LoadingSpinner';

const MyShop = () => {
const [loading, setLoading] = useState(true);
Expand All @@ -23,7 +24,7 @@ const MyShop = () => {
checkAuth();
}, [router]);

if (loading) return <div>로딩중...</div>;
if (loading) return <LoadingSpinner />;
if (error) return <div className="text-red-500">{error}</div>;

return (
Expand Down