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
9 changes: 9 additions & 0 deletions public/assets/background_.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/assets/bannerMain.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions src/pages/main/Board.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Header } from '@/shared/components';

import Boardgame from '@/pages/main/components/board/Boardgame';
import router from 'next/router';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

라우터 import 방식이 잘못되었습니다.

Next.js에서 라우터를 사용하려면 useRouter 훅을 import해야 합니다. 현재 코드는 default export로 router를 import하려고 하는데, next/router는 default export를 제공하지 않습니다.

다음과 같이 수정하세요:

-import router from 'next/router';
+import { useRouter } from 'next/router';

그리고 컴포넌트 내부에서 훅을 사용하세요:

 const Board = () => {
+  const router = useRouter();
+
   return (

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

🤖 Prompt for AI Agents
In src/pages/main/Board.tsx around line 4, the code imports a non-existent
default export "router" from 'next/router'; replace that import with the
useRouter hook (import { useRouter } from 'next/router') and then call
useRouter() inside the component to get the router instance (replace any direct
references to the old "router" variable with the result of useRouter()). Ensure
you remove the default import and update any router usage (push, pathname,
query, etc.) to use the hook-returned object.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

여기 next/router 보다는
next/navigation 쓰는게 더 좋을거 같아요!

useRouter

Copy link
Copy Markdown
Contributor Author

@jjangminii jjangminii Oct 26, 2025

Choose a reason for hiding this comment

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

음..저희가 next 13버전이후를 사용하고 있긴하지만 pages 라우팅 기반이라 router 방식이 맞지 않을까싶습니다
첨부해주신 링크에도 각 라우팅 기반의 방식이 다르게 담겨있는데 버전도 같이 설정해서 확인해 볼 수 있는거같아요. 이 부분에 대해선 저도 좀 더 찾아보겠습니다-!
image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pages Router (pages/ 디렉토리)는 전통적인 파일 기반 라우팅 방식으로, 각 파일이 하나의 페이지 컴포넌트 역할을 하고 클라이언트-사이드 라우팅을 기본으로 하며 next/router에서 useRouter() 등을 사용하고
App Router (app/ 디렉토리)는 Next.js 13부터 도입된 방식으로, React Server Components, 중첩 레이아웃, 서버-클라이언트 경계를 고려한 최신 라우팅 구조로 next/navigation에서 useRouter(), usePathname(), useSearchParams()사용해야합니다

두 라우터 방식이 내부적으로 다르게 구현되어 있기 때문에, 라우팅 관련 API 및 훅들도 그 구조에 맞춰야한다고 생각하는데 저희가 Next.js 13 이후 버전을 사용하고 있지만 Pages Router 기반이라 next/router에서 useRouter()를 사용하는게 맞다고 생각이듭니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

확인 했습니다! next 더 공부해봐야겠네요.. 겉핥기식으로 알고있다는 느낌을 받아서 답변 감사합니다!


const Board = () => {
return (
<div className='relative w-full h-[100vh] bg-[#46d1cd] overflow-auto'>
<Header title='지도' onClick={() => router.back()} />

<main className='relative pt-[11.8rem]'>
<Boardgame />
</main>
</div>
);
};

export default Board;
Empty file.
43 changes: 43 additions & 0 deletions src/pages/main/components/board/Boardgame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use client';
import Image from 'next/image';
import { boardData } from '@/shared/constants/main/boardData';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

항상 확장성 용이하게 만드는 코드 진짜 배워갑니다..


const Boardgame = () => {
return (
<div className='relative w-full h-full bg-[#46d1cd] overflow-hidden'>
<Image
src='/assets/background_.svg'
alt='board background'
width={402}
height={755}
className='w-full h-full object-cover'
priority
/>

<div
className='
absolute top-0 left-0
w-full h-full
grid grid-cols-4 grid-rows-8 gap-0
px-[2rem] pb-[1.7rem]
'
>
{boardData.map((row, r) =>
row.map((cell, c) => {
const key = `cell-${r}-${c}`;

if (!cell.active) {
return <div key={key} className='bg-transparent' />;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

!cell.active일 때 null 반환으로 안하고
bg-transparent 한 특별한 이유가 있을까요??

Copy link
Copy Markdown
Contributor Author

@jjangminii jjangminii Oct 26, 2025

Choose a reason for hiding this comment

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

null로 하게 될 경우 그 자리에 아무 요소도 렌더되지 않기 때문에 그리드의 셀 개수가 줄어들어 전체 배치가 틀어질 수 있어요 grid는 따로 빈 칸이라는 개념이 없어 요소가 없으면 셀 자체도 사라져버립니당
그래서 bg-transparent로 보이지 않는 <div>를 렌더링해서 해당 셀은 비어 있지만 공간은 차지한다는 상태를 만들어 원하는 그리드의 모양 유지했습니다

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

셀 자체가 없어지는걸 생각을 못했네요 설명 감사합니다!

}

return (
<div key={key} onClick={() => console.log(cell.label)}></div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

프로덕션 코드에서 console.log를 제거하세요.

클릭 핸들러에 console.log만 있습니다. 이는 플레이스홀더 코드로 보이며 프로덕션 환경에서는 제거되어야 합니다. 실제 클릭 동작(예: 상세 페이지 이동, 모달 표시 등)을 구현하거나 TODO 주석을 추가해주세요.

-              <div key={key} onClick={() => console.log(cell.label)}></div>
+              <div 
+                key={key} 
+                onClick={() => {
+                  // TODO: 셀 클릭 시 상세 페이지 또는 모달 표시
+                }}
+              ></div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div key={key} onClick={() => console.log(cell.label)}></div>
<div
key={key}
onClick={() => {
// TODO: 셀 클릭 시 상세 페이지 또는 모달 표시
}}
></div>
🤖 Prompt for AI Agents
In src/pages/main/components/board/Boardgame.tsx around line 34 the onClick
handler currently only calls console.log(cell.label); remove this console.log
from production code and replace it with a real action or a clear TODO: either
call a passed-in handler prop (e.g., onCellClick(cell) or navigate to a detail
route / open a modal) or add a TODO comment that explains the intended behavior
and throw a noop or guard to prevent accidental logging; ensure the handler uses
a stable function reference (not inline) if you wire it to props or navigation.

);
Comment on lines +29 to +35
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

활성 셀의 접근성을 개선하세요.

활성 셀이 클릭 가능하지만 시각적 피드백, 키보드 접근성, ARIA 레이블이 없습니다. 사용자에게 인터랙티브 요소임을 알리고 접근성을 향상시켜야 합니다.

다음과 같이 개선하세요:

             return (
               <div 
                 key={key} 
-                onClick={() => console.log(cell.label)}
-              ></div>
+                role="button"
+                tabIndex={0}
+                aria-label={cell.label || '보드 셀'}
+                onClick={() => {
+                  // TODO: 클릭 핸들러 구현
+                }}
+                onKeyDown={(e) => {
+                  if (e.key === 'Enter' || e.key === ' ') {
+                    // TODO: 키보드 핸들러 구현
+                  }
+                }}
+                className="cursor-pointer hover:opacity-80 focus:outline-none focus:ring-2 focus:ring-white"
+              />
             );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!cell.active) {
return <div key={key} className='bg-transparent' />;
}
return (
<div key={key} onClick={() => console.log(cell.label)}></div>
);
if (!cell.active) {
return <div key={key} className='bg-transparent' />;
}
return (
<div
key={key}
role="button"
tabIndex={0}
aria-label={cell.label || '보드 셀'}
onClick={() => {
// TODO: 클릭 핸들러 구현
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
// TODO: 키보드 핸들러 구현
}
}}
className="cursor-pointer hover:opacity-80 focus:outline-none focus:ring-2 focus:ring-white"
/>
);

}),
)}
</div>
</div>
);
};

export default Boardgame;
9 changes: 6 additions & 3 deletions src/pages/main/components/stampBoard/Stamp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,22 @@ const Stamp = ({ index, acquired, className }: StampProps) => {
return (
<div
className={cn(
'flex items-center justify-center w-[3rem] h-[3rem]',
'flex items-center justify-center w-[2.8rem] h-[2.8rem]',
className,
)}
aria-label={`${index + 1}번째 스탬프 획득`}
>
<Icon name='Stamp' size={30} color='pink-400' aria-hidden />
<Icon name='Stamp' size={28} color='pink-400' aria-hidden />
</div>
);
}

return (
<div
className={cn('rounded-full bg-pink-100 w-[3rem] h-[3rem]', className)}
className={cn(
'rounded-full bg-pink-100 w-[2.8rem] h-[2.8rem]',
className,
)}
aria-label={`${index + 1}번째 스탬프 미획득`}
/>
);
Expand Down
17 changes: 12 additions & 5 deletions src/pages/main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@ import { cn } from '@/shared/lib';
import StampBoard from './components/stampBoard/StampBoard';
import { ControlBar } from '@/shared/components';
import Image from 'next/image';
import router from 'next/router';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

라우터 import 방식이 잘못되었습니다.

Board.tsx와 동일한 문제입니다. next/router에서 default export로 router를 import할 수 없습니다. useRouter 훅을 사용해야 합니다.

다음과 같이 수정하세요:

-import router from 'next/router';
+import { useRouter } from 'next/router';

