Skip to content

[FEAT] 약관 페이지 및 설정 약관 탭 구현 / 약관 API 연결 - #167

Merged
jjangminii merged 5 commits into
developfrom
feat/web/163-terms-policy-pages
Jul 13, 2026
Merged

[FEAT] 약관 페이지 및 설정 약관 탭 구현 / 약관 API 연결#167
jjangminii merged 5 commits into
developfrom
feat/web/163-terms-policy-pages

Conversation

@jjangminii

@jjangminii jjangminii commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

ISSUE 🔗

close #163



What is this PR? 🔍

로그인 페이지의 "이용약관"/"개인정보 처리방침" 링크와 설정 > 약관 탭을 실제 약관 조회 API(GET /api/v1/terms)로 연동하고, 최신 화면설계서에 맞춰 설정 쪽 탭을 서비스 이용약관/개인정보 처리방침 두 개로 분리했습니다.

배경

  • 기존 구조: 로그인 페이지의 "이용약관"/"개인정보 처리방침" 링크가 href="#" placeholder였고, 설정 > 약관 탭도 제목만 있는 빈 스텁이었습니다.
  • 발생 문제: 실제로는 이미 약관 조회 API(GET /api/v1/terms, type=SERVICE|PRIVACY)가 준비돼 있었는데 연동이 안 돼 있었고, 작업 중간에 화면설계서가 갱신되어 설정의 "약관" 탭이 서비스 이용약관/개인정보 처리방침 두 개의 별도 탭으로 나뉘는 것으로 확인됐습니다.
  • 해결 방향: 공유 쿼리 훅(useTerms)과 로컬 zod 검증 스키마를 만들어 독립 /policy 페이지와 설정의 두 탭이 같은 데이터 소스를 쓰도록 하고, 레이아웃 컴포넌트(PolicyDocument)는 도메인 간 공유 위치로 옮겨 중복 없이 재사용했습니다.

독립 /policy 페이지

  • 변경 요약: 로그인 없이 접근 가능한 /policy 라우트를 추가하고, ?type=SERVICE/?type=PRIVACY 쿼리로 각각 다른 약관만 표시하도록 했습니다.
  • 이유: 로그인 페이지의 두 링크가 서로 다른 약관으로 이어져야 했습니다.
  • 구현 방식: page.tsx(서버 컴포넌트)가 searchParams.type을 읽어 유효하지 않으면 SERVICE로 기본값 처리하고, 로고만 있는 정적 헤더를 먼저 렌더링한 뒤 AsyncBoundary로 감싼 PolicyContainer(클라이언트, useTerms(type))가 본문을 스트리밍합니다. 로고 클릭 시 로그인 페이지로 돌아갑니다.
  • 경계 · 제약: 현재 백엔드가 약관 데이터를 아직 시딩하지 않아 빈 배열을 반환할 수 있어, 빈 상태 문구(Policy.empty)를 추가했습니다.

공유 데이터 레이어

  • 변경 요약: types/terms-type.ts(zod 스키마)와 queries/use-terms.ts(useSuspenseQuery 훅)를 앱 전역 공유 위치에 추가했습니다.
  • 이유: /policy 페이지와 설정 두 탭이 동일한 API 데이터를 쓰는데, 도메인 간 직접 import는 금지돼 있어 공유 로직을 도메인 밖으로 뽑아야 했습니다.
  • 구현 방식: getGetTermsQueryKey + getTerms(생성 코드)로 queryKey/queryFn을 직접 조립하고, select에서 termsListSchema.parse()로 응답을 검증해 백엔드 스펙이 조용히 바뀌어도 파싱 실패로 드러나게 했습니다. type을 생략하면 전체 약관을 조회합니다.

