Skip to content

[refactor] AdminTabAdapter 도입 및 어드민 탭 모바일/데스크탑 분리 - #1841

Open
suhyun113 wants to merge 2 commits into
feature/#1834-photo-edit-mobile-components-MOA-1043from
refactor/#1840-add-admin-tab-router-MOA-1046
Open

[refactor] AdminTabAdapter 도입 및 어드민 탭 모바일/데스크탑 분리#1841
suhyun113 wants to merge 2 commits into
feature/#1834-photo-edit-mobile-components-MOA-1043from
refactor/#1840-add-admin-tab-router-MOA-1046

Conversation

@suhyun113

@suhyun113 suhyun113 commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

#️⃣연관된 이슈

#1840

📝작업 내용

어드민 탭의 모바일/데스크탑 분기 로직을 기존 각 탭 컴포넌트에서 분리하여 AdminTabAdapter로 중앙화했습니다.

변경 내용

AdminTabAdapter 도입

  • useDevice로 device를 감지해 desktop / mobile 중 하나를 렌더링하는 전담 컴포넌트 추가
  • 앞으로 모바일을 지원하는 탭은 AdminRoutes에서 AdminTabAdapter로 감싸는 방식으로 일관되게 확장 가능

AdminRoutes 라우트 구조 개선

  • AdminTabAdapter 적용 (club-info, club-intro)
  • 카테고리 주석(동아리 프로필 / 동아리 활동 / 지원 관리 / 계정 관리)으로 라우트 그룹 명확화
  • 라우트 순서를 ADMIN_TABS 상수 순서와 일치하도록 정렬

ClubInfoEditTab / ClubIntroEditTab 모바일 컴포넌트 독립화

  • Desktop 컴포넌트에서 useDevice 분기 제거 → 데스크탑 렌더링만 담당
  • Mobile 컴포넌트가 부모로부터 props를 받는 대신 훅을 직접 호출하도록 변경 → 완전히 독립적으로 동작

Before / After

Before: 각 탭이 훅 호출 → device 분기 → mobile에 props 전달

ClubInfoEditTab
  ├─ useClubInfoEdit() ← 모바일에서도 실행됨
  ├─ if (isMobile) return <ClubInfoEditTabMobile ...12개 props />
  └─ return <Desktop />

After: AdminTabAdapter가 분기, 각 컴포넌트는 독립

AdminTabAdapter (useDevice)
  ├─ <ClubInfoEditTab />   ← 데스크탑에서만 useClubInfoEdit 호출
  └─ <ClubInfoEditTabMobile /> ← 모바일에서만 useClubInfoEdit 호출

중점적으로 리뷰받고 싶은 부분(선택)

  • AdminTabAdapter의 props 타입으로 React.ReactNode를 사용한 것이 적절한지 (vs React.ComponentType)

🫡 참고사항

  • PhotoEditTab은 별도 브랜치(feature/#1839)에서 동일한 패턴으로 작업 예정

Summary by CodeRabbit

  • 새로운 기능
    • 관리자 페이지의 동아리 프로필 편집 화면이 기기 유형에 맞는 데스크톱·모바일 레이아웃을 제공합니다.
    • 동아리 정보 및 소개 편집 기능이 모바일과 태블릿 환경에 최적화되었습니다.
  • 개선 사항
    • 동아리 정보와 소개 편집 상태가 화면 간 일관되게 유지됩니다.
    • 관리자 메뉴가 동아리 프로필, 활동, 지원 관리, 계정 관리 영역으로 명확하게 정리되었습니다.

- 어드민 탭의 모바일/데스크탑 분기를 전담하는 AdminTabAdapter 컴포넌트 추가
- AdminRoutes에서 AdminTabAdapter를 적용하고 카테고리 주석으로 라우트 구조 명확화
- 라우트 순서를 ADMIN_TABS 상수 순서와 일치하도록 정렬
- Desktop 컴포넌트에서 useDevice 분기 제거, 데스크탑 렌더링만 담당하도록 단순화
- Mobile 컴포넌트가 props 대신 훅을 직접 호출하도록 변경해 완전히 독립적으로 동작
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
moadong Ready Ready Preview, Comment Jul 20, 2026 12:15pm

@reviewer-harry

reviewer-harry Bot commented Jul 20, 2026

Copy link
Copy Markdown

Claude finished @suhyun113's task in 2m 9s —— View job


harry 리뷰 (opened) 🧐

.github/harry-review-rules.md 기준으로 PR 전체 변경사항을 확인했어요.

  • 리뷰 룰 로드
  • PR 전체 diff 분석
  • 인라인 지적 등록

총평

AdminTabAdapter로 device 분기를 라우트 레이어에 모으고 각 탭이 자기 데이터를 직접 소유하게 한 방향은 아주 좋아요. props로 12개씩 넘기던 걸 없애서 모바일/데스크탑이 서로 독립적으로 동작하게 된 점이 특히 깔끔해요. 👍

한 가지 동작 회귀만 짚었어요 (인라인 참고):

  • 모바일 소개 편집 페이지뷰 트래킹 유실 — 기존 ClubIntroEditTab은 device 분기 에서 useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE)를 호출해 모바일에서도 페이지뷰가 찍혔는데, 이번 변경으로 모바일은 ClubIntroEditTabMobile이 바로 렌더되면서 이 호출이 빠졌어요. ClubInfo 쪽은 useTrackPageViewuseClubInfoEdit 훅 내부에 있어 유지되니, 소개 탭도 동일하게 맞춰 주면 좋아요. Fix this →