그리고 컴포넌트 내부에서:

 export default function MainPage() {
+  const router = useRouter();
+
   return (

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

🤖 Prompt for AI Agents
In src/pages/main/index.tsx around line 5, the file incorrectly imports a
default "router" from 'next/router'; change the import to use the useRouter hook
(import { useRouter } from 'next/router') and inside the component call the hook
(const router = useRouter()) so existing router usage still works; update any
references if they assumed a module-level router to use the local router
variable returned from the hook.

import { BottomNav } from '@/shared/components/tab/BottomNav';

export default function MainPage() {
return (
<div className={cn('px-[2.4rem] bg-white flex flex-col gap-[1rem]')}>
<div
className={cn(
'px-[2.4rem] bg-white flex flex-col gap-[1rem] h-full pt-[1.3rem] pb-[12rem]',
)}
>
<ControlBar
isLoggedIn={false}
onLogin={() => {}}
userName='글다'
className='fixed top-[0.6rem] left-0 right-0 z-50 px-[2rem]'
className='fixed top-[1rem] left-0 right-0 z-50 px-[2rem]'
/>

<main className='w-full pt-[6.2rem] flex flex-col gap-4 overflow-auto'>
<main className='w-full pt-[6.3rem] flex flex-col gap-4 overflow-auto'>
<section>
<Image
src='/assets/bannerMain.svg'
Expand All @@ -26,7 +32,7 @@ export default function MainPage() {

<section
onClick={() => {
/* TODO: 페이지 이동 */
router.push('/main/Board');
}}
>
<Image
Expand All @@ -38,8 +44,9 @@ export default function MainPage() {
/>
</section>

<StampBoard count={3} total={8} />
<StampBoard count={3} total={10} />
</main>
<BottomNav />
</div>
);
}
12 changes: 6 additions & 6 deletions src/shared/components/tab/BottomNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ export const BottomNav = () => {

return (
<nav
className="
fixed bottom-[2rem] left-[2.4rem]
className='
fixed bottom-[5.3rem] left-[2.4rem]
w-[calc(100%-4.8rem)]
flex justify-between items-center
py-[1rem]
rounded-[37.07px]
bg-mint-50 border border-mint-300
"
aria-label="Bottom navigation"
'
aria-label='Bottom navigation'
>
{NAV_ITEMS.map((item) => {
const isActive =
Expand All @@ -42,8 +42,8 @@ export const BottomNav = () => {
<Link
key={item.id}
href={item.href}
aria-current={isActive ? 'page' : undefined}
className="flex-1 flex justify-center"
aria-current={isActive ? 'page' : undefined}
className='flex-1 flex justify-center'
>
<TabItem label={item.label} icon={item.icon} isActive={isActive} />
</Link>
Expand Down
File renamed without changes.
41 changes: 41 additions & 0 deletions src/shared/constants/main/boardData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const boardData = [
[{ active: false }, { active: false }, { active: false }, { active: false }],
[
{ active: true, label: '김수환관' },
{ active: true, label: '부천 아트벙커' },
{ active: true, label: '한국 만화박물관' },
{ active: false },
],
[
{ active: false },
{ active: false },
{ active: false },
{ active: true, label: '상동 호수 공원' },
],
[
{ active: false },
{ active: true, label: '부천역' },
{ active: true, label: '다솔관' },
{ active: true, label: '부천 자유 시장' },
],
[
{ active: false },
{ active: true, label: '중앙도서관' },
{ active: false },
{ active: false },
],
[
{ active: false },
{ active: false },
{ active: true, label: '역곡공원' },
{ active: false },
],
[
{ active: false },
{ active: false },
{ active: false },
{ active: true, label: '부천 식물원' },
],
[{ active: false }, { active: false }, { active: false }, { active: false }],
];
export { boardData };
Comment on lines +1 to +41
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

TypeScript 타입 정의를 추가하세요.

boardData에 명시적인 타입 정의가 없어 타입 안정성이 보장되지 않습니다. 셀 구조와 배열 차원에 대한 타입을 정의하면 실수를 방지하고 코드 품질을 향상시킬 수 있습니다.

다음과 같이 타입을 추가하세요:

+interface BoardCell {
+  active: boolean;
+  label?: string;
+}
+
+type BoardRow = [BoardCell, BoardCell, BoardCell, BoardCell];
+type BoardData = BoardRow[];
+
-const boardData = [
+const boardData: BoardData = [
   [{ active: false }, { active: false }, { active: false }, { active: false }],
🤖 Prompt for AI Agents
In src/shared/constants/main/boardData.ts lines 1-41, there is no TypeScript
type for boardData which hurts type safety; add a Cell type (e.g. { active:
boolean; label?: string }) and annotate boardData as an array of Cell arrays
(Cell[][]) — optionally export the Cell type — then update the const declaration
to use that type so each cell and the board shape are type-checked.

Loading