설정 약관 탭 분리

  • 변경 요약: useSettingsTab"privacy"를 추가하고, 설정 사이드바를 개인 정보 / 약관 / 개인 정보 처리 방침 / 회원 탈퇴 4개 탭으로 늘렸습니다. SettingsPolicyContainerSERVICE 약관만, 신규 SettingsPrivacyContainerPRIVACY 약관만 PolicyDocument로 표시합니다.
  • 이유: 작업 도중 화면설계서가 갱신되어, 원래 하나의 "약관" 탭에서 두 약관을 한꺼번에 나열하던 구조가 두 개의 독립된 탭으로 바뀌었습니다.
  • 구현 방식: PolicyDocument(제목+구분선+본문)를 app/[locale]/policy/_components에서 components/policy/로 옮겨 /policy 페이지와 설정 두 탭이 함께 씁니다. 영문 "약관" 탭 라벨은 Figma 영문 레퍼런스를 근거로 "Terms & Policies" → "Terms of Service"로 수정했습니다(순서는 한국어 디자인 기준을 따라 약관이 개인정보 처리방침보다 먼저 오도록 유지).



To Reviewers

  • 백엔드가 아직 약관 데이터를 시딩하지 않아 실제 화면에서는 빈 상태 문구만 보일 수 있습니다. 데이터가 채워지면 그대로 렌더링됩니다.
  • /settings, /focus처럼 로그인 가드가 걸린 라우트라 헤드리스 브라우저로 설정 탭 클릭까지는 직접 확인하지 못했고, /policy 독립 페이지와 로그인 링크 연결만 실제로 확인했습니다. 로그인 가능한 환경에서 설정 탭 전환도 한 번 확인해 주시면 좋겠습니다.
  • 영문 "약관" 라벨 변경과 순서 유지 결정은 대화 중 스크린샷 기준으로 정한 것이라, 실제 최신 디자인 의도와 맞는지 확인 부탁드립니다.



Screenshot 📷

화면 스크린샷
/policy 빈 상태 백엔드 미시딩으로 "약관 내용을 아직 준비 중입니다." 문구 노출 확인
image image



Test Checklist ✔

  • pnpm check-types 통과
  • pnpm lint 통과
  • Playwright로 /ko/policy?type=SERVICE, /ko/policy?type=PRIVACY 실제 API(https://api.timo.kr/api/v1/terms) 호출 및 렌더링 확인
  • Playwright로 로그인 페이지의 두 링크가 각각 올바른 /policy?type=...로 연결되는지 확인
  • Playwright로 /policy 로고 클릭 시 로그인 페이지로 이동하는지 확인
  • 로그인 상태에서 설정 > 약관/개인정보 처리방침 탭 전환 — 미실행: 로그인 가드로 인해 로컬에서 미확인, 리뷰어 확인 요청

- 약관 조회 API(GET /api/v1/terms)를 연동해 독립 /policy 페이지와 설정 > 약관 탭을 구현했습니다
- 로그인 페이지의 이용약관·개인정보 처리방침 링크를 /policy?type=SERVICE, /policy?type=PRIVACY로 연결하고, 약관 로고 클릭 시 로그인 페이지로 이동하도록 했습니다
- 약관 데이터가 없을 때(현재 백엔드 미시딩 상태) 보여줄 빈 상태 문구를 추가했습니다
- 최신 화면설계서에 맞춰 설정의 "약관" 탭을 서비스 이용약관(SERVICE)/개인정보 처리방침(PRIVACY) 두 개의 별도 탭으로 분리했습니다
- 두 탭이 공유하는 레이아웃 컴포넌트(PolicyDocument)를 도메인 간 공유 위치(components/policy)로 옮겼습니다
- 영문 "약관" 탭 라벨을 Figma 영문 레퍼런스에 맞춰 "Terms & Policies" → "Terms of Service"로 수정했습니다
@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 2:33pm

Request Review

@github-actions github-actions Bot added the ⏰ Timo-web Timo 웹 서비스 label Jul 13, 2026
@github-actions
github-actions Bot requested review from kimminna and yumin-kim2 July 13, 2026 02:33
@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

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: be00372d-08cf-44c5-a0b2-c0f0a1ec992e

📥 Commits

Reviewing files that changed from the base of the PR and between f1c0fe4 and 35568b2.

📒 Files selected for processing (3)
  • apps/timo-web/components/policy/PolicyDocument.tsx
  • apps/timo-web/messages/en.json
  • apps/timo-web/messages/ko.json

Walkthrough

약관 타입과 API 응답 검증, 약관 조회 훅, 독립 정책 페이지, 설정의 서비스·개인정보 탭, 로그인 화면의 정책 링크가 추가되었습니다. 약관이 없을 때 표시할 다국어 메시지와 공통 문서 렌더러도 구현되었습니다.

Changes

약관 정책 흐름

Layer / File(s) Summary
약관 계약과 조회 훅
apps/timo-web/types/terms-type.ts, apps/timo-web/queries/use-terms.ts
SERVICE·PRIVACY 타입과 약관 응답 스키마를 정의하고, 검증된 약관 목록을 반환하는 useTerms 훅을 추가했습니다.
정책 라우트와 문서 렌더링
apps/timo-web/app/[locale]/policy/..., apps/timo-web/components/policy/PolicyDocument.tsx
쿼리의 약관 타입을 검증하는 /policy 페이지와 PolicyContainer를 추가하고, 약관 제목·본문을 공통 문서 컴포넌트로 렌더링합니다.
설정 약관 및 개인정보 탭
apps/timo-web/app/[locale]/(main)/settings/..., apps/timo-web/messages/*
설정 탭 타입과 네비게이션에 privacy를 추가하고, SERVICE·PRIVACY 약관을 AsyncBoundarySettingsTermsContainer로 표시합니다.
로그인 정책 링크 연결
apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx, apps/timo-web/constants/routes.ts
로그인 화면의 약관 링크를 /policySERVICE·PRIVACY 쿼리 경로로 연결했습니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant LoginContainer
  participant PolicyPage
  participant PolicyContainer
  participant useTerms
  participant PolicyDocument
  LoginContainer->>PolicyPage: SERVICE 또는 PRIVACY 링크 열기
  PolicyPage->>PolicyContainer: 검증된 type 전달
  PolicyContainer->>useTerms: type별 약관 조회
  useTerms-->>PolicyContainer: 검증된 terms 반환
  PolicyContainer->>PolicyDocument: title과 content 전달
Loading

Possibly related PRs

  • Team-Timo/Timo-client#127: 설정 탭과 네비게이션에 privacy 경로를 추가한 변경으로, 이번 설정 탭 라우팅 흐름과 직접 연결됩니다.

Suggested labels: ♦️ 민아

Suggested reviewers: yumin-kim2

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning 이슈 #163은 설정의 약관 탭에서 SERVICE+PRIVACY를 함께 보여주길 요구하지만, 현재는 두 탭으로 분리돼 요구와 어긋납니다. 설정 약관은 이슈 기준으로 한 탭에 두 약관을 함께 표시하거나, 분리 탭이 맞다면 이슈 요구사항을 갱신하세요.
Out of Scope Changes check ⚠️ Warning 로그인 링크와 /policy 페이지는 범위 내지만, settings의 privacy 탭 추가와 탭 분리는 이슈에 없는 추가 UI 변경입니다. privacy 탭 분리가 의도라면 이슈를 갱신하고, 아니면 설정 약관은 기존 단일 탭 범위로 되돌리세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed 제목이 약관 페이지와 설정 약관 탭 구현, 약관 API 연동이라는 핵심 변경을 잘 요약합니다.
Description check ✅ Passed 설명은 로그인/설정의 약관 API 연동과 /policy 페이지 구현을 구체적으로 다뤄 변경사항과 일치합니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web/163-terms-policy-pages

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

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale]/home 200.87 kB 🔴 406.75 kB
/[locale]/today 156.71 kB 🔴 362.58 kB
/[locale]/focus 158.62 kB 🔴 364.49 kB
/[locale]/settings/account 0 B 🟡 205.87 kB
/[locale]/settings 167.14 kB 🔴 373.01 kB
/[locale]/statistics 151.77 kB 🔴 357.64 kB
/[locale]/[...rest] 0 B 🟡 205.87 kB
/[locale]/login 120.63 kB 🟡 326.51 kB
/[locale]/oauth/callback 118.51 kB 🟡 324.38 kB
/[locale]/onboarding 230.55 kB 🔴 436.43 kB
/[locale] 0 B 🟡 205.87 kB
/[locale]/policy 124.77 kB 🟡 330.65 kB

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

Lighthouse — timo-web
URL Perf A11y LCP CLS TBT
/en/home 🔴 68 🟢 95 🔴 15.6s 🟢 0.000 🟡 304ms
/en/today 🔴 58 🟢 95 🔴 15.3s 🟢 0.000 🔴 654ms
/en/focus 🔴 61 🟢 95 🔴 15.2s 🟢 0.000 🟡 515ms
/en/statistics 🔴 61 🟢 95 🔴 15.2s 🟢 0.000 🟡 537ms

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 변환 권장

측정 커밋: e28289e

@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: 5

🤖 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 13-26: Refactor the tab handling in the settings tabs container to
map the “policy” and “privacy” tab values to their respective
SettingsPolicyContainer and SettingsPrivacyContainer components, then render the
selected component once inside a shared AsyncBoundary. Preserve the existing
behavior for both tabs while removing the duplicated conditional rendering.

In
`@apps/timo-web/app/`[locale]/(main)/settings/policy/_containers/SettingsPolicyContainer.tsx:
- Around line 8-19: Extract the shared terms-rendering logic from
SettingsPolicyContainer and SettingsPrivacyContainer into a single
SettingsTermsContainer component that accepts a TermsType prop, then use that
component from SettingsTabsContainer with “SERVICE” for policy and “PRIVACY” for
privacy. Preserve the existing translation, layout, PolicyDocument, and
empty-state rendering behavior.

In
`@apps/timo-web/app/`[locale]/(main)/settings/privacy/_containers/SettingsPrivacyContainer.tsx:
- Around line 8-22: Extract the duplicated useTerms, translation,
term-selection, and empty-state rendering from SettingsPrivacyContainer and
PolicyContainer into a shared PolicyContent component. Make PolicyContent accept
the terms type and heading level as props, preserve the existing PolicyDocument
rendering and Policy empty-state translation, then update both containers to
delegate to it.

In `@apps/timo-web/app/`[locale]/policy/_containers/PolicyContainer.tsx:
- Around line 14-24: Remove the terms[0] fallback in PolicyContainer so only a
term whose type matches the requested type is rendered. Preserve the existing
empty-state response when no matching term is found, and keep the PolicyDocument
rendering unchanged for a valid match.

In `@apps/timo-web/app/`[locale]/policy/page.tsx:
- Around line 11-12: Move the isTermsType type guard out of page.tsx into the
existing terms-type.ts module, or replace its usage with
termsTypeSchema.safeParse. Update page.tsx to import and reuse the shared
validation logic, keeping routing and layout composition free of hardcoded
terms-type business logic and synchronized with the schema.
🪄 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: 9a09ceb5-cf1a-47cd-9f8c-0e7854a54ea9

📥 Commits

Reviewing files that changed from the base of the PR and between 8b39b01 and 6c7af2e.

📒 Files selected for processing (14)
  • apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx
  • apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsTabsContainer.tsx
  • apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsTab.ts
  • apps/timo-web/app/[locale]/(main)/settings/policy/_containers/SettingsPolicyContainer.tsx
  • apps/timo-web/app/[locale]/(main)/settings/privacy/_containers/SettingsPrivacyContainer.tsx
  • apps/timo-web/app/[locale]/login/_containers/LoginContainer.tsx
  • apps/timo-web/app/[locale]/policy/_containers/PolicyContainer.tsx
  • apps/timo-web/app/[locale]/policy/page.tsx
  • apps/timo-web/components/policy/PolicyDocument.tsx
  • apps/timo-web/constants/routes.ts
  • apps/timo-web/messages/en.json
  • apps/timo-web/messages/ko.json
  • apps/timo-web/queries/use-terms.ts
  • apps/timo-web/types/terms-type.ts

Comment thread apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsTabsContainer.tsx Outdated
Comment on lines +8 to +22
export const SettingsPrivacyContainer = () => {
const t = useTranslations("Policy");
const { data: terms } = useTerms("PRIVACY");
const term = terms[0];

return (
<div className="px-15 pt-7.5">
{term ? (
<PolicyDocument title={term.title} content={term.content} />
) : (
<p className="typo-body-m-12 text-timo-gray-700">{t("empty")}</p>
)}
</div>
);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

PolicyContainer와 로직 중복 — 공통 추출을 제안합니다

SettingsPrivacyContainerPolicyContainer와 동일한 패턴을 따릅니다: useTerms 호출 → term 선택 → PolicyDocument 또는 빈 상태 렌더링. 두 컨테이너 모두 useTranslations("Policy")t("empty")를 사용합니다.

이 패턴을 공통 컴포넌트로 추출하면 유지보수성이 향상됩니다. 예를 들어 PolicyContent 컴포넌트를 만들어 typeheadingLevel을 prop으로 받도록 하면, 두 컨테이너에서 각각 재사용할 수 있습니다.

♻️ 공통 PolicyContent 컴포넌트 추출 제안
// components/policy/PolicyContent.tsx
"use client";

import { useTranslations } from "next-intl";

import type { TermsType } from "`@/types/terms-type`";
import { PolicyDocument } from "`@/components/policy/PolicyDocument`";
import { useTerms } from "`@/queries/use-terms`";

export interface PolicyContentProps {
  type: TermsType;
  headingLevel?: "h1" | "h2";
}

export const PolicyContent = ({ type, headingLevel = "h1" }: PolicyContentProps) => {
  const t = useTranslations("Policy");
  const { data: terms } = useTerms(type);
  const term = terms.find((item) => item.type === type);

  if (!term) {
    return <p className="typo-body-m-12 text-timo-gray-700">{t("empty")}</p>;
  }

  return <PolicyDocument title={term.title} content={term.content} headingLevel={headingLevel} />;
};

그 후 SettingsPrivacyContainer는 단순화:

 "use client";

-import { useTranslations } from "next-intl";
-
-import { PolicyDocument } from "`@/components/policy/PolicyDocument`";
-import { useTerms } from "`@/queries/use-terms`";
+import { PolicyContent } from "`@/components/policy/PolicyContent`";

 export const SettingsPrivacyContainer = () => {
-  const t = useTranslations("Policy");
-  const { data: terms } = useTerms("PRIVACY");
-  const term = terms[0];
-
   return (
     <div className="px-15 pt-7.5">
-      {term ? (
-        <PolicyDocument title={term.title} content={term.content} />
-      ) : (
-        <p className="typo-body-m-12 text-timo-gray-700">{t("empty")}</p>
-      )}
+      <PolicyContent type="PRIVACY" headingLevel="h2" />
     </div>
   );
 };
🤖 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/privacy/_containers/SettingsPrivacyContainer.tsx
around lines 8 - 22, Extract the duplicated useTerms, translation,
term-selection, and empty-state rendering from SettingsPrivacyContainer and
PolicyContainer into a shared PolicyContent component. Make PolicyContent accept
the terms type and heading level as props, preserve the existing PolicyDocument
rendering and Policy empty-state translation, then update both containers to
delegate to it.

Comment thread apps/timo-web/app/[locale]/policy/_containers/PolicyContainer.tsx
Comment thread apps/timo-web/app/[locale]/policy/page.tsx Outdated

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/generated/endpoints/terms/terms.zod.tsGetTermsResponse랑 내용이 겹치는 것 같아서 새로 만들 필요 없이 가져다 쓰면 좋을 것 같아요! 확인 부탁드립니다

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

terms.zod.tsGetTermsResponsetype을 그냥 zod.string()으로 생성해서 "SERVICE" | "PRIVACY" 리터럴 타입을 잃게 돼요. 이 리터럴 타입은 PolicyContainer/SettingsTermsContainertype prop, useTerms(type?: TermsType), page.tsxtermsTypeSchema.safeParse(type) 쿼리 파라미터 검증까지 앱 전반에서 실제로 쓰이고 있어서, 이 타입 좁히기 자체가 terms-type.ts가 존재하는 이유입니다.

또한 GetTermsResponsestatus/message/data 래퍼 안에 terms 배열이 인라인으로 정의되어 있고, 아이템 단위 스키마가 별도로 export되어 있지 않아요. 재사용하려면 GetTermsResponse.shape.data.unwrap().shape.terms.element 같은 방식으로 생성 파일 내부 구조에 직접 접근해야 하는데, 이 파일은 "Do not edit manually" 주석이 붙은 orval 자동 생성 파일이라 재생성 시 구조가 바뀌면 조용히 깨질 수 있습니다.

그래서 termsItemSchema/termsListSchema 를 통째로 제거하기보다는, termsTypeSchema(리터럴 enum)는 그대로 유지하는 게 맞다고 생각이들어요.

혜원님은 어떻게 생각하시나요?

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.

아하 설명 감사합니다! ✌🏻
생성된 스키마를 재사용하는 쪽만 생각했는데 TermsType의 리터럴 타입과 orval 내부 구조 의존성까지 생각하면 지금처럼 별도 스키마를 유지하는 게 맞는 것 같습니다!

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.

TermsTypeSchema 부분만 별도로 사용하면 될 것 같아요~

@jjangminii jjangminii changed the title [FEAT] 약관 페이지 및 설정 약관 탭 구현 [FEAT] 약관 페이지 및 설정 약관 탭 구현 / 약관 API 연결 Jul 13, 2026
- 설정 탭의 서비스 이용약관/개인정보 처리방침 컨테이너를 SettingsTermsContainer로 통합해 중복된 조회·번역·빈 상태 로직을 제거했습니다
- PolicyContainer에서 타입이 일치하지 않는 약관을 렌더링하던 폴백을 제거했습니다
- policy 페이지의 약관 타입 검증을 termsTypeSchema.safeParse로 교체해 스키마와 동기화했습니다
- PolicyDocument에 headingLevel prop을 추가해 설정 탭에서 제목 레벨을 조정할 수 있도록 했습니다

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

Caution

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

⚠️ Outside diff range comments (1)
apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsTermsContainer.tsx (1)

14-30: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

terms[0] 대신 terms.find()를 사용해 PolicyContainer와 일관성을 맞추세요

PolicyContainer는 과거 리뷰 피드백을 반영해 terms.find((item) => item.type === type)를 사용합니다. 반면 SettingsTermsContainerterms[0]를 직접 사용합니다. API가 요청한 타입과 다른 약관을 반환할 경우, terms[0]는 잘못된 타입의 약관을 렌더링하게 됩니다. 두 컨테이너가 동일한 useTerms 훅을 공유하므로 조회 로직도 일관되게 유지하는 것이 안전합니다.

🔄 Proposed fix: `terms.find()`로 일관성 확보
 export const SettingsTermsContainer = ({
   type,
 }: SettingsTermsContainerProps) => {
   const t = useTranslations("Policy");
   const { data: terms } = useTerms(type);
-  const term = terms[0];
+  const term = terms.find((item) => item.type === type);
🤖 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/SettingsTermsContainer.tsx
around lines 14 - 30, Update SettingsTermsContainer to select the term with
terms.find((item) => item.type === type) instead of terms[0], matching
PolicyContainer and ensuring only the requested policy type is rendered.
🤖 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.

Outside diff comments:
In
`@apps/timo-web/app/`[locale]/(main)/settings/_containers/SettingsTermsContainer.tsx:
- Around line 14-30: Update SettingsTermsContainer to select the term with
terms.find((item) => item.type === type) instead of terms[0], matching
PolicyContainer and ensuring only the requested policy type is rendered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a8eb0f14-df74-4620-a94a-2a55ce23e6c4

📥 Commits

Reviewing files that changed from the base of the PR and between 6c7af2e and f1c0fe4.

📒 Files selected for processing (7)
  • apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsTabsContainer.tsx
  • apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsTermsContainer.tsx
  • apps/timo-web/app/[locale]/(main)/settings/policy/_components/.gitkeep
  • apps/timo-web/app/[locale]/(main)/settings/policy/_queries/.gitkeep
  • apps/timo-web/app/[locale]/policy/_containers/PolicyContainer.tsx
  • apps/timo-web/app/[locale]/policy/page.tsx
  • apps/timo-web/components/policy/PolicyDocument.tsx

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.

TermsTypeSchema 부분만 별도로 사용하면 될 것 같아요~

Comment on lines +10 to +11
headingLevel = "h1",
}: PolicyDocumentProps) => {

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.

헤딩 레벨을 props로 전달한 이유가 따로 있나요?

- 설정 탭·독립 정책 페이지 모두 h1을 그대로 쓰고 있어 사용되지 않던 headingLevel prop을 제거했습니다
- 설정 탭들은 각자 독립적인 h1을 갖는 게 이 프로젝트의 기존 패턴이라 heading 레벨을 조정할 필요가 없었습니다
…to feat/web/163-terms-policy-pages

# Conflicts:
#	apps/timo-web/messages/en.json
#	apps/timo-web/messages/ko.json

@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: 1

🤖 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/messages/ko.json`:
- Around line 206-207: Update the switchButton translation in the Korean
messages to a label describing the switch action, such as “전환하기,” instead of
duplicating the completeButton translation “완료하기.”
🪄 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: be00372d-08cf-44c5-a0b2-c0f0a1ec992e

📥 Commits

Reviewing files that changed from the base of the PR and between f1c0fe4 and 35568b2.

📒 Files selected for processing (3)
  • apps/timo-web/components/policy/PolicyDocument.tsx
  • apps/timo-web/messages/en.json
  • apps/timo-web/messages/ko.json

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 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/messages/ko.json`:
- Around line 206-207: Update the switchButton translation in the Korean
messages to a label describing the switch action, such as “전환하기,” instead of
duplicating the completeButton translation “완료하기.”
🪄 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: be00372d-08cf-44c5-a0b2-c0f0a1ec992e

📥 Commits

Reviewing files that changed from the base of the PR and between f1c0fe4 and 35568b2.

📒 Files selected for processing (3)
  • apps/timo-web/components/policy/PolicyDocument.tsx
  • apps/timo-web/messages/en.json
  • apps/timo-web/messages/ko.json
🛑 Comments failed to post (1)
apps/timo-web/messages/ko.json (1)

206-207: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

switchButton 번역을 전환 동작에 맞게 수정해 주세요.

switchButton"완료하기"로 번역되어 completeButton과 동일합니다. 사용자가 작업 전환과 완료를 구분할 수 없으므로 "전환하기" 등 실제 동작에 맞는 라벨로 변경해야 합니다. 메시지 키별 locale 번역을 관리하는 방식은 next-intl 공식 문서도 참고할 수 있습니다. (learn.next-intl.dev)

🤖 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/messages/ko.json` around lines 206 - 207, Update the
switchButton translation in the Korean messages to a label describing the switch
action, such as “전환하기,” instead of duplicating the completeButton translation
“완료하기.”

@jjangminii
jjangminii merged commit 2b0bae8 into develop Jul 13, 2026
12 checks passed
@jjangminii
jjangminii deleted the feat/web/163-terms-policy-pages branch July 13, 2026 14:43
@kimminna kimminna mentioned this pull request Jul 14, 2026
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] 약관 페이지 및 설정 약관 탭 구현

3 participants