Skip to content

[FEAT] 설정 프로필 조회·언어 수정 API 연동 - #173

Merged
kimminna merged 2 commits into
developfrom
feat/web/172-settings-profile-api-integration
Jul 13, 2026
Merged

[FEAT] 설정 프로필 조회·언어 수정 API 연동#173
kimminna merged 2 commits into
developfrom
feat/web/172-settings-profile-api-integration

Conversation

@kimminna

@kimminna kimminna commented Jul 13, 2026

Copy link
Copy Markdown
Member

ISSUE 🔗

close #172



What is this PR? 🔍

설정 페이지의 프로필/언어 mock 데이터를 실제 백엔드 API로 교체했습니다. 내 프로필 조회와 서비스 언어 수정을 React Query로 연동하고, 연동 과정에서 발견된 OAuth 콜백의 캐시 시딩 버그를 함께 수정했습니다.

배경

  • 기존 구조: 설정 프로필 화면은 settingsProfileMock으로 이름·이메일·캘린더 연동 여부를 하드코딩해 렌더링했고, 언어 변경도 저장 시 setTimeout으로 흉내만 냈습니다.
  • 발생 문제: 백엔드에 이미 GET /api/v1/users, PATCH /api/v1/users 엔드포인트가 존재했지만 클라이언트가 붙어있지 않아, 새로고침해도 항상 같은 mock 값이 보였고 언어 변경도 서버에 반영되지 않았습니다.
  • 해결 방향: orval이 생성한 useGetMyProfile / useUpdateLanguage 훅을 로컬 zod 스키마로 한 번 더 검증한 뒤 _queries/use-settings-profile.ts_hooks/useSettingsProfile.ts에서 사용하도록 연결했습니다.

프로필 조회

  • 변경 요약: _queries/use-settings-profile.ts를 추가해 GET /api/v1/usersuseSuspenseQuery로 연동했습니다.
  • 이유: 프로필은 설정 페이지의 필수 데이터이므로, SettingsTabsContainerAsyncBoundary를 추가하고 useSuspenseQuery를 쓰는 쪽으로 맞췄습니다.
  • 구현 방식: queryKey/queryFn은 생성된 getGetMyProfileQueryKey + getMyProfile fetcher로 직접 조립했습니다 (생성된 getGetMyProfileQueryOptions를 그대로 스프레드하면 skipToken 타입 충돌이 발생합니다). select에서 BaseResponseUserProfileResponse.data를 언랩한 뒤, UI가 실제로 쓰는 name / email / calendarConnected 세 필드만 검증하는 로컬 zod 스키마(settingsProfileResponseSchema)로 .parse()합니다.
  • 경계 · 제약: 태그 목록은 별도의 "태그 목록 조회" API(tag.ts) 영역이라 이번 PR 범위에서 제외했고, 기본 태그 4개로만 초기화해두었습니다. 캘린더 연동/해제도 백엔드에 아직 대응 엔드포인트가 없어(/v3/api-docs 확인 결과 /api/v1/users, /api/v1/users/timezone, /api/v1/users/onboarding만 존재) 기존 로컬 스텁을 그대로 유지했습니다.

서비스 언어 수정

  • 변경 요약: 저장 버튼을 눌렀을 때 언어가 변경된 경우 PATCH /api/v1/users로 실제 반영하도록 useUpdateLanguage를 연결했습니다.
  • 이유: 기존에는 언어 변경이 URL 파라미터와 로케일 라우팅에만 반영되고 서버에는 저장되지 않았습니다.
  • 구현 방식: SettingsLanguage("ko"/"en")를 생성된 UpdateLanguageRequestLanguage("KO"/"EN") enum으로 매핑하는 테이블을 두고, isLanguageDirty일 때만 mutation을 호출한 뒤 기존과 동일하게 commitLanguage()로 로케일 라우트를 커밋합니다.

OAuth 콜백 버그 수정

  • 변경 요약: 로그인 성공 시 queryClient.setQueryData(getGetMyProfileQueryKey(), data.user)로 프로필 쿼리 캐시를 미리 채워두던 코드를 제거했습니다.
  • 이유: data.user는 토큰 발급 응답의 UserInfo(BaseResponse 래퍼 없음, calendarConnected 필드 없음)였고, 이 값이 프로필 쿼리 캐시에 그대로 들어가 있으면 설정 페이지가 실제 fetch 없이 이 캐시를 읽어 select의 zod 파싱이 항상 실패했습니다. 실제 로그인 후 이 문제가 재현되어 발견했습니다.
  • 구현 방식: 해당 캐시 시딩 라인과 더 이상 쓰이지 않는 useQueryClient / getGetMyProfileQueryKey import를 제거했습니다. 설정 페이지 진입 시 useSettingsProfileQuery가 정상적으로 네트워크 요청을 보내 실제 프로필을 받아옵니다.



To Reviewers

프로필 응답 로컬 zod 스키마를 name/email/calendarConnected 세 필드로만 좁혀뒀는데, UI가 늘어나면 스키마도 같이 넓혀야 합니다.



Screenshot 📷

image



Test Checklist ✔

  • pnpm check-types 통과
  • pnpm lint 통과
  • 로그인 후 설정 페이지 프로필 표시 재검증

kimminna added 2 commits July 13, 2026 19:37
- 내 프로필 조회(GET /api/v1/users) API를 useSuspenseQuery로 연동했습니다
- 서비스 언어 수정(PATCH /api/v1/users) API를 저장 흐름에 연동했습니다
- 응답 검증용 로컬 zod 스키마를 추가했습니다
- 더 이상 사용하지 않는 프로필 mock 파일을 제거했습니다
- 로그인 성공 시 프로필 쿼리 캐시에 BaseResponse 래퍼 없는 UserInfo 모양 데이터를 직접 심어두던 코드를 제거했습니다
- 해당 캐시가 설정 페이지의 실제 프로필 쿼리와 모양이 달라 파싱 에러를 유발하고 있었습니다
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 13, 2026 10:42am

Request Review

@github-actions
github-actions Bot requested a review from ehye1 July 13, 2026 10:41
@github-actions github-actions Bot added the ⏰ Timo-web Timo 웹 서비스 label Jul 13, 2026
@github-actions
github-actions Bot requested a review from yumin-kim2 July 13, 2026 10:41
@github-actions github-actions Bot added ✨ Feature 새로운 기능(기능성) 구현 ♦️ 민아 민아상 labels Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

설정 프로필 mock 데이터를 React Query 기반 API 조회와 Zod 검증으로 교체했습니다. 언어 변경은 실제 API로 저장하며, 프로필 렌더링에는 비동기 경계를 추가했습니다. OAuth 콜백의 프로필 캐시 시딩 로직은 제거했습니다.

Changes

설정 프로필 API 연동

Layer / File(s) Summary
프로필 계약과 조회 훅
apps/timo-web/app/[locale]/(main)/settings/_types/profile-type.ts, apps/timo-web/app/[locale]/(main)/settings/_queries/use-settings-profile.ts
프로필 응답을 name, email, calendarConnected Zod 스키마로 정의하고, useSuspenseQuery에서 조회·검증합니다.
프로필 상태와 저장 연동
apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfile.ts
API 프로필을 사용하고 캘린더 상태를 로컬 상태로 분리했으며, 언어가 변경된 경우에만 mutateAsync로 저장합니다.
프로필 렌더링 비동기 경계
apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsTabsContainer.tsx
기본 프로필 탭을 AsyncBoundary로 감싸고 정책·탈퇴 탭 분기는 유지합니다.

OAuth 프로필 캐시 처리

Layer / File(s) Summary
OAuth 콜백 성공 처리
apps/timo-web/app/[locale]/oauth/callback/_containers/OauthCallbackContainer.tsx
OAuth 성공 시 프로필 Query 캐시를 갱신하지 않고 액세스 토큰 설정 후 온보딩 또는 홈으로 이동합니다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SettingsProfileContainer
  participant useSettingsProfile
  participant useSettingsProfileQuery
  participant getMyProfile
  SettingsProfileContainer->>useSettingsProfile: 프로필 상태 요청
  useSettingsProfile->>useSettingsProfileQuery: suspense 프로필 조회
  useSettingsProfileQuery->>getMyProfile: GET 내 프로필
  getMyProfile-->>useSettingsProfileQuery: 응답 데이터
  useSettingsProfileQuery-->>useSettingsProfile: Zod 검증 프로필
  useSettingsProfile-->>SettingsProfileContainer: 프로필 및 폼 상태 반환
Loading

Possibly related PRs

Suggested reviewers: yumin-kim2, ehye1, jjangminii

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed 내 프로필 조회, 언어 수정, OAuth 캐시 수정 요구를 충족하고, 캘린더·태그 연동 보류도 이슈와 일치합니다.
Out of Scope Changes check ✅ Passed AsyncBoundary 추가와 mock 제거 등은 프로필 API 전환을 뒷받침하는 범위 내 변경으로 보입니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed 제목이 설정 프로필 조회와 언어 수정 API 연동이라는 변경의 핵심을 정확히 요약합니다.
Description check ✅ Passed 설명은 실제 백엔드 API 연동, OAuth 캐시 버그 수정까지 변경 범위와 목적을 잘 다룹니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web/172-settings-profile-api-integration

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.

@github-actions

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale]/home 194.86 kB 🔴 400.68 kB
/[locale]/today 154.64 kB 🔴 360.47 kB
/[locale]/focus 151.58 kB 🔴 357.41 kB
/[locale]/settings/account 0 B 🟡 205.83 kB
/[locale]/settings 165.27 kB 🔴 371.10 kB
/[locale]/statistics 150.11 kB 🔴 355.94 kB
/[locale]/[...rest] 0 B 🟡 205.83 kB
/[locale]/login 116.90 kB 🟡 322.73 kB
/[locale]/oauth/callback 116.89 kB 🟡 322.72 kB
/[locale]/onboarding 228.94 kB 🔴 434.77 kB
/[locale] 0 B 🟡 205.83 kB

공유 번들: 205.83 kB
🟢 < 200kB  |  🟡 < 350kB  |  🔴 ≥ 350kB (First Load JS · gzip)

Lighthouse — timo-web
URL Perf A11y LCP CLS TBT
/en/home 🔴 57 🟢 95 🔴 15.7s 🟢 0.000 🔴 669ms
/en/today 🔴 60 🟢 95 🔴 15.4s 🟢 0.000 🟡 585ms
/en/focus 🔴 57 🟢 95 🔴 15.2s 🟢 0.000 🔴 695ms
/en/statistics 🔴 67 🟢 95 🔴 15.3s 🟢 0.000 🟡 332ms

Perf ≥ 70 / A11y ≥ 85 목표
LCP 🟢 < 2.5s 🟡 < 4s 🔴 ≥ 4s  |  CLS 🟢 < 0.1 🟡 < 0.25 🔴 ≥ 0.25  |  TBT 🟢 < 200ms 🟡 < 600ms 🔴 ≥ 600ms

Image Optimization — timo-web
파일 크기 포맷 상태
images/google-calendar.png 36.20 kB PNG ⚠️ 🟢
images/google-logo.png 26.79 kB PNG ⚠️ 🟢

총 2개 · 63.00 kB  |  🟢 < 200KB  |  🟡 < 500KB  |  🔴 ≥ 500KB
⚠️ 2개 파일 WebP/AVIF 변환 권장

측정 커밋: 0a5188e

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 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
`@apps/timo-web/app/`[locale]/(main)/settings/_containers/SettingsTabsContainer.tsx:
- Around line 14-18: Update the AsyncBoundary usage in SettingsTabsContainer to
provide an errorFallback so zod errors thrown by useSettingsProfileQuery’s
select validation are handled by an ErrorBoundary instead of crashing the
settings page. Use the existing application error fallback component or pattern
defined for AsyncBoundary.

In `@apps/timo-web/app/`[locale]/(main)/settings/_hooks/useSettingsProfile.ts:
- Around line 104-114: Update handleSave in useSettingsProfile so that, after
updateLanguage succeeds, the React Query cache entry identified by
getGetMyProfileQueryKey is invalidated before completing the existing
reset(values) and commitLanguage() flow.

In `@apps/timo-web/app/`[locale]/(main)/settings/_types/profile-type.ts:
- Around line 11-15: Update the email field in settingsProfileResponseSchema to
use Zod 4’s z.email() format schema instead of z.string(), while leaving the
name and calendarConnected validations unchanged.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: de8eeef6-db33-4d00-be2e-0cbc8c3ced9f

📥 Commits

Reviewing files that changed from the base of the PR and between 8b39b01 and 7f566cf.

📒 Files selected for processing (6)
  • apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsTabsContainer.tsx
  • apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfile.ts
  • apps/timo-web/app/[locale]/(main)/settings/_mocks/profile-mock.ts
  • apps/timo-web/app/[locale]/(main)/settings/_queries/use-settings-profile.ts
  • apps/timo-web/app/[locale]/(main)/settings/_types/profile-type.ts
  • apps/timo-web/app/[locale]/oauth/callback/_containers/OauthCallbackContainer.tsx
💤 Files with no reviewable changes (1)
  • apps/timo-web/app/[locale]/(main)/settings/_mocks/profile-mock.ts

Comment on lines +14 to +18
return (
<AsyncBoundary>
<SettingsProfileContainer />
</AsyncBoundary>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

AsyncBoundaryerrorFallback가 누락되어 zod 검증 실패 시 페이지가 크래시됩니다.

useSettingsProfileQueryselect에서 settingsProfileResponseSchema.parse(data)를 호출합니다. API 응답이 스키마와 불일치하면 zod가 에러를 throw하는데, 현재 AsyncBoundaryerrorFallback을 전달하지 않아 ErrorBoundary가 래핑되지 않습니다. 결과적으로 처리되지 않은 에러가 발생해 설정 페이지 전체가 크래시됩니다.

🔒️ 제안하는 개선
  return (
-   <AsyncBoundary>
+   <AsyncBoundary
+     pendingFallback={<SettingsProfileSkeleton />}
+     errorFallback={<SettingsProfileError />}
+   >
      <SettingsProfileContainer />
    </AsyncBoundary>
  );

AsyncBoundary 컴포넌트는 errorFallback을 전달받아야 ErrorBoundary로 래핑합니다. 컴포넌트 정의를 참고하세요.

📝 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
return (
<AsyncBoundary>
<SettingsProfileContainer />
</AsyncBoundary>
);
return (
<AsyncBoundary
pendingFallback={<SettingsProfileSkeleton />}
errorFallback={<SettingsProfileError />}
>
<SettingsProfileContainer />
</AsyncBoundary>
);
🤖 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
`@apps/timo-web/app/`[locale]/(main)/settings/_containers/SettingsTabsContainer.tsx
around lines 14 - 18, Update the AsyncBoundary usage in SettingsTabsContainer to
provide an errorFallback so zod errors thrown by useSettingsProfileQuery’s
select validation are handled by an ErrorBoundary instead of crashing the
settings page. Use the existing application error fallback component or pattern
defined for AsyncBoundary.

Comment on lines +104 to 114
const isLanguageDirty = language !== locale;

const handleSave = handleSubmit(async (values) => {
try {
// TODO: API 연동
await new Promise((resolve) => setTimeout(resolve, 1000));
if (isLanguageDirty) {
await updateLanguage({
data: { language: LANGUAGE_REQUEST_MAP[language] },
});
}
reset(values);
commitLanguage();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

언어 업데이트 후 프로필 쿼리 캐시 무효화가 필요합니다.

updateLanguage 성공 후 reset(values)commitLanguage()는 실행되지만, 프로필 쿼리(getGetMyProfileQueryKey) 캐시가 무효화되지 않습니다. 동일한 엔드포인트(/api/v1/users)를 사용하므로, mutation 성공 후 캐시를 무효화하지 않으면 재조회 시 stale data가 반환될 수 있습니다.

♻️ 제안하는 개선
+import { useQueryClient } from "`@tanstack/react-query`";
+import { getGetMyProfileQueryKey } from "`@/api/generated/endpoints/user/user`";
+
 // ... inside useSettingsProfile
+ const queryClient = useQueryClient();

  const handleSave = handleSubmit(async (values) => {
    try {
      if (isLanguageDirty) {
        await updateLanguage({
          data: { language: LANGUAGE_REQUEST_MAP[language] },
        });
+       await queryClient.invalidateQueries({
+         queryKey: getGetMyProfileQueryKey(),
+       });
      }
      reset(values);
      commitLanguage();
    } catch {

React Query의 invalidateQueries 문서를 참고하세요.

📝 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
const isLanguageDirty = language !== locale;
const handleSave = handleSubmit(async (values) => {
try {
// TODO: API 연동
await new Promise((resolve) => setTimeout(resolve, 1000));
if (isLanguageDirty) {
await updateLanguage({
data: { language: LANGUAGE_REQUEST_MAP[language] },
});
}
reset(values);
commitLanguage();
const queryClient = useQueryClient();
const isLanguageDirty = language !== locale;
const handleSave = handleSubmit(async (values) => {
try {
if (isLanguageDirty) {
await updateLanguage({
data: { language: LANGUAGE_REQUEST_MAP[language] },
});
await queryClient.invalidateQueries({
queryKey: getGetMyProfileQueryKey(),
});
}
reset(values);
commitLanguage();
🤖 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 `@apps/timo-web/app/`[locale]/(main)/settings/_hooks/useSettingsProfile.ts
around lines 104 - 114, Update handleSave in useSettingsProfile so that, after
updateLanguage succeeds, the React Query cache entry identified by
getGetMyProfileQueryKey is invalidated before completing the existing
reset(values) and commitLanguage() flow.

Comment on lines +11 to +15
export const settingsProfileResponseSchema = z.object({
name: z.string(),
email: z.string(),
calendarConnected: z.boolean(),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

이메일 필드 검증을 z.email()로 강화해 보세요.

Zod 4부터 z.email()이 독립적인 문자열 포맷 스키마로 제공됩니다. 현재 z.string()을 사용 중인데, 이메일 형식 검증을 추가하면 API 응답의 신뢰성을 더욱 높일 수 있습니다.

♻️ 제안하는 개선
 export const settingsProfileResponseSchema = z.object({
   name: z.string(),
-  email: z.string(),
+  email: z.email(),
   calendarConnected: z.boolean(),
 });

Zod 4 문서의 String Enhancements 섹션을 참고하세요.

📝 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
export const settingsProfileResponseSchema = z.object({
name: z.string(),
email: z.string(),
calendarConnected: z.boolean(),
});
export const settingsProfileResponseSchema = z.object({
name: z.string(),
email: z.email(),
calendarConnected: z.boolean(),
});
🤖 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 `@apps/timo-web/app/`[locale]/(main)/settings/_types/profile-type.ts around
lines 11 - 15, Update the email field in settingsProfileResponseSchema to use
Zod 4’s z.email() format schema instead of z.string(), while leaving the name
and calendarConnected validations unchanged.

@ehye1 ehye1 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.

굿굿 ! 설정 API 연동 확인햇습니닷🫰🏻

@kimminna
kimminna merged commit 13d8e39 into develop Jul 13, 2026
20 checks passed
@kimminna
kimminna deleted the feat/web/172-settings-profile-api-integration branch July 13, 2026 16:01
@kimminna kimminna mentioned this pull request Jul 14, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Jul 15, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능(기능성) 구현 ⏰ Timo-web Timo 웹 서비스 ♦️ 민아 민아상

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 설정 프로필 조회·언어 수정 API 연동

2 participants