Skip to content

Feat(client): 레벨 페이지 레이아웃#57

Merged
jjangminii merged 6 commits intodevelopfrom
feat/#55/mypage-layout
Sep 9, 2025
Merged

Feat(client): 레벨 페이지 레이아웃#57
jjangminii merged 6 commits intodevelopfrom
feat/#55/mypage-layout

Conversation

@jjangminii
Copy link
Collaborator

@jjangminii jjangminii commented Sep 6, 2025

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

📄 Tasks

  • 레벨페이지 레이아웃

⭐ PR Point (To Reviewer)

  • acorns 값 하나만 넘겨주면 됩니당
  • 피그마랑 동일한 픽셀값으로 작업했는데 사이즈가 뭔가 이상해서 디자이너쌤과 고민중입니다..
  • 뱃지 수정 필요해요 TODO 주석 달아뒀습니다
  • secondary 컬러 중복되어 적용 안되는 이슈로 디자이너 쌤들과 상의 후 이름 수정했습니다

📷 Screenshot

스크린샷 2025-09-06 오후 3 09 20

Summary by CodeRabbit

  • New Features
    • 레벨 페이지를 확장해 레벨별 배경 이미지, 레벨 정보 팝오버(정보 버튼), 오늘 모은 도토리 배지 및 트리 상태 카드를 표시합니다. 현재 내부에서 고정된 도토리 수(1)를 사용하며 API 연동은 추후 반영 예정입니다.
  • Style
    • 테마 변수명(--color-secondary → --color-subcolor)을 정리해 배경 보조색 일관성을 개선했습니다.
  • Refactor
    • 레이아웃에서 옵션 메뉴 버튼을 제거해 UI를 단순화했습니다.

@jjangminii jjangminii linked an issue Sep 6, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Sep 6, 2025

Walkthrough

Layout에서 OptionsMenuButton을 제거하고 Outlet만 렌더링하도록 정리했습니다. Level 페이지는 자체 상태(acorns=1 하드코드)로 getTreeLevel을 호출해 LevelScene, LevelInfoCard, Badge, TreeStatusCard 등을 렌더링하도록 교체되었고, LevelScene과 타입/유틸 리팩터가 추가되었습니다. 테마 변수명이 변경되었습니다.

Changes

Cohort / File(s) Summary
Layout cleanup
apps/client/src/layout/Layout.tsx
OptionsMenuButton import 및 JSX 제거; Layout은 이제 Outlet만 렌더링.
Level page & composition
apps/client/src/pages/level/Level.tsx
플레이스홀더를 자체 상태 기반 Level 페이지로 교체(acorns 하드코드 1), getTreeLevel 사용, 헤더·정보 팝오버·Badge, TreeStatusCard, LevelScene 렌더링.
Level scene component
apps/client/src/pages/level/components/LevelScene.tsx
신규 컴포넌트 추가: level에 따라 이미지 매핑(1–5) 및 절대포지션 데코레이션.
Level types
apps/client/src/pages/level/types/treeLevelType.ts
TREE_LEVEL_TABLE, TreeLevel, TreeLevelRowShape, TreeLevelResult 등 강타입 레벨 데이터 모델 추가.
Level utils refactor
apps/client/src/pages/level/utils/treeLevel.ts
로컬 타입 제거 후 새 타입 모듈 사용; findLevelRow/calcProgress 헬퍼 추가; getTreeLevel 구현 이동 및 TREE_LEVEL_TABLE 재내보내기.
Level components imports update
apps/client/src/pages/level/components/LevelInfoCard.tsx, apps/client/src/pages/level/components/TreeStatusCard.tsx
TreeLevel 타입/유틸 임포트 경로를 절대 alias(@pages/level/...)로 변경.
Design token rename
packages/tailwind-config/shared-styles.css
@theme 블록 내 --color-secondary--color-subcolor로 변수명 변경(값 동일).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant LevelPage as Level (pages/level/Level.tsx)
    participant Utils as getTreeLevel
    participant Scene as LevelScene
    participant Info as LevelInfoCard
    participant Status as TreeStatusCard

    User->>LevelPage: 페이지 진입 (acorns 기본값)
    LevelPage->>Utils: getTreeLevel(acorns)
    Utils-->>LevelPage: info { level, progress... }
    LevelPage->>Scene: render(level = info.level)
    LevelPage->>Status: render(acorns)
    User->>LevelPage: 정보 버튼 클릭
    LevelPage->>Info: InfoCard 오버레이 표시
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Progress bar 구현 (#25) Progress 컴포넌트나 디자인시스템 관련 코드가 PR에 포함되어 있지 않음.
마이페이지 레이아웃 구현 (#55) 마이페이지 관련 컴포넌트 연결/레이아웃 작업이 이 PR에 없음.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Level 페이지 확장 및 UI 추가 (apps/client/src/pages/level/Level.tsx) #25/#55 목적에 명시된 프로그레스바·마이페이지 레이아웃 작업과 직접 관련 없음.
신규 LevelScene 추가 (apps/client/src/pages/level/components/LevelScene.tsx) 연결된 이슈의 요구사항(프로그레스바/마이페이지)과 무관한 레벨 씬 데코 컴포넌트 추가임.
CSS 토큰명 변경 (packages/tailwind-config/shared-styles.css) 토큰 리네이밍은 이슈 목표에 포함되지 않은 전역 스타일 변경임.

Possibly related PRs

Suggested reviewers

  • jllee000
  • constantly-dev

Poem

깡총깡총, 숲 속 코드 밭에 토끼 한 마리,
변수 이름 바꾸고 씬을 새로 입혔네,
메뉴는 사라지고 Outlet만 인사하니,
도토리 하나로 레벨을 바라보며,
토끼는 기쁘게 춤을 추네. 🐇🌰

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#55/mypage-layout

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the feat 기능 개발하라 개발 달려라 달려 label Sep 6, 2025
@github-actions github-actions bot requested a review from jllee000 September 6, 2025 06:29
@github-actions
Copy link

github-actions bot commented Sep 6, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
apps/client/src/pages/level/components/LevelScene.tsx (2)

12-19: 타입 안정성 개선: satisfies로 키 누락 방지

Record<TreeLevel, string> 명시와 as const를 함께 쓰는 대신, satisfies로 키 완전성을 보장하면서 리터럴 타입을 유지하세요.

-const SCENE_BY_LEVEL: Record<TreeLevel, string> = {
+const SCENE_BY_LEVEL = {
-} as const;
+} as const satisfies Record<TreeLevel, string>;

31-37: 이미지 로딩 최적화 소소한 개선

Above-the-fold가 아니라면 loading="lazy"decoding="async" 추가를 고려하세요.

-      <img
+      <img
         src={src}
         alt={`지식나무 숲 Lv.${level}`}
         className="h-auto w-full select-none rounded-[1.2rem] object-cover"
+        loading="lazy"
+        decoding="async"
         draggable={false}
       />
apps/client/src/pages/level/Level.tsx (4)

9-14: 필수 prop에 디폴트값이 의미 없음 — 선택(prop)화 또는 디폴트 제거

LevelPageProps에서 acorns가 필수인데, 함수 파라미터에 기본값이 있어도 TS 상으론 전달 강제입니다. 선택(prop)으로 바꾸거나 디폴트를 제거하세요. 기본값 유지가 목적이면 아래처럼 권장합니다.

-interface LevelPageProps {
-  acorns: number;
-}
+interface LevelPageProps {
+  acorns?: number;
+}

-export default function Level({ acorns = 3 }: LevelPageProps) {
+export default function Level({ acorns = 3 }: LevelPageProps) {

19-19: 불필요한 캐스팅 제거

getTreeLevel이 반환하는 info.level이 이미 TreeLevel이라면 캐스팅 불필요합니다.

-        <LevelScene level={info.level as TreeLevel} />
+        <LevelScene level={info.level} />

26-34: 접근성: Hover 전용 토글은 터치 환경에서 비노출

모바일/터치에서 hover가 없어 정보 카드 접근이 어려울 수 있습니다. 클릭/포커스 토글이 되는 접근성 컴포넌트(예: @radix-ui/react-popover 또는 디자인시스템 Popover)를 고려하세요. aria-expanded, aria-controls, aria-haspopup 등을 함께 설정하면 좋습니다.


46-48: 불필요 TODO 주석 정리

이미 Badge를 구현했으므로 TODO 주석은 제거해 주세요.

-            {/* TODO: 오늘 모은 도토리 개수 배지 */}
             <Badge text={'오늘 모은 도토리 개수'} countNum={acorns} />
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16afe23 and b19c6b2.

⛔ Files ignored due to path filters (5)
  • apps/client/src/assets/chippi_level1.png is excluded by !**/*.png
  • apps/client/src/assets/chippi_level2.png is excluded by !**/*.png
  • apps/client/src/assets/chippi_level3.png is excluded by !**/*.png
  • apps/client/src/assets/chippi_level4.png is excluded by !**/*.png
  • apps/client/src/assets/chippi_level5.png is excluded by !**/*.png
📒 Files selected for processing (4)
  • apps/client/src/layout/Layout.tsx (0 hunks)
  • apps/client/src/pages/level/Level.tsx (1 hunks)
  • apps/client/src/pages/level/components/LevelScene.tsx (1 hunks)
  • packages/tailwind-config/shared-styles.css (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/client/src/layout/Layout.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-04T08:38:00.583Z
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#54
File: apps/client/src/pages/level/components/TreeStatusCard.tsx:12-12
Timestamp: 2025-09-04T08:38:00.583Z
Learning: In TreeStatusCard component, the progress bar calculation `info.level * 20` is intentional design - level 1 starts at 20%, level 2 at 40%, etc., up to level 5 at 100%. This ensures no level shows 0% progress for UX purposes.

Applied to files:

  • apps/client/src/pages/level/Level.tsx
🧬 Code graph analysis (1)
apps/client/src/pages/level/Level.tsx (4)
apps/client/src/pages/level/utils/treeLevel.ts (2)
  • getTreeLevel (47-50)
  • TreeLevel (17-17)
packages/design-system/src/lib/utils.ts (1)
  • cn (108-110)
apps/client/src/pages/level/components/LevelInfoCard.tsx (1)
  • LevelInfoCard (14-68)
apps/client/src/pages/level/components/TreeStatusCard.tsx (1)
  • TreeStatusCard (9-38)
🪛 Biome (2.1.2)
packages/tailwind-config/shared-styles.css

[error] 183-183: expected , but instead found --color-subcolor

Remove --color-subcolor

(parse)


[error] 183-183: Unexpected value or character.

Expected one of:

(parse)


[error] 183-183: expected , but instead found ;

Remove ;

(parse)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: storybook

Comment on lines 182 to 185
/* Secondary Color */
--color-secondary: #f4f2ec;
--color-subcolor: #f4f2ec;

/* Grayscale */
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

토큰명 리네임은 합리적이나 Biome 파서 오류로 CI 차단 가능

Tailwind v4 @theme 문법을 Biome(2.1.2)이 아직 정상 파싱하지 못해 parse 에러가 납니다. 코드 자체는 정상 CSS이므로 린터 설정/우회가 필요합니다.

권장 조치:

  • 단기: 해당 파일에서 Biome 파싱을 비활성화(override)하거나 @theme 블록만 무시하도록 설정.
  • 중기: Biome 업데이트 또는 packages/tailwind-config/**/*.css 경로를 Biome 대상에서 제외.

추가로, 점진적 마이그레이션을 위해 이전 토큰도 함께 노출해두면 레거시 클래스(bg-secondary)가 남아 있어도 안전합니다.

   /* Secondary Color */
-  --color-subcolor: #f4f2ec;
+  --color-subcolor: #f4f2ec;
+  /* temporary alias for backward-compat */
+  --color-secondary: var(--color-subcolor);

확인 스크립트:


🏁 Script executed:

#!/bin/bash
# 1) 레거시 사용처 존재 여부
rg -nP 'bg-secondary|--color-secondary'
# 2) Biome가 해당 파일에서만 실패하는지 확인
pnpm biome check packages/tailwind-config/shared-styles.css || true

Length of output: 11255


Biome 파서 오류로 CI 실패 확인 – 단기 대응 필요

  • Biome(2.1.2)가 packages/tailwind-config/shared-styles.css@theme 블록 내 CSS 변수 정의를 파싱하지 못해 207개 에러 발생 확인
  • 단기: 해당 파일에 대해 Biome 파서 무시/override 설정 추가
  • 중기: Biome 최신 버전으로 업그레이드하거나 packages/tailwind-config/**/*.css 경로를 검사 대상에서 제외
  • 레거시 토큰(--color-secondary) 사용처(packages/design-system/styles.css)가 존재하므로, 점진적 마이그레이션을 위해 alias 변수 추가 권장
   /* Secondary Color */
+  /* temporary alias for backward-compat */
+  --color-secondary: var(--color-subcolor);
📝 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
/* Secondary Color */
--color-secondary: #f4f2ec;
--color-subcolor: #f4f2ec;
/* Grayscale */
/* Secondary Color */
/* temporary alias for backward-compat */
--color-secondary: var(--color-subcolor);
--color-subcolor: #f4f2ec;
/* Grayscale */
🧰 Tools
🪛 Biome (2.1.2)

[error] 183-183: expected , but instead found --color-subcolor

Remove --color-subcolor

(parse)


[error] 183-183: Unexpected value or character.

Expected one of:

(parse)


[error] 183-183: expected , but instead found ;

Remove ;

(parse)

🤖 Prompt for AI Agents
In packages/tailwind-config/shared-styles.css around lines 182 to 185, Biome
2.1.2 fails parsing CSS variable definitions inside the @theme block causing CI
errors; as a short-term fix add a Biome parser override/ignore entry for this
file (or for packages/tailwind-config/**/*.css) in the repo's Biome config so
the file is skipped by the parser, and also add a legacy alias variable
declaration mapping --color-secondary to --color-subcolor within this CSS file
to keep compatibility with packages/design-system/styles.css; for mid-term
consider upgrading Biome or excluding the path from parsing altogether.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/client/src/pages/level/Level.tsx (1)

36-41: 숨김 상태에서도 클릭되는 오버레이 — pointer-events 기본값 수정

보이지 않을 때(opacity:0)도 클릭/포커스가 가능합니다. 기본을 pointer-events-none으로 두고 보일 때만 활성화하세요. (이전 코멘트와 동일 제안)

-                  className={cn(
-                    'pointer-events-auto absolute left-0 top-[2.4rem] z-[20]',
-                    'opacity-0 transition-opacity duration-150',
-                    'peer-hover:opacity-100 peer-focus-visible:opacity-100'
-                  )}
+                  className={cn(
+                    'pointer-events-none absolute left-0 top-[2.4rem] z-[20]',
+                    'opacity-0 transition-opacity duration-150',
+                    'peer-hover:pointer-events-auto peer-focus-visible:pointer-events-auto',
+                    'peer-hover:opacity-100 peer-focus-visible:opacity-100'
+                  )}
🧹 Nitpick comments (2)
apps/client/src/pages/level/Level.tsx (2)

19-20: 불필요한 타입 단언 제거 검토

info.level as TreeLevel 단언은 불필요할 가능성이 큽니다. 반환 타입이 이미 TreeLevel이라면 캐스트를 제거하세요. TS 에러가 난다면 getTreeLevel 반환 타입을 정교화하는 편이 낫습니다.

-        <LevelScene level={info.level as TreeLevel} />
+        <LevelScene level={info.level} />

46-47: 불필요한 TODO 제거

이미 배지를 렌더링하고 있으므로 TODO는 제거해 코드 노이즈를 줄이세요.

-            {/* TODO: 오늘 모은 도토리 개수 배지 */}
             <Badge text={'오늘 모은 도토리 개수'} countNum={acorns} />
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b19c6b2 and aee099d.

📒 Files selected for processing (1)
  • apps/client/src/pages/level/Level.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-04T08:38:00.583Z
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#54
File: apps/client/src/pages/level/components/TreeStatusCard.tsx:12-12
Timestamp: 2025-09-04T08:38:00.583Z
Learning: In TreeStatusCard component, the progress bar calculation `info.level * 20` is intentional design - level 1 starts at 20%, level 2 at 40%, etc., up to level 5 at 100%. This ensures no level shows 0% progress for UX purposes.

Applied to files:

  • apps/client/src/pages/level/Level.tsx
🧬 Code graph analysis (1)
apps/client/src/pages/level/Level.tsx (5)
apps/client/src/pages/level/utils/treeLevel.ts (2)
  • getTreeLevel (47-50)
  • TreeLevel (17-17)
packages/design-system/src/lib/utils.ts (1)
  • cn (108-110)
apps/client/src/pages/level/components/LevelScene.tsx (1)
  • LevelScene (24-40)
apps/client/src/pages/level/components/LevelInfoCard.tsx (1)
  • LevelInfoCard (14-68)
apps/client/src/pages/level/components/TreeStatusCard.tsx (1)
  • TreeStatusCard (9-38)

Comment on lines 27 to 33
type="button"
className="peer p-[0.4rem]"
aria-describedby="level-info-card"
>
<Icon name="ic_info" width={20} height={20} />
</button>
<div
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

아이콘 전용 버튼: 접근 가능한 이름과 ARIA 속성 정정 필요

현재 버튼은 시각적 아이콘만 있고 접근 가능한 이름이 없습니다. 또한 aria-describedby는 설명 텍스트 연결 용도라 팝오버/카드 토글에는 부적절합니다. 버튼에 이름을 부여하고 제어 대상은 aria-controls로 연결하세요.

-                <button
+                <button
                   type="button"
                   className="peer p-[0.4rem]"
-                  aria-describedby="level-info-card"
+                  aria-controls="level-info-card"
+                  aria-haspopup="dialog"
+                  aria-label="레벨 안내 보기"
                 >

팁: 완전한 접근성(포커스 트랩, ESC 닫기, aria-expanded 반영 등)을 원하시면 Radix UI Popover/디자인시스템 컴포넌트로 교체하는 것을 권장합니다. 필요하면 구현 도와드릴게요.

📝 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
<button
type="button"
className="peer p-[0.4rem]"
aria-describedby="level-info-card"
>
<Icon name="ic_info" width={20} height={20} />
</button>
<button
type="button"
className="peer p-[0.4rem]"
aria-controls="level-info-card"
aria-haspopup="dialog"
aria-label="레벨 안내 보기"
>
<Icon name="ic_info" width={20} height={20} />
</button>
🤖 Prompt for AI Agents
In apps/client/src/pages/level/Level.tsx around lines 27 to 33, the icon-only
button lacks an accessible name and uses aria-describedby incorrectly for
toggling a popover; replace aria-describedby with aria-controls pointing to the
popover/card ID, give the button an accessible label (either aria-label or
include visually-hidden text) and add aria-expanded (true/false) and
aria-haspopup="dialog" (or "menu" as appropriate) to reflect toggle state;
ensure the popover element has the matching id and update code where the popover
is opened/closed to set aria-expanded accordingly (or swap to Radix UI
Popover/design-system component if you want a full accessible implementation).

@jllee000 jllee000 changed the title Feat(client): 레벨페이지 레이아웃작업 Feat(client): 레벨 페이지 레이아웃 Sep 6, 2025
@constantly-dev constantly-dev self-requested a review September 7, 2025 05:50
Copy link
Member

@constantly-dev constantly-dev left a comment

Choose a reason for hiding this comment

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

고생하셨습니다! 간단한 코멘트 몇 개 달았으니 확인해주세요!
추가로 저 png level 이미지는 이후에 최적화해도 좋을 것 같네요 👍

Comment on lines 9 to 13
interface LevelPageProps {
acorns?: number;
}

export default function Level({ acorns = 3 }: LevelPageProps) {
Copy link
Member

Choose a reason for hiding this comment

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

acorns가 optional인 부분과 default가 3이 맞는지 궁금합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

default 값은 테스트 해보느라 제가 임의로 넣어둔 값이었어요 이부분은 삭제하겠습니다
샌각해보니 저 acorns은 Props가 아닌 페이지에서 API 연결 후 받아오는 값이라 수정하겠습니다-!

Copy link
Member

Choose a reason for hiding this comment

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

확인했습니다~

</div>
</div>
</div>
{/* TODO: 오늘 모은 도토리 개수 배지 */}
Copy link
Member

Choose a reason for hiding this comment

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

badge 추가된 걸까요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아직 수정 안된 부분입니다 따로 원래 있던 뱃지 사용하고있는 코드입니다

// components/LevelScene.tsx
import * as React from 'react';
import { cn } from '@pinback/design-system/utils';
import { type TreeLevel } from '../utils/treeLevel';
Copy link
Member

Choose a reason for hiding this comment

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

Level 페이지 컴포넌트에도 해당 type 쓰이던데 util에서 불러오는 것 같아서! util파일이 아닌 type으로 따로 빼고 재사용 해도 되지 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

유틸이 아닌 타입으로 뺴는게 맞는거 같아보이네요 수정하겠습니다-!

Comment on lines 20 to 22
interface LevelSceneProps extends React.HTMLAttributes<HTMLDivElement> {
level: TreeLevel;
}
Copy link
Member

Choose a reason for hiding this comment

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

트리셰이킹이 되긴 하겠지만 React 전체 모듈을 불러올 이유가 크게 없다면 그냥 HTMLAttributes만 불러와도 될 것 같아요!
React 전체 import 제거하고!

Comment on lines 24 to 31
export default function LevelScene({
level,
className,
...rest
}: LevelSceneProps) {
const src = SCENE_BY_LEVEL[level];
return (
<div className={cn('relative w-full', className)} {...rest}>
Copy link
Member

Choose a reason for hiding this comment

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

단순 궁금증으로 rest props로 들어오는 예시가 있을까요??


/* Secondary Color */
--color-secondary: #f4f2ec;
--color-subcolor: #f4f2ec;
Copy link
Member

Choose a reason for hiding this comment

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

👍

Comment on lines 3 to 5
import LevelScene from './components/LevelScene';
import LevelInfoCard from './components/LevelInfoCard';
import TreeStatusCard from './components/TreeStatusCard';
Copy link
Member

Choose a reason for hiding this comment

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

절대 경로 체크!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/client/src/pages/level/utils/treeLevel.ts (1)

28-31: getTreeLevel 입력 유효성 보강 (NaN/음수/실수 방지)

API/연동 단계에서 비정상 값이 들어오면 진행률 NaN 등 부작용이 납니다. 정수화+하한 0 클램프를 권장합니다.

-export function getTreeLevel(acorns: number): TreeLevelResult {
-  const { row, next } = findLevelRow(acorns, TREE_LEVEL_TABLE);
-  return { ...row, ...calcProgress(acorns, row, next) };
-}
+export function getTreeLevel(acorns: number): TreeLevelResult {
+  const n = Math.floor(Number(acorns));
+  const count = Number.isFinite(n) && n >= 0 ? n : 0;
+  const { row, next } = findLevelRow(count, TREE_LEVEL_TABLE);
+  return { ...row, ...calcProgress(count, row, next) };
+}
♻️ Duplicate comments (2)
apps/client/src/pages/level/Level.tsx (2)

24-30: 아이콘 버튼 접근성: aria-describedby 오용, 이름 누락

아이콘 전용 버튼에 접근 가능한 이름이 없고, 토글 대상 연결에 aria-describedby를 사용했습니다. aria-controls/aria-haspopup로 교체하고 라벨을 추가하세요. (완전한 a11y는 상태 기반 Popover로 aria-expanded 동기화 권장)

-                <button
+                <button
                   type="button"
                   className="peer flex items-center justify-center p-[0.4rem]"
-                  aria-describedby="level-info-card"
+                  aria-controls="level-info-card"
+                  aria-haspopup="dialog"
+                  aria-label="레벨 안내 보기"
                 >

23-42: 키보드 포커스 이동 시 오버레이 닫힘 — group-hover/focus-within 추가

버튼에서 오버레이로 포커스가 이동하면 peer 기반 제어가 끊깁니다. 래퍼에 group을 주고 group-hover/group-focus-within을 추가해 포커스 유지 시 노출되게 하세요.

-              <div className="relative items-center">
+              <div className="relative group items-center">
@@
-                <div
+                <div
                   id="level-info-card"
                   className={cn(
-                    'pointer-events-none absolute left-0 top-[3rem] z-[20]',
-                    'opacity-0 transition-opacity duration-150',
-                    'peer-hover:pointer-events-auto peer-focus-visible:pointer-events-auto',
-                    'peer-hover:opacity-100 peer-focus-visible:opacity-100'
+                    'pointer-events-none absolute left-0 top-[3rem] z-[20]',
+                    'opacity-0 transition-opacity duration-150',
+                    'peer-hover:pointer-events-auto peer-focus-visible:pointer-events-auto',
+                    'group-hover:pointer-events-auto group-focus-within:pointer-events-auto',
+                    'peer-hover:opacity-100 peer-focus-visible:opacity-100',
+                    'group-hover:opacity-100 group-focus-within:opacity-100'
                   )}
                 >
🧹 Nitpick comments (8)
apps/client/src/pages/level/types/treeLevelType.ts (2)

9-15: 단일 출처 테이블 설계 훌륭함; 무결성 검증(테스트/개발시 어서션) 추가 제안

레벨 구간이 변경될 때 실수(겹침/누락)를 막기 위해 개발 환경에서만 구간 일관성 검사를 권장합니다. 또한 rangeLabel은 min/max로부터 파생 가능하므로(복제 데이터) 소비 측에서 계산하도록 전환을 고려해 주세요.

아래 유틸은 파일 외부(예: 테스트 또는 초기화 코드)에서 추가:

// dev-only: 테이블 무결성 검사
export function assertTreeLevelTable(table = TREE_LEVEL_TABLE) {
  table.forEach((row, i) => {
    if (i === 0) {
      if (row.min !== 0) throw new Error('첫 구간 min은 0이어야 합니다.');
    }
    if (row.max != null && row.min > row.max) {
      throw new Error(`레벨 ${row.level}: min <= max 이어야 합니다.`);
    }
    if (i > 0) {
      const prev = table[i - 1];
      const prevEnd = prev.max ?? Infinity;
      if (row.min !== (prev.max == null ? prevEnd : prevEnd + 1)) {
        throw new Error(`레벨 ${row.level}: 이전 구간과 불연속입니다.`);
      }
    }
    if (i < table.length - 1 && row.max == null) {
      throw new Error(`마지막 레벨 외에는 max가 필요합니다.`);
    }
  });
}

// (선택) rangeLabel 파생
export function formatRangeLabel(min: number, max?: number) {
  return max == null ? `${min}개 이상` : min === max ? `${min}개` : `${min}${max}개`;
}

21-25: progressToNext의 범위(0–100) 문서화/브랜딩 타입 고려

UI 진행률 계약을 타입으로 더 명확히 하고 싶다면 브랜딩 타입을 사용해도 좋습니다. (선택)

적용 diff(해당 라인 내 변경):

-export type TreeLevelResult = TreeLevelRow & {
-  progressToNext: number;
+export type TreeLevelResult = TreeLevelRow & {
+  progressToNext: Percent;
   nextMin?: number;
   remainingToNext?: number;
 };

추가 타입 선언(파일 상단 등 외부에 배치):

type Percent = number & { readonly __brand: 'Percent0to100' };
apps/client/src/pages/level/components/LevelInfoCard.tsx (2)

15-15: 원본 불변 역순 변환은 OK; toReversed()로 간결화 가능(환경 지원 시)

ES2023을 타깃/폴리필 중이라면 아래가 더 명료합니다. 미지원이면 현행 유지 권장.

-  const rows = [...TREE_LEVEL_TABLE].reverse();
+  const rows = TREE_LEVEL_TABLE.toReversed();

환경 확인: tsconfig lib에 ES2023(Array) 포함 여부와 빌드 타깃/브라우저 지원을 점검해 주세요.


36-43: 아이콘 접근성 라벨을 더 구체적으로

화면읽기에서 의미가 선명하도록 이름 포함을 권장합니다.

-                <Icon
-                  name={LEVEL_TOOLTIP_ICON[row.level]}
-                  width={46}
-                  height={46}
-                  className="rounded-[0.8rem]"
-                  aria-label={`${row.level} 썸네일 아이콘`}
-                />
+                <Icon
+                  name={LEVEL_TOOLTIP_ICON[row.level]}
+                  width={46}
+                  height={46}
+                  className="rounded-[0.8rem]"
+                  aria-label={`${row.name} (레벨 ${row.level}) 아이콘`}
+                />
apps/client/src/pages/level/Level.tsx (3)

11-13: 임시 하드코드 값 커밋 지양 + 입력 가드 계획

acorns=1 하드코드는 남기지 말고 API 연동 시점에 제거하세요. 또한 음수/NaN/실수 대비 입력 가드는 유틸(getTreeLevel)에서 처리하도록 별도 코멘트에 제안했습니다.


15-15: 모바일 주소창 변동 대응(h-dvh 대신 svh 사용 권장)

iOS 등에서 dvh가 튀는 이슈가 있어 svh 사용을 권장합니다.

-    <div className={cn('bg-subcolor mx-auto h-dvh w-full overflow-hidden')}>
+    <div className={cn('bg-subcolor mx-auto min-h-[100svh] w-full overflow-hidden')}>

17-17: 불필요한 타입 단언 제거

getTreeLevel의 반환 타입이 TreeLevelResult라면 level은 이미 TreeLevel일 가능성이 높습니다. 캐스트 제거해보세요(유틸 타입 정합성은 아래 코멘트 참조).

-        <LevelScene level={info.level as TreeLevel} />
+        <LevelScene level={info.level} />
apps/client/src/pages/level/utils/treeLevel.ts (1)

15-26: progressToNext 하한 보장

이상치 대비로 [0,1] 범위를 확실히 보장하는 편이 안전합니다.

-  const span = Math.max(1, next.min - row.min);
-  const progressToNext = Math.min(1, (count - row.min) / span);
+  const span = Math.max(1, next.min - row.min);
+  const raw = (count - row.min) / span;
+  const progressToNext = Math.max(0, Math.min(1, raw));
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9220ac4 and 1eb6504.

📒 Files selected for processing (6)
  • apps/client/src/pages/level/Level.tsx (1 hunks)
  • apps/client/src/pages/level/components/LevelInfoCard.tsx (1 hunks)
  • apps/client/src/pages/level/components/LevelScene.tsx (1 hunks)
  • apps/client/src/pages/level/components/TreeStatusCard.tsx (1 hunks)
  • apps/client/src/pages/level/types/treeLevelType.ts (1 hunks)
  • apps/client/src/pages/level/utils/treeLevel.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/client/src/pages/level/components/LevelScene.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-04T08:38:00.583Z
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#54
File: apps/client/src/pages/level/components/TreeStatusCard.tsx:12-12
Timestamp: 2025-09-04T08:38:00.583Z
Learning: In TreeStatusCard component, the progress bar calculation `info.level * 20` is intentional design - level 1 starts at 20%, level 2 at 40%, etc., up to level 5 at 100%. This ensures no level shows 0% progress for UX purposes.

Applied to files:

  • apps/client/src/pages/level/components/TreeStatusCard.tsx
🧬 Code graph analysis (2)
apps/client/src/pages/level/Level.tsx (6)
apps/client/src/pages/level/utils/treeLevel.ts (1)
  • getTreeLevel (28-31)
packages/design-system/src/lib/utils.ts (1)
  • cn (108-110)
apps/client/src/pages/level/components/LevelScene.tsx (1)
  • LevelScene (23-34)
apps/client/src/pages/level/types/treeLevelType.ts (1)
  • TreeLevel (17-17)
apps/client/src/pages/level/components/LevelInfoCard.tsx (1)
  • LevelInfoCard (14-68)
apps/client/src/pages/level/components/TreeStatusCard.tsx (1)
  • TreeStatusCard (9-38)
apps/client/src/pages/level/types/treeLevelType.ts (1)
apps/client/src/pages/level/utils/treeLevel.ts (1)
  • TREE_LEVEL_TABLE (33-33)
🔇 Additional comments (4)
apps/client/src/pages/level/types/treeLevelType.ts (1)

17-17: TreeLevel 유니온을 테이블에서 자동 파생한 점 좋습니다

단일 소스 유지에 유리하고 추론 품질도 좋습니다. 별도 조치 필요 없습니다.

apps/client/src/pages/level/components/TreeStatusCard.tsx (1)

3-3: 경로 별칭(@pages)으로의 import 정리 LGTM

모듈 경로 일관성이 좋아졌습니다. 기존 학습 노트의 의도적 계산(info.level * 20)과도 충돌 없습니다.

apps/client/src/pages/level/components/LevelInfoCard.tsx (1)

4-4: 타입/테이블 중앙화에 따른 import 변경 LGTM

의존성 방향이 명확해졌습니다.

apps/client/src/pages/level/Level.tsx (1)

33-38: pointer-events 기본값 수정은 훌륭합니다

보이지 않을 때 클릭 방지 처리(pointer-events-none 기본)는 적절합니다.

Copy link
Collaborator

@jllee000 jllee000 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!
pr에 남겨주신 화면 레이아웃이 깨지는(가려지는) 부분은 디바이스 크기나 화면에 따라.. 피그마랑 차이가 계속 생길 거 같네요!! 이런 케이스가 많이 보이게 된다면, 추후에 px로 지정한 공컴들을 고정값이 아니라 w-full로 하고 외부에서 % 로 감싸는 방식으로 수정해야 할 수도 있겠단 생각이 드네요,,,ㅠㅠ

Comment on lines +32 to +37
id="level-info-card"
className={cn(
'pointer-events-none absolute left-0 top-[3rem] z-[20]',
'opacity-0 transition-opacity duration-150',
'peer-hover:pointer-events-auto peer-focus-visible:pointer-events-auto',
'peer-hover:opacity-100 peer-focus-visible:opacity-100'
Copy link
Collaborator

Choose a reason for hiding this comment

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

이거 cn이 스타일값 객체들을 여러개 합칠 때 사용하는 걸로 아는데요!
이 코드 경우에, 분기나 객체 구분이 안보이는데, z-[20] opacity-0 ..처럼 쭉 이어서 나열하지않고 cn을 쓰신 이유가 궁금합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

맞습니당 이 코드에 따로 분기나 객체 구분은 없어요-! 다만 한줄로 쭉 나열해서 쓰다보면 가독성이 좋지 않아 cn으로 구분해서 사용했습니다-!

Comment on lines +28 to +30
src={src}
draggable={false}
className="pointer-events-none h-full w-full select-none rounded-[1.2rem] object-contain object-[right_bottom]"
Copy link
Collaborator

Choose a reason for hiding this comment

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

와우 이미지 드래그나 복사 불가하게 제한해둔 거 너무 세심하고 꼼꼼하시네유

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

테스트하다가 드래그되는게 불편해서 따로 처리해뒀습니당-!

import { Level, Progress } from '@pinback/design-system/ui';
import { cn } from '@pinback/design-system/utils';
import { getTreeLevel } from '../utils/treeLevel';
import { getTreeLevel } from '@pages/level/utils/treeLevel';
Copy link
Collaborator

Choose a reason for hiding this comment

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

제 코드에도 상대경로로 작업한게 많은데, 이거보고 저도 바꿔놔야겠다고 생각났네요

@jjangminii jjangminii merged commit c792060 into develop Sep 9, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 개발하라 개발 달려라 달려

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 마이페이지 레이아웃 구현

3 participants