[refactor] AdminTabAdapter 도입 및 어드민 탭 모바일/데스크탑 분리 - #1841
Conversation
- 어드민 탭의 모바일/데스크탑 분기를 전담하는 AdminTabAdapter 컴포넌트 추가 - AdminRoutes에서 AdminTabAdapter를 적용하고 카테고리 주석으로 라우트 구조 명확화 - 라우트 순서를 ADMIN_TABS 상수 순서와 일치하도록 정렬
- Desktop 컴포넌트에서 useDevice 분기 제거, 데스크탑 렌더링만 담당하도록 단순화 - Mobile 컴포넌트가 props 대신 훅을 직접 호출하도록 변경해 완전히 독립적으로 동작
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @suhyun113's task in 2m 9s —— View job harry 리뷰 (opened) 🧐
총평
한 가지 동작 회귀만 짚었어요 (인라인 참고):
리뷰받고 싶다고 하신 부분:
|
Walkthrough관리자 동아리 프로필 라우팅에 디바이스 어댑터를 도입하고, 정보·소개 모바일 편집 탭이 각 편집 훅의 상태를 직접 사용하도록 변경했습니다. 데스크톱 탭에서는 모바일 분기 로직을 제거했습니다. Changes관리자 동아리 프로필 편집
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant AdminRoutes
participant AdminTabAdapter
participant DeviceHook as useDevice
participant EditTab as 동아리 편집 탭
AdminRoutes->>AdminTabAdapter: 프로필 라우트 렌더링
AdminTabAdapter->>DeviceHook: 디바이스 유형 조회
alt 모바일 또는 태블릿
AdminTabAdapter->>EditTab: 모바일 탭 렌더링
else 데스크톱
AdminTabAdapter->>EditTab: 데스크톱 탭 렌더링
end
Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
| setFaqs, | ||
| isDirty, | ||
| handleUpdateClub, | ||
| } = useClubIntroEdit(); |
There was a problem hiding this comment.
리팩터링 전에는 ClubIntroEditTab이 device 분기 위에서 useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE)를 호출해서 모바일에서도 페이지뷰가 찍혔어요. 이번 변경으로 모바일은 ClubIntroEditTabMobile이 바로 렌더되는데 여기엔 useTrackPageView 호출이 없어서, 모바일 동아리 소개 편집 페이지뷰 트래킹이 유실돼요.
참고로 ClubInfoEditTab 쪽은 useTrackPageView가 useClubInfoEdit 훅 내부에 있어서 모바일에서도 그대로 유지돼요. 소개 탭도 동일하게 아래처럼 추가해 주는 게 좋아요.
| } = useClubIntroEdit(); | |
| } = useClubIntroEdit(); | |
| useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE); |
(상단에 import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView';와 import { PAGE_VIEW } from '@/constants/eventName'; 추가가 필요해요. 또는 ClubInfo처럼 훅 내부로 옮기면 분기 위치와 무관하게 트래킹이 유지돼요.)
There was a problem hiding this comment.
Code Review
This pull request introduces AdminTabAdapter to separate desktop and mobile views for the club info and club intro edit tabs, simplifying the components and their props. However, the reviewer raised a critical concern regarding state loss: since the desktop and mobile components now independently call their respective state hooks, resizing the window or rotating a mobile device will cause the active component to unmount, resetting all unsaved form data. To resolve this, it is recommended to either lift the state up to a common parent (or Context) or pass the state down as props. Additionally, the reviewer suggested centralizing page view tracking within AdminTabAdapter to prevent duplicate or missing analytics events during view switches.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
|
||
| const AdminTabAdapter = ({ desktop, mobile }: AdminTabAdapterProps) => { | ||
| const { isMobile, isTablet } = useDevice(); | ||
| return <>{isMobile || isTablet ? mobile : desktop}</>; |
There was a problem hiding this comment.
⚠️ 화면 크기 조절 및 기기 회전 시 작성 중인 데이터 유실 위험 (State Loss)
AdminTabAdapter를 통해 데스크탑 컴포넌트와 모바일 컴포넌트를 완전히 분리하고, 각 컴포넌트 내부에서 useClubInfoEdit / useClubIntroEdit 훅을 독립적으로 호출하도록 변경되었습니다.
이 구조는 컴포넌트 간의 의존성을 줄이고 props 전달을 단순화하는 장점이 있지만, 치명적인 UX 문제를 발생시킬 수 있습니다:
- 문제 상황: 사용자가 데스크탑 뷰에서 폼을 입력하던 중 브라우저 창 크기를 줄이거나, 모바일 기기에서 화면을 회전(가로/세로 전환)하여 브레이크포인트가 변경되면
AdminTabAdapter에 의해 기존 컴포넌트가 언마운트되고 새로운 컴포넌트가 마운트됩니다. - 원인: React 컴포넌트가 언마운트되면 내부 상태(state)는 완전히 소멸합니다. 두 컴포넌트가 동일한 커스텀 훅을 호출하더라도 상태를 공유하지 않으므로, 저장하지 않은 모든 입력 데이터가 유실되고 서버에서 가져온 초기 데이터로 리셋됩니다.
💡 해결 방안 제안
-
방안 A (권장 - 상태 유지와 UI 분리):
기존처럼 하나의 공통 진입점 컴포넌트(예:ClubInfoEditTab)에서useClubInfoEdit훅을 한 번만 호출하여 상태를 유지하고, 내부에서isMobile여부에 따라 데스크탑 전용 UI 컴포넌트와 모바일 전용 UI 컴포넌트를 스위칭하며 props를 넘겨주는 방식을 유지하는 것이 안전합니다. 12개의 props가 너무 많다면, 폼 상태 객체를 하나로 묶어서 전달하거나 필요한 부분만 그룹화하여 전달하는 방식으로 props 구조를 개선할 수 있습니다. -
방안 B (Context API 활용):
꼭 컴포넌트를 완전히 독립시키고 싶다면,AdminTabAdapter상위에ClubInfoEditProvider와 같은 Context를 두어 폼 상태를 공통 부모 레벨에서 관리하고, 데스크탑/모바일 컴포넌트는 각각 이 Context를 구독하도록 구현해야 합니다.
| import useDevice from '@/hooks/useDevice'; | ||
|
|
||
| interface AdminTabAdapterProps { | ||
| desktop: React.ReactNode; | ||
| mobile: React.ReactNode; | ||
| } | ||
|
|
||
| const AdminTabAdapter = ({ desktop, mobile }: AdminTabAdapterProps) => { | ||
| const { isMobile, isTablet } = useDevice(); | ||
| return <>{isMobile || isTablet ? mobile : desktop}</>; | ||
| }; |
There was a problem hiding this comment.
📊 페이지 뷰 중복 트래킹 및 누락 문제 해결
현재 ClubIntroEditTabMobile에서는 페이지 뷰 트래킹(useTrackPageView)이 누락되어 있고, ClubInfoEditTab에서는 useClubInfoEdit 훅 내부에서 트래킹을 처리하고 있어 화면 크기 조절(resize) 시 컴포넌트가 마운트/언마운트되면서 중복 트래킹이 발생할 수 있습니다.
이를 해결하기 위해 AdminTabAdapter에서 pageViewName을 prop으로 받아 공통으로 페이지 뷰를 트래킹하도록 개선하는 것을 제안합니다. AdminTabAdapter는 resize 시에도 언마운트되지 않고 유지되므로 중복 트래킹을 방지하고 모바일/데스크탑 모두 일관되게 트래킹할 수 있습니다.
참고: 이 변경을 적용한 후, ClubIntroEditTab.tsx 및 useClubInfoEdit.ts 내부에 남아있는 기존 useTrackPageView 호출과 관련 import는 제거해 주세요.
| import useDevice from '@/hooks/useDevice'; | |
| interface AdminTabAdapterProps { | |
| desktop: React.ReactNode; | |
| mobile: React.ReactNode; | |
| } | |
| const AdminTabAdapter = ({ desktop, mobile }: AdminTabAdapterProps) => { | |
| const { isMobile, isTablet } = useDevice(); | |
| return <>{isMobile || isTablet ? mobile : desktop}</>; | |
| }; | |
| import useDevice from '@/hooks/useDevice'; | |
| import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; | |
| interface AdminTabAdapterProps { | |
| desktop: React.ReactNode; | |
| mobile: React.ReactNode; | |
| pageViewName: string; | |
| } | |
| const AdminTabAdapter = ({ desktop, mobile, pageViewName }: AdminTabAdapterProps) => { | |
| const { isMobile, isTablet } = useDevice(); | |
| useTrackPageView(pageViewName); | |
| return <>{isMobile || isTablet ? mobile : desktop}</>; | |
| }; |
| import { Navigate, Route, Routes } from 'react-router-dom'; | ||
| import useDevice from '@/hooks/useDevice'; | ||
| import AdminPage from '@/pages/AdminPage/AdminPage'; | ||
| import AdminTabAdapter from '@/pages/AdminPage/AdminTabAdapter'; |
There was a problem hiding this comment.
| <Route | ||
| path='club-info' | ||
| element={ | ||
| <AdminTabAdapter | ||
| desktop={<ClubInfoEditTab />} | ||
| mobile={<ClubInfoEditTabMobile />} | ||
| /> | ||
| } | ||
| /> | ||
| <Route | ||
| path='club-intro' | ||
| element={ | ||
| <AdminTabAdapter | ||
| desktop={<ClubIntroEditTab />} | ||
| mobile={<ClubIntroEditTabMobile />} | ||
| /> | ||
| } | ||
| /> |
There was a problem hiding this comment.
AdminTabAdapter에 각 탭에 맞는 pageViewName을 전달하여 페이지 뷰 트래킹이 일관되게 동작하도록 설정합니다.
| <Route | |
| path='club-info' | |
| element={ | |
| <AdminTabAdapter | |
| desktop={<ClubInfoEditTab />} | |
| mobile={<ClubInfoEditTabMobile />} | |
| /> | |
| } | |
| /> | |
| <Route | |
| path='club-intro' | |
| element={ | |
| <AdminTabAdapter | |
| desktop={<ClubIntroEditTab />} | |
| mobile={<ClubIntroEditTabMobile />} | |
| /> | |
| } | |
| /> | |
| <Route | |
| path='club-info' | |
| element={ | |
| <AdminTabAdapter | |
| desktop={<ClubInfoEditTab />} | |
| mobile={<ClubInfoEditTabMobile />} | |
| pageViewName={PAGE_VIEW.CLUB_INFO_EDIT_PAGE} | |
| /> | |
| } | |
| /> | |
| <Route | |
| path='club-intro' | |
| element={ | |
| <AdminTabAdapter | |
| desktop={<ClubIntroEditTab />} | |
| mobile={<ClubIntroEditTabMobile />} | |
| pageViewName={PAGE_VIEW.CLUB_INTRO_EDIT_PAGE} | |
| /> | |
| } | |
| /> |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/pages/AdminPage/AdminTabAdapter.tsx`:
- Around line 8-11: Update AdminTabAdapter so device changes do not replace the
desktop and mobile ReactNode trees, which currently unmounts edit views and
clears useClubInfoEdit/useClubIntroEdit state. Keep both views mounted and
switch their visibility or presentation based on isMobile/isTablet, preserving
each view’s local state across resize and rotation.
In
`@frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTabMobile.tsx`:
- Around line 21-41: 모바일 ClubIntroEditTabMobile에서 club-intro 편집 페이지뷰 추적이 누락되어
있습니다. ClubIntroEditTabMobile의 본문에서
useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE)를 호출하거나, 권장 패턴에 따라
useClubIntroEdit 훅 내부에 동일한 호출을 추가해 모바일·데스크톱에서 일관되게 이벤트가 발생하도록 수정하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: dc94a95d-6a82-401b-8135-e5f8b456835c
📒 Files selected for processing (6)
frontend/src/pages/AdminPage/AdminRoutes.tsxfrontend/src/pages/AdminPage/AdminTabAdapter.tsxfrontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsxfrontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTabMobile.tsxfrontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsxfrontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTabMobile.tsx
💤 Files with no reviewable changes (2)
- frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
- frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx
| const AdminTabAdapter = ({ desktop, mobile }: AdminTabAdapterProps) => { | ||
| const { isMobile, isTablet } = useDevice(); | ||
| return <>{isMobile || isTablet ? mobile : desktop}</>; | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# useDevice 훅 구현 확인 (resize 리스너 존재 여부, 재계산 시점 등)
fd -i useDevice.ts src -x cat -n {}Repository: Moadong/moadong
Length of output: 244
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repo files matching useDevice =="
git ls-files | rg 'useDevice(\.[tj]sx?|)$|AdminTabAdapter\.tsx$|ClubInfoEditTab|ClubIntroEdit|PrivateRoute'
echo
echo "== search for useDevice references =="
rg -n "useDevice" .
echo
echo "== search for admin edit state hooks/components =="
rg -n "useClubInfoEdit|useClubIntroEdit|isDirty|draft|edit" frontend/src/pages frontend/src/components frontend/src/hooks frontend/src/apisRepository: Moadong/moadong
Length of output: 13211
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== frontend/src/hooks/useDevice.ts =="
sed -n '1,220p' frontend/src/hooks/useDevice.ts | cat -n
echo
echo "== frontend/src/pages/AdminPage/AdminTabAdapter.tsx =="
sed -n '1,120p' frontend/src/pages/AdminPage/AdminTabAdapter.tsx | cat -n
echo
echo "== frontend/src/pages/AdminPage/AdminRoutes.tsx =="
sed -n '1,220p' frontend/src/pages/AdminPage/AdminRoutes.tsx | cat -n
echo
echo "== ClubInfoEditTab / ClubInfoEditTabMobile =="
sed -n '1,220p' frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx | cat -n
echo "---"
sed -n '1,240p' frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTabMobile.tsx | cat -n
echo
echo "== ClubIntroEditTab / ClubIntroEditTabMobile =="
sed -n '1,220p' frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx | cat -n
echo "---"
sed -n '1,240p' frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTabMobile.tsx | cat -nRepository: Moadong/moadong
Length of output: 27775
🏁 Script executed:
#!/bin/bash
set -euo pipefail
for f in \
frontend/src/hooks/useDevice.ts \
frontend/src/pages/AdminPage/AdminTabAdapter.tsx \
frontend/src/pages/AdminPage/AdminRoutes.tsx \
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx \
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTabMobile.tsx \
frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx \
frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTabMobile.tsx
do
echo "===== $f ====="
sed -n '1,220p' "$f" | cat -n
echo
doneRepository: Moadong/moadong
Length of output: 28038
디바이스 전환 시 편집 초안이 초기화됩니다. useDevice가 resize에 반응하고, AdminTabAdapter가 desktop/mobile를 서로 다른 ReactNode로 교체해서 화면 크기 변화나 회전 때 useClubInfoEdit/useClubIntroEdit의 로컬 상태가 unmount/mount로 사라집니다. 두 뷰가 상태를 공유하도록 구조를 바꾸는 게 안전합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/AdminPage/AdminTabAdapter.tsx` around lines 8 - 11, Update
AdminTabAdapter so device changes do not replace the desktop and mobile
ReactNode trees, which currently unmounts edit views and clears
useClubInfoEdit/useClubIntroEdit state. Keep both views mounted and switch their
visibility or presentation based on isMobile/isTablet, preserving each view’s
local state across resize and rotation.
| import useClubIntroEdit from './hooks/useClubIntroEdit'; | ||
|
|
||
| interface ClubIntroEditTabMobileProps { | ||
| introDescription: string; | ||
| setIntroDescription: (v: string) => void; | ||
| activityDescription: string; | ||
| setActivityDescription: (v: string) => void; | ||
| awards: Award[]; | ||
| idealCandidate: IdealCandidate; | ||
| setIdealCandidate: (v: IdealCandidate) => void; | ||
| benefits: string; | ||
| setBenefits: (v: string) => void; | ||
| faqs: FAQ[]; | ||
| setFaqs: (v: FAQ[]) => void; | ||
| isDirty: boolean; | ||
| handleUpdateClub: () => void; | ||
| } | ||
|
|
||
| const ClubIntroEditTabMobile = ({ | ||
| introDescription, | ||
| setIntroDescription, | ||
| activityDescription, | ||
| setActivityDescription, | ||
| awards, | ||
| idealCandidate, | ||
| setIdealCandidate, | ||
| benefits, | ||
| setBenefits, | ||
| faqs, | ||
| setFaqs, | ||
| isDirty, | ||
| handleUpdateClub, | ||
| }: ClubIntroEditTabMobileProps) => { | ||
| const ClubIntroEditTabMobile = () => { | ||
| const navigate = useNavigate(); | ||
|
|
||
| const { | ||
| introDescription, | ||
| setIntroDescription, | ||
| activityDescription, | ||
| setActivityDescription, | ||
| awards, | ||
| idealCandidate, | ||
| setIdealCandidate, | ||
| benefits, | ||
| setBenefits, | ||
| faqs, | ||
| setFaqs, | ||
| isDirty, | ||
| handleUpdateClub, | ||
| } = useClubIntroEdit(); | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
모바일 사용자의 club-intro 편집 페이지뷰 트래킹 누락.
데스크톱 ClubIntroEditTab.tsx는 컴포넌트 본문에서 직접 useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE)를 호출하지만, useClubIntroEdit 훅 내부에는 이 호출이 없습니다. 이전에는 디바이스 분기가 ClubIntroEditTab 내부에서 이뤄져 이 컴포넌트가 항상 마운트되었으므로 모바일 사용자도 트래킹되었지만, 이제 AdminTabAdapter가 모바일/태블릿에는 ClubIntroEditTabMobile만 마운트하므로 이 파일에 동일한 호출이 없어 해당 이벤트가 전혀 발생하지 않습니다. (참고로 useClubInfoEdit는 훅 내부에 useTrackPageView를 포함해 이 문제가 없어, club-info/club-intro 간 비대칭이 존재합니다.)
🔧 제안 수정
import { useNavigate } from 'react-router-dom';
import WebviewTopBar from '`@/components/common/WebviewTopBar/WebviewTopBar`';
+import { PAGE_VIEW } from '`@/constants/pageView`';
+import useTrackPageView from '`@/hooks/useTrackPageView`';
import {
ACTIVITY_DESCRIPTION_MAX,
...
} from '`@/constants/adminFieldLimits`';
...
const ClubIntroEditTabMobile = () => {
const navigate = useNavigate();
+ useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE);
const {(가급적 useClubIntroEdit 훅 내부로 옮겨 useClubInfoEdit와 동일한 패턴으로 통일하는 것을 권장합니다.)
📝 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.
| import useClubIntroEdit from './hooks/useClubIntroEdit'; | |
| interface ClubIntroEditTabMobileProps { | |
| introDescription: string; | |
| setIntroDescription: (v: string) => void; | |
| activityDescription: string; | |
| setActivityDescription: (v: string) => void; | |
| awards: Award[]; | |
| idealCandidate: IdealCandidate; | |
| setIdealCandidate: (v: IdealCandidate) => void; | |
| benefits: string; | |
| setBenefits: (v: string) => void; | |
| faqs: FAQ[]; | |
| setFaqs: (v: FAQ[]) => void; | |
| isDirty: boolean; | |
| handleUpdateClub: () => void; | |
| } | |
| const ClubIntroEditTabMobile = ({ | |
| introDescription, | |
| setIntroDescription, | |
| activityDescription, | |
| setActivityDescription, | |
| awards, | |
| idealCandidate, | |
| setIdealCandidate, | |
| benefits, | |
| setBenefits, | |
| faqs, | |
| setFaqs, | |
| isDirty, | |
| handleUpdateClub, | |
| }: ClubIntroEditTabMobileProps) => { | |
| const ClubIntroEditTabMobile = () => { | |
| const navigate = useNavigate(); | |
| const { | |
| introDescription, | |
| setIntroDescription, | |
| activityDescription, | |
| setActivityDescription, | |
| awards, | |
| idealCandidate, | |
| setIdealCandidate, | |
| benefits, | |
| setBenefits, | |
| faqs, | |
| setFaqs, | |
| isDirty, | |
| handleUpdateClub, | |
| } = useClubIntroEdit(); | |
| import { useNavigate } from 'react-router-dom'; | |
| import WebviewTopBar from '`@/components/common/WebviewTopBar/WebviewTopBar`'; | |
| import { PAGE_VIEW } from '`@/constants/pageView`'; | |
| import useTrackPageView from '`@/hooks/useTrackPageView`'; | |
| import { | |
| ACTIVITY_DESCRIPTION_MAX, | |
| ... | |
| } from '`@/constants/adminFieldLimits`'; | |
| import useClubIntroEdit from './hooks/useClubIntroEdit'; | |
| const ClubIntroEditTabMobile = () => { | |
| const navigate = useNavigate(); | |
| useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE); | |
| const { | |
| introDescription, | |
| setIntroDescription, | |
| activityDescription, | |
| setActivityDescription, | |
| awards, | |
| idealCandidate, | |
| setIdealCandidate, | |
| benefits, | |
| setBenefits, | |
| faqs, | |
| setFaqs, | |
| isDirty, | |
| handleUpdateClub, | |
| } = useClubIntroEdit(); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTabMobile.tsx`
around lines 21 - 41, 모바일 ClubIntroEditTabMobile에서 club-intro 편집 페이지뷰 추적이 누락되어
있습니다. ClubIntroEditTabMobile의 본문에서
useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE)를 호출하거나, 권장 패턴에 따라
useClubIntroEdit 훅 내부에 동일한 호출을 추가해 모바일·데스크톱에서 일관되게 이벤트가 발생하도록 수정하세요.
seongwon030
left a comment
There was a problem hiding this comment.
width를 줄였다 늘였다 할 때 아예 다른 컴포넌트가 렌더링된다면 상태유지가 되어야 한다는 전제 하여야 할 것 같은데 혹시 이것도 같이 고려하셨을까요?
현재 어드민 페이지의 데스크탑과 모바일 컴포넌트의 구조를 생각하다보니 이 문제는 고려하지 못했네요 말씀해주신대로 resize하면 한쪽이 언마운트되어서 서로 가지고 있는 state가 소멸됩니다 당연히 이 부분이 중요하지만, 제 생각에 현재 구조가 데스크탑 컴포넌트에 모바일 컴포넌트가 의존하고 있어서 불필요하게 마운트되고 있어 변경된 현재 구조가 더 좋다고 생각했습니다 두가지 모두 만족시키는 방법을 찾아봤는데 tanstack, zustand, context 없이 단순하게 state를 올려서 해결도 가능한거 같아서 먼저 물어봅니다 현재 상황에서 가장 간단한건 hook을 AdminTabAdapter보다 한 단계 위에 두어 한 번만 파일을 호출하도록 Wrapper를 추가하는 방법이 있을 것 같습니다 데스크탑, 모바일 각 파일 내부에서는 훅 호출을 제거하고 props로 받도록 수정하면 될 것 같습니다. 그런데 이 경우 데스크탑 모바일이 각각 총 3개의 파일에 의존해야해서 복잡할 것 같습니다. 가장 간단하긴 한데 이 방법으로 진행할까요? |
#️⃣연관된 이슈
#1840
📝작업 내용
어드민 탭의 모바일/데스크탑 분기 로직을 기존 각 탭 컴포넌트에서 분리하여
AdminTabAdapter로 중앙화했습니다.변경 내용
AdminTabAdapter도입useDevice로 device를 감지해desktop/mobile중 하나를 렌더링하는 전담 컴포넌트 추가AdminRoutes에서AdminTabAdapter로 감싸는 방식으로 일관되게 확장 가능AdminRoutes라우트 구조 개선AdminTabAdapter적용 (club-info,club-intro)동아리 프로필/동아리 활동/지원 관리/계정 관리)으로 라우트 그룹 명확화ADMIN_TABS상수 순서와 일치하도록 정렬ClubInfoEditTab/ClubIntroEditTab모바일 컴포넌트 독립화useDevice분기 제거 → 데스크탑 렌더링만 담당Before / After
Before: 각 탭이 훅 호출 → device 분기 → mobile에 props 전달
After:
AdminTabAdapter가 분기, 각 컴포넌트는 독립중점적으로 리뷰받고 싶은 부분(선택)
AdminTabAdapter의 props 타입으로React.ReactNode를 사용한 것이 적절한지 (vsReact.ComponentType)🫡 참고사항
PhotoEditTab은 별도 브랜치(feature/#1839)에서 동일한 패턴으로 작업 예정Summary by CodeRabbit