리뷰받고 싶다고 하신 부분: React.ReactNode vs React.ComponentType

지금처럼 React.ReactNode(엘리먼트 주입)로 두는 게 이 케이스엔 적절해요. element={<AdminTabAdapter desktop={<ClubInfoEditTab />} mobile={<ClubInfoEditTabMobile />} />}에서 두 JSX는 element 객체로만 생성될 뿐, 실제 컴포넌트 함수(그리고 내부 훅)는 AdminTabAdapter가 반환해 마운트하는 한쪽만 실행돼요. 그래서 훅/데이터 패칭 이중 실행 걱정은 없고, 사용처도 desktop={<A/>} 형태라 읽기 쉬워요.
React.ComponentType은 어댑터 안에서 <Desktop />처럼 직접 렌더하거나 React.lazy로 코드 스플리팅할 여지가 있을 때 유리한데, 지금 요구사항엔 과해요. 나중에 탭 단위 lazy 로딩을 도입하면 그때 ComponentType으로 전환을 고려해도 충분해요.

그 외 라우트 정렬/카테고리 주석, 타입 정리는 룰 위반 없이 깔끔해요.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

관리자 동아리 프로필 라우팅에 디바이스 어댑터를 도입하고, 정보·소개 모바일 편집 탭이 각 편집 훅의 상태를 직접 사용하도록 변경했습니다. 데스크톱 탭에서는 모바일 분기 로직을 제거했습니다.

Changes

관리자 동아리 프로필 편집

