Skip to content

Comments

fix: Clarity 초기화 위치 수정 및 그룹 스터디 수정 폼 초기값 안정화#387

Merged
Hyeonjun0527 merged 2 commits intodevelopfrom
refactor/etc
Feb 8, 2026
Merged

fix: Clarity 초기화 위치 수정 및 그룹 스터디 수정 폼 초기값 안정화#387
Hyeonjun0527 merged 2 commits intodevelopfrom
refactor/etc

Conversation

@Hyeonjun0527
Copy link
Member

@Hyeonjun0527 Hyeonjun0527 commented Feb 8, 2026

🌱 연관된 이슈

☘️ 작업 내용

  • 서버 레이아웃의 Clarity.init()를 클라이언트 전용 컴포넌트로 이동
  • service/landing 레이아웃에 ClarityInit 적용
  • edit 모드에서 데이터 로딩 전 GroupStudyForm 마운트 방지
  • phone verification store의 persist partialize/onRehydrateStorage 타입 명시
  • 전화번호 인증 동기화 시 memberId 저장/반영 로직 보강

🍀 참고사항

스크린샷 (선택)

Summary by CodeRabbit

  • 새로운 기능

    • 그룹 스터디 편집 모드에서 로딩 중 별도 UI 표시 추가
  • 버그 수정

    • 편집 중 불필요한 폼 렌더링 방지로 안정성 향상
    • 멤버 변경 시 휴대폰 인증 캐시 초기화 동작 개선
  • 리팩토링

    • 분석(Clarity) 초기화 흐름을 컴포넌트화하여 렌더 트리에서 초기화하도록 정리
    • 휴대폰 인증 저장/동기화 로직 및 훅 내부 흐름 개선
    • 토스트 종료 타이밍 관리를 상수화하여 일관성 확보

@Hyeonjun0527 Hyeonjun0527 self-assigned this Feb 8, 2026
@vercel
Copy link

vercel bot commented Feb 8, 2026

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

Project Deployment Actions Updated (UTC)
study-platform-client-dev Ready Ready Preview, Comment Feb 8, 2026 4:47pm

@coderabbitai
Copy link

coderabbitai bot commented Feb 8, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

레이아웃에서 직접 Microsoft Clarity 초기화를 제거하고 재사용 가능한 ClarityInit 클라이언트 컴포넌트로 대체했습니다. 전화번호 인증 스토어/훅의 내부 persisted 상태(shape), memberId 및 verifiedAt 처리와 rehydrate 흐름을 정리·조정했고, 그룹 스터디 폼 모달의 편집/로딩 렌더링 분기를 개선했습니다. Toast 컴포넌트는 exit 타이밍 상수를 정리했습니다.

Changes

Cohort / File(s) Summary
레이아웃: Clarity 초기화 대체
src/app/(landing)/layout.tsx, src/app/(service)/layout.tsx
직접 @microsoft/clarity 임포트 및 런타임 초기화 제거. 대신 ClarityInit 컴포넌트를 import하여 렌더 트리 내에서 초기화하도록 변경(기존 MainProvider 및 initialAccessToken 흐름은 유지).
ClarityInit 컴포넌트 추가
src/components/analytics/clarity-init.tsx
클라이언트 전용 컴포넌트 추가: 전달된 projectId가 있으면 한 번만 Clarity.init(projectId) 호출하고 전역 플래그(window.__clarityInitialized)로 재초기화 방지. 빈 Fragment 반환.
전화번호 인증: 스토어 타입 및 동기화 변경
src/features/phone-verification/model/store.ts, src/features/phone-verification/model/use-phone-auth-mutation.ts, src/features/phone-verification/model/use-phone-verification-status.ts
Persisted 상태 타입(PersistedPhoneVerificationState) 명시, setVerified가 기존 memberId 병합 로직으로 동작하도록 변경, rehydrate 시 setHasHydrated(true) 처리 추가. 훅 내부 호출을 usePhoneVerificationStore.getState() 통해 수행하도록 내부 호출 방식 및 제어 흐름 조정(공개 API 서명은 변경 없음).
그룹 스터디 폼 모달: 편집 로딩 처리 개선
src/features/study/group/ui/group-study-form-modal.tsx
로딩 시 조기 종료 제거, optional chaining으로 안전하게 접근, 편집 모드에 대해 editDefaultValues 계산 및 로딩 중에는 한국어 로딩 메시지 표시하는 분기 추가.
UI: Toast 타이밍 정리
src/components/ui/toast.tsx
로컬 exitDuration 제거 및 공용 EXIT_DURATION 상수 사용. effect 의존성에 isRendered 추가하여 cleanup 경로 반영.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
당근 한 움큼 들고 말하네,
Clarity는 컴포넌트 속에 숨었네.
번호는 시간과 아이디로 춤추고,
모달은 기다림에 친절해졌지요.
깡총깡총, 축하의 토끼 춤! 🥕🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 주요 변경사항인 Clarity 초기화 위치 수정과 그룹 스터디 폼 초기값 안정화를 명확하게 요약하고 있으며, 변경사항과 직접적으로 관련이 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/etc

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

- 서버 레이아웃의 Clarity.init()를 클라이언트 전용 컴포넌트로 이동
- service/landing 레이아웃에 ClarityInit 적용
- edit 모드에서 데이터 로딩 전 GroupStudyForm 마운트 방지
- phone verification store의 persist partialize/onRehydrateStorage 타입 명시
- 전화번호 인증 동기화 시 memberId 저장/반영 로직 보강
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: 5

Caution

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

⚠️ Outside diff range comments (2)
src/features/phone-verification/model/use-phone-verification-status.ts (1)

147-196: ⚠️ Potential issue | 🔴 Critical

병합 충돌 마커가 해결되지 않았습니다.

useEffect 내 서버→스토어 동기화 로직과 의존성 배열에 충돌 마커가 남아 있어 CI가 실패합니다.

"Updated upstream" 브랜치가 resolvedServerIsVerified를 사용하고 usePhoneVerificationStore.getState()로 현재 상태를 가져오는 더 완성된 구현으로 보입니다. 반면 "Stashed changes"는 존재하지 않는 zustandIsVerified, zustandPhoneNumber 변수를 참조하고 있어 해당 부분을 사용하면 런타임 에러가 발생합니다.

🐛 충돌 해결 제안 (Updated upstream 기준)
  // 서버 → 스토어 동기화
  useEffect(() => {
    if (isProfileLoading || !userProfile || !memberId) return;

-<<<<<<< Updated upstream
    const current = usePhoneVerificationStore.getState();

    if (resolvedServerIsVerified) {
      // 서버가 인증됨 → 스토어 갱신 (memberId 포함)
      if (
        !current.isVerified ||
        current.memberId !== memberId ||
        current.phoneNumber !== serverPhoneNumber
      ) {
        store.setVerified(serverPhoneNumber ?? '', memberId);
-||||||| Stash base
-    if (serverPhoneNumber) {
-      // 서버에 전화번호가 있으면 Zustand도 동기화
-      if (!zustandIsVerified || zustandPhoneNumber !== serverPhoneNumber) {
-        setVerified(serverPhoneNumber);
-=======
-    if (serverPhoneNumber) {
-      // 서버에 전화번호가 있으면 Zustand도 동기화
-      if (!zustandIsVerified || zustandPhoneNumber !== serverPhoneNumber) {
-        setVerified(serverPhoneNumber, memberId ?? undefined);
->>>>>>> Stashed changes
      }
    } else {
      // 서버가 미인증 → 같은 유저 캐시 제거
      if (current.isVerified && current.memberId === memberId) {
        store.reset();
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    isProfileLoading,
    userProfile,
    memberId,
    serverIsVerified,
-<<<<<<< Updated upstream
    serverPhoneNumber,
    resolvedServerIsVerified,
-||||||| Stash base
-    zustandIsVerified,
-    zustandPhoneNumber,
-    setVerified,
-    reset,
-=======
-    zustandIsVerified,
-    zustandPhoneNumber,
-    setVerified,
-    reset,
-    memberId,
->>>>>>> Stashed changes
  ]);
src/features/phone-verification/model/store.ts (1)

7-114: ⚠️ Potential issue | 🔴 Critical

파일 전체에 병합 충돌 마커가 해결되지 않았습니다.

이 파일에 여러 개의 병합 충돌 블록이 있어 CI 파이프라인이 실패합니다:

  • Lines 7-21: PhoneVerificationState 인터페이스 정의
  • Lines 35-69: 초기 상태 및 setVerified 구현
  • Lines 74-85: reset 구현
  • Lines 90-114: persist 설정

두 브랜치 모두 verifiedAt, memberId, hasHydrated를 추가하는 동일한 목표를 가지고 있습니다. 주요 차이점:

  1. verifiedAt 타입: string | null vs Date | null - 서버 동기화를 위해 string (ISO 형식)이 더 적합합니다.
  2. setVerified: spread 연산자 vs state 콜백 - state 콜백이 기존 memberId를 유지하므로 더 안전합니다.
🐛 충돌 해결 제안
 interface PhoneVerificationState {
   isVerified: boolean;
   phoneNumber: string | null;
-<<<<<<< Updated upstream
   verifiedAt: string | null;
   memberId: number | null;
   hasHydrated: boolean;
   setVerified: (phoneNumber: string, memberId?: number) => void;
-||||||| Stash base
-  verifiedAt: Date | null;
-  setVerified: (phoneNumber: string) => void;
-=======
-  verifiedAt: string | null;
-  memberId: number | null;
-  hasHydrated: boolean;
-  setVerified: (phoneNumber: string, memberId?: number) => void;
-  setHasHydrated: (hasHydrated: boolean) => void;
->>>>>>> Stashed changes
   reset: () => void;
   setHasHydrated: (hasHydrated: boolean) => void;
 }

 export const usePhoneVerificationStore = create<PhoneVerificationState>()(
   persist(
     (set) => ({
       isVerified: false,
-<<<<<<< Updated upstream
-      phoneNumber: null as string | null,
-      verifiedAt: null as string | null,
-      memberId: null as number | null,
-      hasHydrated: false,
-      setVerified: (phoneNumber, memberId) =>
-        set({
-||||||| Stash base
-      phoneNumber: '',
-      verifiedAt: new Date(),
-      setVerified: (phoneNumber) =>
-        set({
-=======
       phoneNumber: null,
       verifiedAt: null,
       memberId: null,
       hasHydrated: false,
       setVerified: (phoneNumber, memberId) =>
         set((state) => ({
->>>>>>> Stashed changes
           isVerified: true,
           phoneNumber,
-<<<<<<< Updated upstream
-          verifiedAt: new Date().toISOString(),
-          ...(memberId !== undefined ? { memberId } : {}),
-||||||| Stash base
-          verifiedAt: new Date(),
-=======
           verifiedAt: new Date().toISOString(),
           memberId: memberId ?? state.memberId,
         })),
-      setHasHydrated: (hasHydrated) =>
-        set({
-          hasHydrated,
->>>>>>> Stashed changes
-        }),
       reset: () =>
         set({
           isVerified: false,
-<<<<<<< Updated upstream
-          phoneNumber: null as string | null,
-          verifiedAt: null as string | null,
-          memberId: null as number | null,
-||||||| Stash base
-          phoneNumber: null,
-          verifiedAt: null,
-=======
           phoneNumber: null,
           verifiedAt: null,
           memberId: null,
->>>>>>> Stashed changes
         }),
       setHasHydrated: (hasHydrated) => set({ hasHydrated }),
     }),
     {
-<<<<<<< Updated upstream
-      name: 'phone-verification-storage',
-      partialize: (state) => ({
-||||||| Stash base
-      name: 'phone-verification-storage', // 로컬 스토리지에 저장
-=======
       name: 'phone-verification-storage',
       partialize: (state): PersistedPhoneVerificationState => ({
->>>>>>> Stashed changes
         isVerified: state.isVerified,
         phoneNumber: state.phoneNumber,
         verifiedAt: state.verifiedAt,
         memberId: state.memberId,
       }),
       onRehydrateStorage: () => (state) => {
         state?.setHasHydrated(true);
       },
     },
   ),
 );

추가 참고사항:

  • null as string | null 형태의 타입 단언은 불필요합니다. TypeScript가 null을 자동으로 추론합니다.
  • setVerified에서 state 콜백을 사용하는 "Stashed changes" 방식이 기존 memberId를 보존하므로 더 안전합니다.
🤖 Fix all issues with AI agents
In `@src/app/`(landing)/layout.tsx:
- Around line 64-76: Remove the leftover git conflict markers (<<<<<<<, =======,
>>>>>>>) in layout.tsx and resolve the duplicated Clarity init block so the file
parses; ensure only the server-safe code remains (keep the await
getServerCookie('accessToken') call inside the server component) and move any
browser-only Clarity.init(CLARITY_PROJECT_ID) call out of the server component
(or guard it so it only runs in a client component/effect), referencing
getServerCookie, CLARITY_PROJECT_ID, and Clarity.init to locate the conflicting
sections; after cleanup verify no direct window/Clarity access remains in the
server component and that Clarity initialization is performed from a client-only
place.

In `@src/app/`(service)/layout.tsx:
- Around line 36-48: Remove the leftover Git merge-conflict markers (<<<<<<,
=======, >>>>>>>) from layout.tsx and resolve the conflict by keeping the
intended branch: ensure the server-side retrieval const initialAccessToken =
await getServerCookie('accessToken'); remains, retain the client-only Clarity
initialization block (if (typeof window !== 'undefined' && CLARITY_PROJECT_ID) {
Clarity.init(CLARITY_PROJECT_ID); }), and keep the GlobalToast-related JSX/logic
if that was selected; after updating the body, remove any now-unused imports
(e.g., Clarity, GlobalToast, getServerCookie) or add missing ones so the file
compiles cleanly.

In `@src/features/phone-verification/model/use-phone-auth-mutation.ts`:
- Around line 42-62: Remove the leftover Git conflict markers (<<<<<<<, |||||||,
=======, >>>>>>>) and consolidate the duplicated blocks into a single
implementation: compute currentMemberId using memberId ??
Number(getCookie('memberId')) and call setVerified(variables.phoneNumber,
currentMemberId || undefined); ensure only one copy of that logic remains and no
conflict markers remain in the file.

In `@src/features/study/group/ui/group-study-form-modal.tsx`:
- Around line 270-275: The conditional rendering uses an undefined isLoading
variable; replace it with the correct loading state(s) — e.g., use the combined
flags isGroupStudyLoading || isVerificationLoading in the Modal.Body check
(where mode === 'edit' is evaluated) so the block shows while either
isGroupStudyLoading or isVerificationLoading is true; ensure the other
conditional that uses editDefaultValues remains unchanged.
- Around line 102-109: There are unresolved Git merge conflict markers around
the guard clause; remove the markers (`<<<<<<<`, `|||||||`, `=======`,
`>>>>>>>`) and leave a single, correct guard or no guard depending on intent: if
you need the early return keep the guard using the loaded state variable used
elsewhere (use isGroupStudyLoading, not isLoading) as the condition in the guard
clause in function/component containing the lines with
isGroupStudyLoading/isLoading; otherwise remove the entire guard line so
conditional rendering later (lines ~241-244) handles it. Ensure only one of
isGroupStudyLoading or no guard remains and run a quick build to verify parsing
errors are gone.
🧹 Nitpick comments (1)
src/features/study/group/ui/group-study-form-modal.tsx (1)

264-280: edit 모드에서 데이터 로딩 실패 시 fallback UI가 없습니다.

isGroupStudyLoading이 false이고 editDefaultValues도 null/undefined인 경우(데이터 로딩 실패 또는 groupStudyInfo가 없는 경우), 모달 본문이 비어 보이게 됩니다.

♻️ 에러 상태 처리 추가 제안
             {mode === 'edit' && isGroupStudyLoading && (
               <Modal.Body className="font-designer-16m text-text-subtle py-800 text-center">
                 스터디 정보를 불러오는 중입니다...
               </Modal.Body>
             )}
+            {mode === 'edit' && !isGroupStudyLoading && !editDefaultValues && (
+              <Modal.Body className="font-designer-16m text-text-subtle py-800 text-center">
+                스터디 정보를 불러오지 못했습니다.
+              </Modal.Body>
+            )}
             {mode === 'edit' && !isGroupStudyLoading && editDefaultValues && (

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

🤖 Fix all issues with AI agents
In `@src/features/phone-verification/model/store.ts`:
- Around line 24-26: The TypeScript error comes from implicit any for
phoneNumber, verifiedAt, memberId when using the persist middleware; fix it by
declaring an explicit state type and/or asserting the initial null values' types
for the persisted slice: define a PhoneVerificationState interface (or type)
with phoneNumber: string | null, verifiedAt: string | null (or Date | null as
appropriate), memberId: string | null and use that type as the generic for your
store/persist call, or alternatively cast the initial values like phoneNumber as
string | null, verifiedAt as string | null, memberId as string | null so
TypeScript can infer correct types for the persist middleware and eliminate the
implicit any errors.

In `@src/features/study/group/ui/group-study-form-modal.tsx`:
- Around line 262-271: When in edit mode the code only renders GroupStudyForm
when editDefaultValues exists, causing an empty modal if isGroupStudyLoading is
false and editDefaultValues is null; add a fallback branch for mode === 'edit'
&& !isGroupStudyLoading && !editDefaultValues that renders a Modal.Body with an
error/empty state message and a retry action (e.g., a Retry button that calls
your existing refetch function or a new handler like refetchGroupStudy) and/or a
close option so the user isn’t left with a blank modal; update the conditional
rendering around isGroupStudyLoading/editDefaultValues/GroupStudyForm and wire
the retry to the appropriate handler (or expose a new onRetry prop) instead of
leaving the modal empty.

Comment on lines 24 to 26
phoneNumber: null,
verifiedAt: null,
memberId: null,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

TypeScript 타입 추론 오류 수정 필요

정적 분석 도구에서 phoneNumber, verifiedAt, memberId 속성에 암시적 any 타입 오류가 발생합니다. persist 미들웨어와 함께 사용할 때 TypeScript가 제네릭 타입에서 null 값의 타입을 올바르게 추론하지 못하는 경우가 있습니다.

🛠️ 명시적 타입 단언 추가 제안
      isVerified: false,
-      phoneNumber: null,
-      verifiedAt: null,
-      memberId: null,
+      phoneNumber: null as string | null,
+      verifiedAt: null as string | null,
+      memberId: null as number | null,
      hasHydrated: false,
📝 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
phoneNumber: null,
verifiedAt: null,
memberId: null,
isVerified: false,
phoneNumber: null as string | null,
verifiedAt: null as string | null,
memberId: null as number | null,
hasHydrated: false,
🧰 Tools
🪛 GitHub Check: typecheck

[failure] 26-26:
Object literal's property 'memberId' implicitly has an 'any' type.


[failure] 25-25:
Object literal's property 'verifiedAt' implicitly has an 'any' type.


[failure] 24-24:
Object literal's property 'phoneNumber' implicitly has an 'any' type.

🤖 Prompt for AI Agents
In `@src/features/phone-verification/model/store.ts` around lines 24 - 26, The
TypeScript error comes from implicit any for phoneNumber, verifiedAt, memberId
when using the persist middleware; fix it by declaring an explicit state type
and/or asserting the initial null values' types for the persisted slice: define
a PhoneVerificationState interface (or type) with phoneNumber: string | null,
verifiedAt: string | null (or Date | null as appropriate), memberId: string |
null and use that type as the generic for your store/persist call, or
alternatively cast the initial values like phoneNumber as string | null,
verifiedAt as string | null, memberId as string | null so TypeScript can infer
correct types for the persist middleware and eliminate the implicit any errors.

Comment on lines +262 to +271
{mode === 'edit' && isGroupStudyLoading && (
<Modal.Body className="font-designer-16m text-text-subtle py-800 text-center">
스터디 정보를 불러오는 중입니다...
</Modal.Body>
)}
{mode === 'edit' && !isGroupStudyLoading && editDefaultValues && (
<GroupStudyForm
defaultValues={editDefaultValues}
onSubmit={handleSubmitForm}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

편집 모드에서 데이터 미존재 시 빈 화면 상태가 발생합니다.
isGroupStudyLoading이 false인데 editDefaultValues가 null이면 모달 바디가 비어 보입니다(예: 조회 실패/데이터 없음). 에러/빈 상태 메시지 또는 재시도 UI를 추가해 주세요.

🤖 Prompt for AI Agents
In `@src/features/study/group/ui/group-study-form-modal.tsx` around lines 262 -
271, When in edit mode the code only renders GroupStudyForm when
editDefaultValues exists, causing an empty modal if isGroupStudyLoading is false
and editDefaultValues is null; add a fallback branch for mode === 'edit' &&
!isGroupStudyLoading && !editDefaultValues that renders a Modal.Body with an
error/empty state message and a retry action (e.g., a Retry button that calls
your existing refetch function or a new handler like refetchGroupStudy) and/or a
close option so the user isn’t left with a blank modal; update the conditional
rendering around isGroupStudyLoading/editDefaultValues/GroupStudyForm and wire
the retry to the appropriate handler (or expose a new onRetry prop) instead of
leaving the modal empty.

- 서버 레이아웃의 Clarity.init()를 클라이언트 전용 컴포넌트로 이동
- service/landing 레이아웃에 ClarityInit 적용
- edit 모드에서 데이터 로딩 전 GroupStudyForm 마운트 방지
- phone verification store의 persist partialize/onRehydrateStorage 타입 명시
- 전화번호 인증 동기화 시 memberId 저장/반영 로직 보강

fix: 타입체크
@Hyeonjun0527 Hyeonjun0527 merged commit 520a4ff into develop Feb 8, 2026
8 of 9 checks passed
@Hyeonjun0527 Hyeonjun0527 deleted the refactor/etc branch February 8, 2026 16:48
@Hyeonjun0527 Hyeonjun0527 restored the refactor/etc branch February 13, 2026 07:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant