[FEAT] 구글 캘린더 연동 구현#227
Conversation
- 캘린더 연동/해제/인증 관련 엔드포인트 및 모델을 추가했습니다 - 기존 엔드포인트 파일들을 최신 스펙으로 재생성했습니다
- 캘린더 OAuth 콜백 페이지를 추가했습니다 - 콜백에서 code/state로 캘린더 연동 API를 호출하고 프로필 쿼리를 갱신했습니다 - 온보딩 캘린더 연동 버튼에 authorize API 호출 및 리다이렉트 로직을 연결했습니다 - 설정 페이지 캘린더 연동 버튼에 authorize API 호출 및 리다이렉트 로직을 연결했습니다 - localStorage의 calendarConnectOrigin 값으로 OAuth 완료 후 이동 경로를 구분했습니다
…to feat/web/187-google-calendar-api-integration
…e-calendar-api-integration
…e-calendar-api-integration
- pnpm gen:api 재생성 반영 - CalendarEventsResponse 로컬 zod 스키마 및 타입 추가 (schemas/calendar) - useCalendarEventsQuery 훅 추가 (DAY/WEEK/TWO_WEEK 필터 지원) - HomeTodoContainer: 홈 뷰 필터에 따라 WEEK/TWO_WEEK로 캘린더 이벤트 조회 후 일별 렌더링 - TodayTodoListContainer: DAY 필터로 오늘 캘린더 이벤트 조회 후 렌더링 - CalendarEventItem 컴포넌트 추가 - 구글 캘린더 연동 해제 확인 모달(SettingsCalendarDisconnectModalContainer) 적용 - 캘린더 연동/해제 실패 토스트 처리 추가 - focus 엔드포인트 응답 구조 변경 반영 (BaseResponse 래퍼 제거) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- HomeCalendarEventCard, TodayCalendarEventCard 컴포넌트를 추가했습니다 - CalendarEventItem 컴포넌트를 삭제했습니다 - 캘린더 이벤트 체크박스 상태를 컨테이너로 리프팅했습니다 - completedCount와 totalCount 계산에 캘린더 이벤트를 포함했습니다 - 캘린더 연결 해제 시 이벤트 쿼리 캐시를 제거했습니다
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 20 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
WalkthroughGoogle Calendar OAuth 연결·해제와 콜백 처리를 추가하고, 연결된 캘린더 이벤트를 홈·오늘 화면에 표시합니다. 이벤트 조회 스키마와 React Query 훅, 체크 상태 및 개수 반영, 설정 오류 토스트와 다국어 문구를 확장했습니다. ChangesGoogle Calendar 통합
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant SettingsProfileContainer
participant useSettingsProfile
participant authorize
participant CalendarCallbackContainer
participant connectCalendar
User->>SettingsProfileContainer: 캘린더 연결 클릭
SettingsProfileContainer->>useSettingsProfile: 연결 처리 요청
useSettingsProfile->>authorize: authorizationUrl 요청
authorize-->>useSettingsProfile: authorizationUrl 반환
useSettingsProfile-->>User: OAuth 페이지로 리다이렉트
User->>CalendarCallbackContainer: code/state 콜백
CalendarCallbackContainer->>connectCalendar: 캘린더 연결 요청
connectCalendar-->>CalendarCallbackContainer: 연결 결과
CalendarCallbackContainer-->>User: 설정 또는 홈으로 리다이렉트
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 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 |
Timo Performance ReportBundle Size — timo-web
Lighthouse — timo-web
Image Optimization — timo-web
측정 커밋: |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx:
- Around line 168-175: Replace index-based calendar event keys and checked-state
tracking with each event’s unique id. In
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx
lines 168-175, update the rendered key and checked lookup to use event.id,
change the related state from Set<number> to Set<string>, and update
toggleCalendarEvent parameters and callers accordingly. Apply the same
event.id-based rendering and state handling in
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx
lines 112-119.
In
`@apps/timo-web/app/`[locale]/(main)/settings/_containers/account/SettingsCalendarDisconnectModalContainer.tsx:
- Around line 19-25: Move SettingsCalendarDisconnectModalContainer to
_components/account/SettingsCalendarDisconnectModal.tsx and rename the exported
component to SettingsCalendarDisconnectModal, removing the Container suffix.
Update all imports and usages to reference the new path and symbol while
preserving its props-only rendering behavior.
In
`@apps/timo-web/app/`[locale]/(main)/settings/_containers/account/SettingsProfileContainer.tsx:
- Around line 25-26: Rename the state variable and setter from
isCalendarErrorToastOpen/setIsCalendarErrorToastOpen to
isCalendarDisconnectErrorToastOpen/setIsCalendarDisconnectErrorToastOpen, and
update every corresponding JSX prop and usage in SettingsProfileContainer.
In
`@apps/timo-web/app/`[locale]/onboarding/_containers/CalendarConnectStepContainer.tsx:
- Around line 22-23: Update isCalendarConnected in CalendarConnectStepContainer
to use a plain constant initialized to false instead of useState, and remove the
now-unused useState import while preserving all existing references.
In `@apps/timo-web/components/calendar/TodayCalendarEventCard.tsx`:
- Around line 19-48: Replace the isHovered useState and derived isDimmed logic
in TodayCalendarEventCard with Tailwind parent-hover styling: add the group
class to the card container and use group-hover variants for the checked-state
background and text colors. Preserve the existing checked behavior when the card
is not hovered and remove the mouse event handlers and unused state.
In `@apps/timo-web/schemas/calendar/calendar-events-schema.ts`:
- Around line 5-23: calendar-events-schema.ts의 타입 네이밍과 선언 방식을 정리하세요. 유니언 타입인
CalendarFilter를 CalendarFilterTypes로 변경하고,
CalendarEvent·CalendarDay·CalendarEventsData는 interface로 전환하세요. 해당 타입을 import하거나
참조하는 모든 위치도 새 이름과 interface 선언에 맞게 수정하세요.
🪄 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: 20f6cc6e-16f6-4e34-8dea-fa2b6a735909
⛔ Files ignored due to path filters (25)
apps/timo-web/api/generated/endpoints/auth/auth.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/auth/auth.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/calendar/calendar.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/calendar/calendar.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/focus/focus.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/focus/focus.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/todo/todo.tsis excluded by!**/generated/**apps/timo-web/api/generated/endpoints/todo/todo.zod.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseCalendarAuthorizeResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseCalendarConnectResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseCalendarDisconnectResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/baseResponseCalendarEventsResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/calendarAuthorizeResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/calendarConnectRequest.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/calendarConnectResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/calendarDayResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/calendarDisconnectResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/calendarEventResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/calendarEventsResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/errorDto.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/focusTodoDetailResponse.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/getCalendarEventsParams.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/index.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoCreateRequestIcon.tsis excluded by!**/generated/**apps/timo-web/api/generated/models/todoUpdateRequestIcon.tsis excluded by!**/generated/**
📒 Files selected for processing (17)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsxapps/timo-web/app/[locale]/(main)/focus/_queries/use-focus-todo-query.tsapps/timo-web/app/[locale]/(main)/settings/_containers/account/SettingsCalendarDisconnectModalContainer.tsxapps/timo-web/app/[locale]/(main)/settings/_containers/account/SettingsProfileContainer.tsxapps/timo-web/app/[locale]/(main)/settings/_hooks/account/use-settings-profile-labels.tsapps/timo-web/app/[locale]/(main)/settings/_hooks/account/use-settings-profile.tsapps/timo-web/app/[locale]/(main)/settings/_types/account/profile-type.tsapps/timo-web/app/[locale]/oauth/calendar/callback/_containers/CalendarCallbackContainer.tsxapps/timo-web/app/[locale]/oauth/calendar/callback/page.tsxapps/timo-web/app/[locale]/onboarding/_containers/CalendarConnectStepContainer.tsxapps/timo-web/components/calendar/HomeCalendarEventCard.tsxapps/timo-web/components/calendar/TodayCalendarEventCard.tsxapps/timo-web/messages/en.jsonapps/timo-web/messages/ko.jsonapps/timo-web/queries/calendar/use-calendar-events-query.tsapps/timo-web/schemas/calendar/calendar-events-schema.ts
| const [isHovered, setIsHovered] = useState(false); | ||
| const isDimmed = checked && !isHovered; | ||
|
|
||
| return ( | ||
| <div | ||
| className={cn( | ||
| "border-timo-gray-500 flex min-w-80 rounded-[4px] border px-5 py-4", | ||
| isDimmed ? "bg-timo-gray-200" : "bg-white", | ||
| )} | ||
| onMouseEnter={() => setIsHovered(true)} | ||
| onMouseLeave={() => setIsHovered(false)} | ||
| > | ||
| <div className="flex min-w-0 flex-1 items-center gap-2"> | ||
| <div className="inline-flex items-center"> | ||
| <Checkbox checked={checked} onChange={onToggle} /> | ||
| </div> | ||
| <div className="shrink-0 p-1"> | ||
| <Image | ||
| src="/images/google-calendar.png" | ||
| alt="Google Calendar" | ||
| width={14} | ||
| height={14} | ||
| /> | ||
| </div> | ||
| <span | ||
| className={cn( | ||
| "typo-headline-b-14 min-w-0 truncate", | ||
| isDimmed ? "text-timo-gray-700" : "text-timo-gray-900", | ||
| )} | ||
| > |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
React 상태 대신 CSS(Tailwind) Hover를 활용해 성능을 개선해 보세요.
단순한 UI Hover 상태를 위해 useState를 사용하면 불필요한 렌더링이 발생합니다. Tailwind의 group과 group-hover 속성을 사용하면 코드를 더 간결하게 만들고 성능도 높일 수 있습니다. 멋지게 최적화해 볼까요? 🚀 (Tailwind 문서 참고: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state)
✨ 제안하는 수정안 (CSS Hover 적용)
- const [isHovered, setIsHovered] = useState(false);
- const isDimmed = checked && !isHovered;
-
return (
<div
className={cn(
- "border-timo-gray-500 flex min-w-80 rounded-[4px] border px-5 py-4",
- isDimmed ? "bg-timo-gray-200" : "bg-white",
+ "border-timo-gray-500 group flex min-w-80 rounded-[4px] border px-5 py-4 transition-colors",
+ checked ? "bg-timo-gray-200 hover:bg-white" : "bg-white",
)}
- onMouseEnter={() => setIsHovered(true)}
- onMouseLeave={() => setIsHovered(false)}
>
<div className="flex min-w-0 flex-1 items-center gap-2">
<div className="inline-flex items-center">
<Checkbox checked={checked} onChange={onToggle} />
</div>
<div className="shrink-0 p-1">
<Image
src="/images/google-calendar.png"
alt="Google Calendar"
width={14}
height={14}
/>
</div>
<span
className={cn(
"typo-headline-b-14 min-w-0 truncate",
- isDimmed ? "text-timo-gray-700" : "text-timo-gray-900",
+ checked ? "text-timo-gray-700 group-hover:text-timo-gray-900" : "text-timo-gray-900",
)}
>📝 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.
| const [isHovered, setIsHovered] = useState(false); | |
| const isDimmed = checked && !isHovered; | |
| return ( | |
| <div | |
| className={cn( | |
| "border-timo-gray-500 flex min-w-80 rounded-[4px] border px-5 py-4", | |
| isDimmed ? "bg-timo-gray-200" : "bg-white", | |
| )} | |
| onMouseEnter={() => setIsHovered(true)} | |
| onMouseLeave={() => setIsHovered(false)} | |
| > | |
| <div className="flex min-w-0 flex-1 items-center gap-2"> | |
| <div className="inline-flex items-center"> | |
| <Checkbox checked={checked} onChange={onToggle} /> | |
| </div> | |
| <div className="shrink-0 p-1"> | |
| <Image | |
| src="/images/google-calendar.png" | |
| alt="Google Calendar" | |
| width={14} | |
| height={14} | |
| /> | |
| </div> | |
| <span | |
| className={cn( | |
| "typo-headline-b-14 min-w-0 truncate", | |
| isDimmed ? "text-timo-gray-700" : "text-timo-gray-900", | |
| )} | |
| > | |
| return ( | |
| <div | |
| className={cn( | |
| "border-timo-gray-500 group flex min-w-80 rounded-[4px] border px-5 py-4 transition-colors", | |
| checked ? "bg-timo-gray-200 hover:bg-white" : "bg-white", | |
| )} | |
| > | |
| <div className="flex min-w-0 flex-1 items-center gap-2"> | |
| <div className="inline-flex items-center"> | |
| <Checkbox checked={checked} onChange={onToggle} /> | |
| </div> | |
| <div className="shrink-0 p-1"> | |
| <Image | |
| src="/images/google-calendar.png" | |
| alt="Google Calendar" | |
| width={14} | |
| height={14} | |
| /> | |
| </div> | |
| <span | |
| className={cn( | |
| "typo-headline-b-14 min-w-0 truncate", | |
| checked ? "text-timo-gray-700 group-hover:text-timo-gray-900" : "text-timo-gray-900", | |
| )} | |
| > |
🤖 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/components/calendar/TodayCalendarEventCard.tsx` around lines 19
- 48, Replace the isHovered useState and derived isDimmed logic in
TodayCalendarEventCard with Tailwind parent-hover styling: add the group class
to the card container and use group-hover variants for the checked-state
background and text colors. Preserve the existing checked behavior when the card
is not hovered and remove the mouse event handlers and unused state.
- 캘린더 이벤트 key와 체크 상태를 index 대신 title 기반으로 변경했습니다 - SettingsCalendarDisconnectModal을 _components로 이동하고 Container suffix를 제거했습니다 - isCalendarErrorToastOpen을 isCalendarDisconnectErrorToastOpen으로 이름을 변경했습니다 - CalendarConnectStepContainer의 불필요한 useState를 상수로 변경했습니다 - TodayCalendarEventCard의 hover 상태를 Tailwind CSS group-hover로 변경했습니다
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/timo-web/app/[locale]/onboarding/_containers/CalendarConnectStepContainer.tsx (1)
23-33: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
localStorage예외 발생 시나리오에 대비하세요.일부 브라우저의 시크릿 모드나 보안 설정에 의해
localStorage접근이 차단된 경우,setItem호출 시 예외(QuotaExceededError또는SecurityError)가 발생할 수 있습니다. 현재 로직에서는 이 예외가 발생하면 외부catch블록으로 넘어가게 되어, 가장 중요한 OAuth 인증 URL로의 리다이렉션(window.location.assign)이 아예 실행되지 않는 불상사가 발생할 수 있습니다.
localStorage할당 실패가 전체 인증 흐름을 차단하지 않도록, 해당 부분만 별도의try-catch로 감싸주세요.
이전 리뷰의 불필요한useState피드백을 변수로 깔끔하게 반영해주신 점, 아주 센스 넘칩니다! ✨관련 공식 문서: MDN - Web Storage API 사용하기
💡 제안하는 수정안
const handleGoogleConnect = async () => { try { const response = await authorize(); const url = response.data?.authorizationUrl; if (!url) return; - localStorage.setItem("calendarConnectOrigin", "onboarding"); + try { + localStorage.setItem("calendarConnectOrigin", "onboarding"); + } catch { + // 브라우저 설정으로 인한 localStorage 접근 실패 무시 + } window.location.assign(url); } catch { // authorize 실패 시 아무 동작 없음 — 사용자가 재시도 가능 } };🤖 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]/onboarding/_containers/CalendarConnectStepContainer.tsx around lines 23 - 33, Update handleGoogleConnect so the localStorage.setItem call is wrapped in its own try-catch, allowing window.location.assign(url) to execute even when storage access fails. Keep the existing authorization error handling and onboarding origin value unchanged.apps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsx (1)
47-56: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value비동기 동작 시 모달의 로딩 UX를 고려해 보세요.
현재
onDisconnect호출 후 즉시onClose가 실행되어 모달이 바로 닫힙니다. 기능상 문제는 없고 에러 토스트도 상위 컴포넌트에 잘 연결되어 있지만, 만약 연결 해제 API 응답이 지연된다면 사용자 입장에서는 처리가 완료된 것인지 헷갈릴 수 있습니다.추후 상위 컨테이너 컴포넌트에서 React 19의
useTransition이나isPending상태를 모달로 전달하여,onDisconnect가 진행되는 동안 버튼에 로딩 스피너를 보여주고 완료 후에 모달을 닫도록 개선한다면 훨씬 부드러운 사용자 경험을 제공할 수 있을 거예요.
순수 UI 컴포넌트로 역할 분리를 가이드라인에 맞게 아주 깔끔하게 해주셨네요! 👍관련 공식 문서: React - useTransition
🤖 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/_components/account/SettingsCalendarDisconnectModal.tsx around lines 47 - 56, Update the SettingsCalendarDisconnectModal disconnect flow so onClose runs only after the asynchronous onDisconnect operation completes. Accept a pending/loading state from the parent, disable the confirmation action and show its loading indicator while the operation is in progress, then close the modal on successful completion while preserving existing error-toast handling.
🤖 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)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx:
- Line 63: Rename toggleCalendarEvent to handleToggleCalendarEvent in both
HomeTodoContainer.tsx (lines 63-63) and TodayTodoListContainer.tsx (lines
48-48), updating all references at each site to preserve event-handler behavior.
- Around line 136-138: Duplicate calendar titles cause incorrect completion
counts and React key warnings. In HomeTodoContainer.tsx lines 136-138 and
TodayTodoListContainer.tsx lines 93-94, calculate completed calendar items by
filtering the calendar-event arrays rather than using
checkedCalendarTitles.size. In HomeTodoContainer.tsx lines 168-175 and
TodayTodoListContainer.tsx lines 112-119, combine each event.title with its map
index for the React key.
---
Outside diff comments:
In
`@apps/timo-web/app/`[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsx:
- Around line 47-56: Update the SettingsCalendarDisconnectModal disconnect flow
so onClose runs only after the asynchronous onDisconnect operation completes.
Accept a pending/loading state from the parent, disable the confirmation action
and show its loading indicator while the operation is in progress, then close
the modal on successful completion while preserving existing error-toast
handling.
In
`@apps/timo-web/app/`[locale]/onboarding/_containers/CalendarConnectStepContainer.tsx:
- Around line 23-33: Update handleGoogleConnect so the localStorage.setItem call
is wrapped in its own try-catch, allowing window.location.assign(url) to execute
even when storage access fails. Keep the existing authorization error handling
and onboarding origin value 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: 4698aae2-4a7b-415c-86ff-827c71839833
📒 Files selected for processing (6)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsxapps/timo-web/app/[locale]/(main)/settings/_components/account/SettingsCalendarDisconnectModal.tsxapps/timo-web/app/[locale]/(main)/settings/_containers/account/SettingsProfileContainer.tsxapps/timo-web/app/[locale]/onboarding/_containers/CalendarConnectStepContainer.tsxapps/timo-web/components/calendar/TodayCalendarEventCard.tsx
- PillButton에 gray-dark variant를 추가했습니다 - Integrate 상태에서는 blue, Disconnect 상태에서는 gray-dark로 변경했습니다
- 이벤트 식별자를 title 단독에서 title-index composite key로 변경했습니다 - 동일 제목 일정이 여러 개일 때 각각 독립적으로 체크 상태를 관리합니다 - React 중복 key 경고를 해소했습니다
…to feat/web/187-google-calendar-api-integration
…to feat/web/187-google-calendar-api-integration
…to feat/web/187-google-calendar-api-integration # Conflicts: # apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx
…//github.com/Team-Timo/Timo-client into feat/web/187-google-calendar-api-integration
ISSUE 🔗
close #187
What is this PR? 🔍
구글 캘린더 OAuth 연동, 일정 조회 API 연동, 캘린더 이벤트 카드 UI를 구현했습니다.
배경
/api/v1/users/calendar/eventsAPI 연동을 통해 캘린더 일정을 홈·오늘 탭에 표시했습니다.OAuth 연동
authorize()API로 OAuth URL을 받아window.location.assign으로 리다이렉트하고, 콜백 페이지(/oauth/calendar/callback)에서 인가 코드를 서버로 전달합니다. 해제 시disconnectCalendar()mutate 후 프로필 쿼리를 invalidate하고 캘린더 이벤트 캐시를removeQueries로 제거합니다.캘린더 이벤트 조회
useCalendarEventsQuery를useQuery로 구현해enabled: calendarConnected조건을 적용했습니다.useSuspenseQuery는enabled옵션을 지원하지 않아useQuery를 선택했습니다. 홈은 뷰 모드에 따라TWO_WEEK/WEEK, 오늘은DAY필터를 사용합니다.staleTime: 0으로 매 마운트마다 최신 데이터를 가져옵니다.캘린더 이벤트 카드
HomeCalendarEventCard, 오늘용TodayCalendarEventCard컴포넌트를 추가했습니다.Map<string, Set<number>>/Set<number>)로 리프팅해completedCount와totalCount계산에 반영했습니다. 카드 내부에는 체크박스와 타이틀 사이에 구글 캘린더 아이콘(14×14, padding 4px)을 삽입했습니다. 체크 시 dimmed 처리(bg-timo-gray-200,text-timo-gray-700), hover 시 undim되는 동작은 기존 투두 카드와 동일합니다.To Reviewers
캘린더 이벤트 카드를 눌렀을 때 상세 모달이 뜨는 기능은 추후에 작업할 예정입니다.
캘린더 연결이 된 상태에서 투두 체크 완료/미완료 계산이 캘린더 이벤트를 포함하는지 확인 부탁드립니다.
Screenshot 📷
Test Checklist ✔