Layer / File(s) Summary
디바이스별 관리자 라우팅
frontend/src/pages/AdminPage/AdminRoutes.tsx, frontend/src/pages/AdminPage/AdminTabAdapter.tsx
AdminTabAdapter가 모바일·태블릿에는 모바일 편집 탭을, 그 외에는 데스크톱 편집 탭을 렌더링하도록 club-infoclub-intro 라우트를 변경했습니다. account-edit 라우트는 계정 관리 섹션으로 재배치했습니다.
동아리 정보 편집 상태 연결
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/*
데스크톱 탭의 모바일 분기를 제거하고, 모바일 탭이 useClubInfoEdit()에서 상태와 핸들러를 직접 사용하도록 변경했습니다. 소셜 링크 저장은 내부 상태 병합 방식으로 처리합니다.
동아리 소개 편집 상태 연결
frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/*
데스크톱 탭의 모바일 분기를 제거하고, 모바일 탭이 useClubIntroEdit()에서 편집 상태와 업데이트 핸들러를 직접 가져오도록 변경했습니다.

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
Loading

Possibly related issues

  • Moadong/moadong 이슈 1801 — ClubIntroEditTabMobileuseClubIntroEdit 기반으로 통합하는 구현 범위가 직접 연결됩니다.

Possibly related PRs

  • Moadong/moadong#1763ClubInfoEditTab의 모바일 위임 및 useClubInfoEdit 적용과 동일한 코드 경로를 다룹니다.
  • Moadong/moadong#1781 — 모바일 소셜 링크 저장을 외부 콜백에서 훅 내부 상태 병합으로 전환한 흐름과 직접 연결됩니다.
  • Moadong/moadong#1805ClubIntroEditTab 모바일 UI와 useClubIntroEdit 상태 연결 변경이 동일한 코드 경로에 있습니다.

Suggested labels: 🔨 Refactor, 💻 FE

Suggested reviewers: seongwon030, lepitaaar

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed AdminTabAdapter 도입과 어드민 탭의 모바일/데스크탑 분리라는 주요 변경점을 정확히 요약합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#1840-add-admin-tab-router-MOA-1046

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.

@suhyun113 suhyun113 changed the title refactor(admin): AdminTabAdapter 도입 및 어드민 탭 모바일/데스크탑 분리 [refactor] AdminTabAdapter 도입 및 어드민 탭 모바일/데스크탑 분리 Jul 20, 2026
@suhyun113 suhyun113 added 🔨 Refactor 코드 리팩토링 💻 FE Frontend labels Jul 20, 2026
@suhyun113
suhyun113 requested a review from seongwon030 July 20, 2026 12:15
setFaqs,
isDirty,
handleUpdateClub,
} = useClubIntroEdit();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

리팩터링 전에는 ClubIntroEditTab이 device 분기 위에서 useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE)를 호출해서 모바일에서도 페이지뷰가 찍혔어요. 이번 변경으로 모바일은 ClubIntroEditTabMobile이 바로 렌더되는데 여기엔 useTrackPageView 호출이 없어서, 모바일 동아리 소개 편집 페이지뷰 트래킹이 유실돼요.

참고로 ClubInfoEditTab 쪽은 useTrackPageViewuseClubInfoEdit 훅 내부에 있어서 모바일에서도 그대로 유지돼요. 소개 탭도 동일하게 아래처럼 추가해 주는 게 좋아요.

Suggested change
} = useClubIntroEdit();
} = useClubIntroEdit();
useTrackPageView(PAGE_VIEW.CLUB_INTRO_EDIT_PAGE);

(상단에 import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView';import { PAGE_VIEW } from '@/constants/eventName'; 추가가 필요해요. 또는 ClubInfo처럼 훅 내부로 옮기면 분기 위치와 무관하게 트래킹이 유지돼요.)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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}</>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

⚠️ 화면 크기 조절 및 기기 회전 시 작성 중인 데이터 유실 위험 (State Loss)

AdminTabAdapter를 통해 데스크탑 컴포넌트와 모바일 컴포넌트를 완전히 분리하고, 각 컴포넌트 내부에서 useClubInfoEdit / useClubIntroEdit 훅을 독립적으로 호출하도록 변경되었습니다.

이 구조는 컴포넌트 간의 의존성을 줄이고 props 전달을 단순화하는 장점이 있지만, 치명적인 UX 문제를 발생시킬 수 있습니다:

  1. 문제 상황: 사용자가 데스크탑 뷰에서 폼을 입력하던 중 브라우저 창 크기를 줄이거나, 모바일 기기에서 화면을 회전(가로/세로 전환)하여 브레이크포인트가 변경되면 AdminTabAdapter에 의해 기존 컴포넌트가 언마운트되고 새로운 컴포넌트가 마운트됩니다.
  2. 원인: React 컴포넌트가 언마운트되면 내부 상태(state)는 완전히 소멸합니다. 두 컴포넌트가 동일한 커스텀 훅을 호출하더라도 상태를 공유하지 않으므로, 저장하지 않은 모든 입력 데이터가 유실되고 서버에서 가져온 초기 데이터로 리셋됩니다.

💡 해결 방안 제안

  • 방안 A (권장 - 상태 유지와 UI 분리):
    기존처럼 하나의 공통 진입점 컴포넌트(예: ClubInfoEditTab)에서 useClubInfoEdit 훅을 한 번만 호출하여 상태를 유지하고, 내부에서 isMobile 여부에 따라 데스크탑 전용 UI 컴포넌트와 모바일 전용 UI 컴포넌트를 스위칭하며 props를 넘겨주는 방식을 유지하는 것이 안전합니다. 12개의 props가 너무 많다면, 폼 상태 객체를 하나로 묶어서 전달하거나 필요한 부분만 그룹화하여 전달하는 방식으로 props 구조를 개선할 수 있습니다.

  • 방안 B (Context API 활용):
    꼭 컴포넌트를 완전히 독립시키고 싶다면, AdminTabAdapter 상위에 ClubInfoEditProvider와 같은 Context를 두어 폼 상태를 공통 부모 레벨에서 관리하고, 데스크탑/모바일 컴포넌트는 각각 이 Context를 구독하도록 구현해야 합니다.

Comment on lines +1 to +11
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}</>;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

📊 페이지 뷰 중복 트래킹 및 누락 문제 해결

현재 ClubIntroEditTabMobile에서는 페이지 뷰 트래킹(useTrackPageView)이 누락되어 있고, ClubInfoEditTab에서는 useClubInfoEdit 훅 내부에서 트래킹을 처리하고 있어 화면 크기 조절(resize) 시 컴포넌트가 마운트/언마운트되면서 중복 트래킹이 발생할 수 있습니다.

이를 해결하기 위해 AdminTabAdapter에서 pageViewName을 prop으로 받아 공통으로 페이지 뷰를 트래킹하도록 개선하는 것을 제안합니다. AdminTabAdapter는 resize 시에도 언마운트되지 않고 유지되므로 중복 트래킹을 방지하고 모바일/데스크탑 모두 일관되게 트래킹할 수 있습니다.

참고: 이 변경을 적용한 후, ClubIntroEditTab.tsxuseClubInfoEdit.ts 내부에 남아있는 기존 useTrackPageView 호출과 관련 import는 제거해 주세요.

Suggested change
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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

AdminTabAdapterpageViewName을 전달하기 위해 PAGE_VIEW 상수를 import합니다.

Suggested change
import AdminTabAdapter from '@/pages/AdminPage/AdminTabAdapter';
import AdminTabAdapter from '@/pages/AdminPage/AdminTabAdapter';
import { PAGE_VIEW } from '@/constants/eventName';

Comment on lines +33 to +50
<Route
path='club-info'
element={
<AdminTabAdapter
desktop={<ClubInfoEditTab />}
mobile={<ClubInfoEditTabMobile />}
/>
}
/>
<Route
path='club-intro'
element={
<AdminTabAdapter
desktop={<ClubIntroEditTab />}
mobile={<ClubIntroEditTabMobile />}
/>
}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

AdminTabAdapter에 각 탭에 맞는 pageViewName을 전달하여 페이지 뷰 트래킹이 일관되게 동작하도록 설정합니다.

Suggested change
<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}
/>
}
/>

@coderabbitai coderabbitai Bot left a comment

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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between d5566ba and c1ad957.

📒 Files selected for processing (6)
  • frontend/src/pages/AdminPage/AdminRoutes.tsx
  • frontend/src/pages/AdminPage/AdminTabAdapter.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
💤 Files with no reviewable changes (2)
  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
  • frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx

Comment on lines +8 to +11
const AdminTabAdapter = ({ desktop, mobile }: AdminTabAdapterProps) => {
const { isMobile, isTablet } = useDevice();
return <>{isMobile || isTablet ? mobile : desktop}</>;
};

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.

🎯 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/apis

Repository: 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 -n

Repository: 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
done

Repository: Moadong/moadong

Length of output: 28038


디바이스 전환 시 편집 초안이 초기화됩니다. useDevice가 resize에 반응하고, AdminTabAdapterdesktop/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.

Comment on lines +21 to +41
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();

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.

🎯 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.

Suggested change
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 seongwon030 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

width를 줄였다 늘였다 할 때 아예 다른 컴포넌트가 렌더링된다면 상태유지가 되어야 한다는 전제 하여야 할 것 같은데 혹시 이것도 같이 고려하셨을까요?

@suhyun113

Copy link
Copy Markdown
Collaborator Author

width를 줄였다 늘였다 할 때 아예 다른 컴포넌트가 렌더링된다면 상태유지가 되어야 한다는 전제 하여야 할 것 같은데 혹시 이것도 같이 고려하셨을까요?

현재 어드민 페이지의 데스크탑과 모바일 컴포넌트의 구조를 생각하다보니 이 문제는 고려하지 못했네요 말씀해주신대로 resize하면 한쪽이 언마운트되어서 서로 가지고 있는 state가 소멸됩니다

당연히 이 부분이 중요하지만, 제 생각에 현재 구조가 데스크탑 컴포넌트에 모바일 컴포넌트가 의존하고 있어서 불필요하게 마운트되고 있어 변경된 현재 구조가 더 좋다고 생각했습니다

두가지 모두 만족시키는 방법을 찾아봤는데 tanstack, zustand, context 없이 단순하게 state를 올려서 해결도 가능한거 같아서 먼저 물어봅니다

현재 상황에서 가장 간단한건 hook을 AdminTabAdapter보다 한 단계 위에 두어 한 번만 파일을 호출하도록 Wrapper를 추가하는 방법이 있을 것 같습니다 데스크탑, 모바일 각 파일 내부에서는 훅 호출을 제거하고 props로 받도록 수정하면 될 것 같습니다. 그런데 이 경우 데스크탑 모바일이 각각 총 3개의 파일에 의존해야해서 복잡할 것 같습니다.

가장 간단하긴 한데 이 방법으로 진행할까요?
의견 주시면 반영해서 작업하겠습니다. 위 방법보다 다른게 나을 것 같다면 그걸로 진행하겠습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend 🔨 Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants