Skip to content

06.26.최신화 - 동적 사이트맵 및 회원가입 약관 동의 업데이트 - #420

Merged
shinwokkang merged 9 commits into
mainfrom
dev
Jun 26, 2026
Merged

shinwokkang merged 9 commits into
mainfrom
dev

Conversation

@shinwokkang

@shinwokkang shinwokkang commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

📌 개요 (Summary)

  • 변경 사항에 대한 간략한 요약을 적어주세요.
  • 관련 이슈가 있다면 링크를 걸어주세요 (예: [fix] cicd / build 문제 #123).

🛠️ 변경 사항 (Changes)

  • 새로운 기능 추가
  • 버그 수정
  • 코드 리팩토링
  • 문서 업데이트
  • 기타 (설명: )

📸 스크린샷 (Screenshots)

(UI 변경 사항이 있다면 첨부해주세요)

✅ 체크리스트 (Checklist)

  • 빌드가 성공적으로 수행되었나요? (pnpm build)
  • 린트 에러가 없나요? (pnpm lint)
  • 불필요한 콘솔 로그나 주석을 제거했나요?

Summary by CodeRabbit

  • New Features

    • Sitemaps now include up-to-date stories, clubs, and news items in addition to static pages.
    • Support pages for privacy, third-party consent, and marketing consent now render from structured content for more consistent display.
  • Bug Fixes

    • Improved resilience so sitemap updates continue even if one content source fails.
    • Updated consent pages and agreement views to show cleaner, more readable formatting.

@shinwokkang shinwokkang self-assigned this Jun 26, 2026
@shinwokkang shinwokkang added ✨ feat 새로운 기능 추가 📝 docs 문서 수정 labels Jun 26, 2026
@vercel

vercel Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
checkmo Ready Ready Preview, Comment Jun 26, 2026 7:26am

@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR makes sitemap generation async and paginated for story, club, and news routes, and rewrites the marketing, privacy, third-party consent, and terms-agreement screens to render from structured consent data constants.

Changes

Dynamic sitemap routes

Layer / File(s) Summary
Sitemap contracts and fetchers
src/types/sitemap.ts, src/lib/api/endpoints/bookstory.ts, src/lib/api/endpoints/Clubs.ts, src/lib/api/endpoints/news.ts, src/services/storyService.ts, src/services/clubService.ts, src/services/newsService.ts
Adds sitemap item/list types, sitemap endpoint URLs, and service methods for fetching paginated story, club, and news sitemap data.
Async sitemap assembly
src/app/sitemap.ts
Makes the sitemap route async, adds a 12-hour revalidate interval, fetches each sitemap source in pages, handles each source with Promise.allSettled, and returns static routes plus the fetched dynamic routes.

Consent documents

Layer / File(s) Summary
Structured consent data
src/constants/setting/marketing.ts, src/constants/setting/privacy.ts, src/constants/setting/thirdParty.ts
Adds structured section arrays for marketing, privacy, and third-party consent content.
Consent page rendering
src/app/support/v1/marketing-consent/page.tsx, src/app/support/v1/privacy/page.tsx, src/app/support/v1/third-party-consent/page.tsx, src/components/base-ui/Join/steps/TermsAgreement/TermsAgreement.tsx
Maps the consent pages and agreement step over the new structured content, rendering lists or trimmed paragraphs from each section and updating the marketing and third-party metadata text.

Sequence Diagram(s)

sequenceDiagram
  participant sitemapRoute
  participant storyService
  participant clubService
  participant newsService
  participant storySitemapEndpoint
  participant clubSitemapEndpoint
  participant newsSitemapEndpoint

  sitemapRoute->>storyService: fetchAllSitemapItems()
  sitemapRoute->>clubService: fetchAllSitemapItems()
  sitemapRoute->>newsService: fetchAllSitemapItems()
  storyService->>storySitemapEndpoint: GET cursorId + limit
  clubService->>clubSitemapEndpoint: GET cursorId + limit
  newsService->>newsSitemapEndpoint: GET cursorId + limit
  storySitemapEndpoint-->>storyService: SitemapListResponse pages
  clubSitemapEndpoint-->>clubService: SitemapListResponse pages
  newsSitemapEndpoint-->>newsService: SitemapListResponse pages
  sitemapRoute->>sitemapRoute: Promise.allSettled() merges staticRoutes + dynamic routes
Loading

Estimated review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • checkmo2025/FE#141: Adds the sitemap endpoints and service methods that this PR’s dynamic sitemap route consumes.
  • checkmo2025/FE#388: Introduces the terms-content rendering path that this PR replaces for the consent pages and agreement flow.
  • checkmo2025/FE#412: Touches the same sitemap route and support consent pages, overlapping with the routing and consent-content changes here.

Poem

🐇 I hopped through sitemaps, page by page,
and tucked fresh words in tidy rows.
The consent leaves now flutter bright,
with news and stories in their rows.
Thump-thump—my burrow sings tonight!

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main changes: dynamic sitemap updates and signup terms consent refactoring.
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.
✨ 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 dev

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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements dynamic sitemap generation by fetching stories, clubs, and news data in parallel, and refactors the legal and marketing consent pages to use centralized constants instead of the deprecated TermsMarkdown component. The review feedback highlights several areas for improvement to prevent runtime errors, specifically recommending defensive checks in the sitemap fetching loop and replacing non-null assertions with safe fallback values in the API services.

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.

Comment thread src/app/sitemap.ts
Comment on lines +17 to +26
const response = await fetchFn(currentCursor, 5000); // 백엔드 최대 허용 limit
if (response.items) {
allItems = allItems.concat(response.items);
}
hasMore = response.hasNext;
if (hasMore && response.nextCursor) {
currentCursor = response.nextCursor;
} else {
hasMore = false;
}

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.

medium

fetchFn이 예기치 않게 undefined 또는 null을 반환할 경우, response.items에 접근할 때 런타임 에러(TypeError: Cannot read properties of undefined)가 발생할 수 있습니다. response가 유효한지 먼저 확인하는 방어적 코드를 추가하고, 잘못된 들여쓰기(indentation)도 함께 수정하는 것이 좋습니다.

    const response = await fetchFn(currentCursor, 5000); // 백엔드 최대 허용 limit
    if (!response) {
      break;
    }
    if (response.items) {
      allItems = allItems.concat(response.items);
    }
    hasMore = response.hasNext;
    if (hasMore && response.nextCursor) {
      currentCursor = response.nextCursor;
    } else {
      hasMore = false;
    }

Comment on lines +17 to +22
getClubsSitemap: async (cursorId?: number, limit: number = 1000): Promise<SitemapListResponse> => {
const res = await apiClient.get<ApiResponse<SitemapListResponse>>(CLUBS.sitemap, {
params: { cursorId, limit },
});
return res.result!;
},

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.

medium

API 응답에서 res.result가 없거나 실패했을 경우, Non-null assertion(!)을 사용하면 런타임에 undefined가 반환되어 이를 호출하는 곳에서 에러가 발생할 수 있습니다. 안전하게 기본값을 반환하거나 방어 코드를 작성하는 것이 좋습니다.

Suggested change
getClubsSitemap: async (cursorId?: number, limit: number = 1000): Promise<SitemapListResponse> => {
const res = await apiClient.get<ApiResponse<SitemapListResponse>>(CLUBS.sitemap, {
params: { cursorId, limit },
});
return res.result!;
},
getClubsSitemap: async (cursorId?: number, limit: number = 1000): Promise<SitemapListResponse> => {
const res = await apiClient.get<ApiResponse<SitemapListResponse>>(CLUBS.sitemap, {
params: { cursorId, limit },
});
return res.result || { items: [], hasNext: false, nextCursor: null, pageSize: limit };
},

Comment on lines +9 to +15
getNewsSitemap: async (cursorId?: number, limit: number = 1000): Promise<SitemapListResponse> => {
const response = await apiClient.get<ApiResponse<SitemapListResponse>>(
NEWS_ENDPOINTS.SITEMAP,
{ params: { cursorId, limit } }
);
return response.result!;
},

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.

medium

response.result!와 같이 Non-null assertion을 사용하면 API 응답에 result가 없을 때 런타임 에러가 발생할 수 있습니다. 안전하게 기본값을 제공하도록 수정하는 것을 권장합니다.

Suggested change
getNewsSitemap: async (cursorId?: number, limit: number = 1000): Promise<SitemapListResponse> => {
const response = await apiClient.get<ApiResponse<SitemapListResponse>>(
NEWS_ENDPOINTS.SITEMAP,
{ params: { cursorId, limit } }
);
return response.result!;
},
getNewsSitemap: async (cursorId?: number, limit: number = 1000): Promise<SitemapListResponse> => {
const response = await apiClient.get<ApiResponse<SitemapListResponse>>(
NEWS_ENDPOINTS.SITEMAP,
{ params: { cursorId, limit } }
);
return response.result || { items: [], hasNext: false, nextCursor: null, pageSize: limit };
},

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

🧹 Nitpick comments (5)
src/services/clubService.ts (1)

17-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer extractResult over the ! non-null assertion for consistency.

storyService.getStoriesSitemap uses extractResult(...), which throws a clear error when result is missing. Here res.result! silently asserts non-null and would propagate undefined downstream into fetchAllSitemapItems if the backend omits result. Align with the story service to keep failure handling uniform.

♻️ Suggested change
-  getClubsSitemap: async (cursorId?: number, limit: number = 1000): Promise<SitemapListResponse> => {
-    const res = await apiClient.get<ApiResponse<SitemapListResponse>>(CLUBS.sitemap, {
-      params: { cursorId, limit },
-    });
-    return res.result!;
-  },
+  getClubsSitemap: async (cursorId?: number, limit: number = 1000): Promise<SitemapListResponse> => {
+    const res = await apiClient.get<ApiResponse<SitemapListResponse>>(CLUBS.sitemap, {
+      params: { cursorId, limit },
+    });
+    return extractResult(res);
+  },
🤖 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 `@src/services/clubService.ts` around lines 17 - 22, The getClubsSitemap method
is using a non-null assertion on res.result instead of the shared extractResult
helper, which can let undefined slip through. Update getClubsSitemap in
clubService to match storyService.getStoriesSitemap by calling extractResult on
the apiClient.get response and returning that value, so missing result data
fails consistently and clearly.
src/app/sitemap.ts (1)

9-30: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider a timeout/abort around the paginated fetches.

fetchAllSitemapItems loops over apiClient.get calls during route revalidation with no timeout. A slow/hung backend would stall sitemap (re)generation. Since failures are already tolerated per-entity via Promise.allSettled, adding a request timeout (or AbortController) would let a stuck entity fail fast and still emit static + other dynamic routes.

🤖 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 `@src/app/sitemap.ts` around lines 9 - 30, `fetchAllSitemapItems` currently
paginates via repeated `fetchFn` calls with no timeout, so a slow or hung
backend can block sitemap generation. Update the `fetchAllSitemapItems` loop
(and the `apiClient.get` path it delegates to) to use a request timeout or
`AbortController`, and make sure a timed-out page fetch fails fast so the rest
of the sitemap work can continue. Keep the pagination behavior intact by
preserving `currentCursor`, `hasMore`, and the `SitemapListResponse` flow while
adding the abort/timeout handling.
src/services/newsService.ts (1)

9-15: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unsafe non-null assertion on an optional result.

ApiResponse here declares result?: T, so response.result! masks a genuinely possible undefined. If the backend returns no result, this resolves to undefined, which then flows into fetchAllSitemapItems (the .items guard hides it, but hasNext becomes undefined). Use extractResult(...) as in storyService to fail fast with a clear message instead.

♻️ Suggested change
-        return response.result!;
+        return extractResult(response);
🤖 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 `@src/services/newsService.ts` around lines 9 - 15, The getNewsSitemap method
is using an unsafe non-null assertion on ApiResponse.result, which can allow
undefined to leak into fetchAllSitemapItems. Update getNewsSitemap to use
extractResult(...) like storyService does, so the NewsService response is
validated and fails fast with a clear error when result is missing. Keep the
change localized to NewsService and preserve the existing API call and return
type behavior.
src/constants/setting/marketing.ts (1)

1-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract a shared ConsentSection type.

MARKETING_DATA, PRIVACY_DATA, and THIRD_PARTY_DATA all share the { title: string; content: string | string[] } shape and are assigned to TERMS_CONTENT_MAP with that exact type. Declaring and applying a shared type (e.g. satisfies ConsentSection[]) would catch typos in content shape at the source rather than only at the map assignment.

🤖 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 `@src/constants/setting/marketing.ts` around lines 1 - 35, Extract and apply a
shared ConsentSection type for the consent content arrays used by
MARKETING_DATA, PRIVACY_DATA, and THIRD_PARTY_DATA so shape errors are caught
where the data is defined. Update MARKETING_DATA to conform to that shared type
(for example via a typed array declaration or a satisfies ConsentSection[]
assertion) while preserving the existing title/content structure, and keep
TERMS_CONTENT_MAP relying on the same type.
src/app/support/v1/marketing-consent/page.tsx (1)

25-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated section-rendering logic across consent pages.

This MARKETING_DATA.map(...) block — the Array.isArray(term.content) branch, the DocumentList vs newline-split paragraph rendering, and the key strategy — is byte-for-byte identical in privacy/page.tsx and third-party-consent/page.tsx, and near-identical in TermsAgreement.tsx. Consider extracting a small ConsentSections component (or a renderConsentSection helper) that takes the section array, so future formatting changes happen in one place.

♻️ Sketch
// src/components/base-ui/PublicDocument/ConsentSections.tsx
export function ConsentSections({ data }: { data: { title: string; content: string | string[] }[] }) {
  return data.map((term) => (
    <DocumentSection key={term.title} title={term.title}>
      {Array.isArray(term.content) ? (
        <DocumentList items={term.content} />
      ) : (
        <div className="flex flex-col gap-2">
          {term.content.split("\n").map((line, index) => (
            <p key={`${line.trim()}-${index}`}>{line.trim()}</p>
          ))}
        </div>
      )}
    </DocumentSection>
  ));
}
🤖 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 `@src/app/support/v1/marketing-consent/page.tsx` around lines 25 - 37, The
section rendering logic in this `MARKETING_DATA.map(...)` block is duplicated
across the consent pages, so extract it into a shared `ConsentSections`
component or `renderConsentSection` helper and reuse it here. Move the
`Array.isArray(term.content)` branching, `DocumentList` vs newline-split
paragraph rendering, and the existing key strategy into that shared helper so
`marketing-consent/page.tsx`, `privacy/page.tsx`,
`third-party-consent/page.tsx`, and `TermsAgreement` all consume the same
implementation.
🤖 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 `@src/app/sitemap.ts`:
- Around line 16-27: The pagination loop in sitemap generation can stop early
when `response.nextCursor` is `0` or another falsy value, even if
`response.hasNext` is still true. Update the `while (hasMore)` flow in
`sitemap.ts` to advance based on an explicit null/undefined check for
`response.nextCursor` instead of truthiness, using `fetchFn`, `currentCursor`,
and `hasMore` as the key control points. Also add a safeguard in the same loop
to break if the backend returns the same cursor again so `hasNext: true` cannot
cause an endless loop.

In `@src/components/base-ui/Join/steps/TermsAgreement/TermsAgreement.tsx`:
- Around line 15-27: `TermsAgreement.tsx` is using agreement IDs that don’t
match the raw `agreements` keys used by `SignupTerms.tsx` and `SignupContext`,
so the required selections may never resolve correctly. Update `TERMS_DATA` in
`TermsAgreement` to use the same standard IDs as the signup flow (`privacy` and
`terms` instead of `servicePrivacy` and `termsOfUse`), and keep
`TERMS_CONTENT_MAP` aligned with those identifiers so the checked state and
Next-button enablement work consistently.

---

Nitpick comments:
In `@src/app/sitemap.ts`:
- Around line 9-30: `fetchAllSitemapItems` currently paginates via repeated
`fetchFn` calls with no timeout, so a slow or hung backend can block sitemap
generation. Update the `fetchAllSitemapItems` loop (and the `apiClient.get` path
it delegates to) to use a request timeout or `AbortController`, and make sure a
timed-out page fetch fails fast so the rest of the sitemap work can continue.
Keep the pagination behavior intact by preserving `currentCursor`, `hasMore`,
and the `SitemapListResponse` flow while adding the abort/timeout handling.

In `@src/app/support/v1/marketing-consent/page.tsx`:
- Around line 25-37: The section rendering logic in this
`MARKETING_DATA.map(...)` block is duplicated across the consent pages, so
extract it into a shared `ConsentSections` component or `renderConsentSection`
helper and reuse it here. Move the `Array.isArray(term.content)` branching,
`DocumentList` vs newline-split paragraph rendering, and the existing key
strategy into that shared helper so `marketing-consent/page.tsx`,
`privacy/page.tsx`, `third-party-consent/page.tsx`, and `TermsAgreement` all
consume the same implementation.

In `@src/constants/setting/marketing.ts`:
- Around line 1-35: Extract and apply a shared ConsentSection type for the
consent content arrays used by MARKETING_DATA, PRIVACY_DATA, and
THIRD_PARTY_DATA so shape errors are caught where the data is defined. Update
MARKETING_DATA to conform to that shared type (for example via a typed array
declaration or a satisfies ConsentSection[] assertion) while preserving the
existing title/content structure, and keep TERMS_CONTENT_MAP relying on the same
type.

In `@src/services/clubService.ts`:
- Around line 17-22: The getClubsSitemap method is using a non-null assertion on
res.result instead of the shared extractResult helper, which can let undefined
slip through. Update getClubsSitemap in clubService to match
storyService.getStoriesSitemap by calling extractResult on the apiClient.get
response and returning that value, so missing result data fails consistently and
clearly.

In `@src/services/newsService.ts`:
- Around line 9-15: The getNewsSitemap method is using an unsafe non-null
assertion on ApiResponse.result, which can allow undefined to leak into
fetchAllSitemapItems. Update getNewsSitemap to use extractResult(...) like
storyService does, so the NewsService response is validated and fails fast with
a clear error when result is missing. Keep the change localized to NewsService
and preserve the existing API call and return type behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 74989e20-c6ca-42a4-a819-9b0c75870986

📥 Commits

Reviewing files that changed from the base of the PR and between 3a1a188 and 3651a68.

📒 Files selected for processing (17)
  • src/app/sitemap.ts
  • src/app/support/v1/marketing-consent/page.tsx
  • src/app/support/v1/privacy/page.tsx
  • src/app/support/v1/third-party-consent/page.tsx
  • src/components/base-ui/Join/steps/TermsAgreement/TermsAgreement.tsx
  • src/components/common/TermsMarkdown.tsx
  • src/constants/setting/marketing.ts
  • src/constants/setting/privacy.ts
  • src/constants/setting/thirdParty.ts
  • src/constants/signupTerms.ts
  • src/lib/api/endpoints/Clubs.ts
  • src/lib/api/endpoints/bookstory.ts
  • src/lib/api/endpoints/news.ts
  • src/services/clubService.ts
  • src/services/newsService.ts
  • src/services/storyService.ts
  • src/types/sitemap.ts
💤 Files with no reviewable changes (2)
  • src/constants/signupTerms.ts
  • src/components/common/TermsMarkdown.tsx

Comment thread src/app/sitemap.ts
Comment on lines +16 to +27
while (hasMore) {
const response = await fetchFn(currentCursor, 5000); // 백엔드 최대 허용 limit
if (response.items) {
allItems = allItems.concat(response.items);
}
hasMore = response.hasNext;
if (hasMore && response.nextCursor) {
currentCursor = response.nextCursor;
} else {
hasMore = false;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Early termination when nextCursor is 0 (or falsy) while hasNext is true.

if (hasMore && response.nextCursor) treats nextCursor === 0 as "no more pages" and stops, silently dropping the rest of the sitemap. If cursor IDs can be 0, prefer an explicit null/undefined check. Also consider guarding against a non-advancing cursor to avoid a potential unbounded loop if the backend repeatedly returns hasNext: true.

♻️ Suggested change
-    hasMore = response.hasNext;
-    if (hasMore && response.nextCursor) {
-      currentCursor = response.nextCursor;
-    } else {
-        hasMore = false;
-    }
+    if (response.hasNext && response.nextCursor != null) {
+      currentCursor = response.nextCursor;
+    } else {
+      hasMore = 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
while (hasMore) {
const response = await fetchFn(currentCursor, 5000); // 백엔드 최대 허용 limit
if (response.items) {
allItems = allItems.concat(response.items);
}
hasMore = response.hasNext;
if (hasMore && response.nextCursor) {
currentCursor = response.nextCursor;
} else {
hasMore = false;
}
}
while (hasMore) {
const response = await fetchFn(currentCursor, 5000); // 백엔드 최대 허용 limit
if (response.items) {
allItems = allItems.concat(response.items);
}
if (response.hasNext && response.nextCursor != null) {
currentCursor = response.nextCursor;
} else {
hasMore = false;
}
}
🤖 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 `@src/app/sitemap.ts` around lines 16 - 27, The pagination loop in sitemap
generation can stop early when `response.nextCursor` is `0` or another falsy
value, even if `response.hasNext` is still true. Update the `while (hasMore)`
flow in `sitemap.ts` to advance based on an explicit null/undefined check for
`response.nextCursor` instead of truthiness, using `fetchFn`, `currentCursor`,
and `hasMore` as the key control points. Also add a safeguard in the same loop
to break if the backend returns the same cursor again so `hasNext: true` cannot
cause an endless loop.

Comment on lines +15 to +27
const TERMS_DATA = [
{ id: "servicePrivacy", label: "서비스 이용을 위한 필수 개인정보 수집·이용 동의 (필수)", required: true },
{ id: "termsOfUse", label: "책모 이용약관 동의 (필수)", required: true },
{ id: "thirdParty", label: "개인정보 제3자 제공 동의 (선택)", required: false },
{ id: "marketing", label: "마케팅 및 이벤트 정보 수신 동의 (선택)", required: false },
];

const TERMS_CONTENT_MAP: Record<string, { title: string; content: string | string[] }[]> = {
servicePrivacy: PRIVACY_DATA,
termsOfUse: TERMS_OF_USE_DATA,
thirdParty: THIRD_PARTY_DATA,
marketing: MARKETING_DATA,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find SignupContext definition and all readers of agreement ids
fd -t f 'SignupContext' 
rg -nP "agreements\s*[\.\[]|'(privacy|terms|servicePrivacy|termsOfUse|thirdParty|marketing)'|\"(privacy|terms|servicePrivacy|termsOfUse)\"" src -g '!**/*.test.*' -C2

Repository: checkmo2025/FE

Length of output: 4662


🏁 Script executed:

#!/bin/bash
# Inspect SignupContext to find how agreements state is initialized and updated
cat -n src/contexts/SignupContext.tsx | head -150

Repository: checkmo2025/FE

Length of output: 6286


Fix agreement ID mismatch between TermsAgreement.tsx and SignupTerms.tsx

The agreements state in SignupContext is a raw map without key normalization. TermsAgreement.tsx uses IDs servicePrivacy and termsOfUse, while SignupTerms.tsx uses privacy and terms. If the flow relies on SignupTerms to set agreement state, or if the backend initializes these keys, TermsAgreement will fail to detect them, keeping the "Next" button disabled.

Align TERMS_DATA IDs in TermsAgreement.tsx to match the standard used in SignupTerms.tsx:

View diff
const TERMS_DATA = [
  { id: "privacy", label: "서비스 이용을 위한 필수 개인정보 수집·이용 동의 (필수)", required: true },
  { id: "terms", label: "책모 이용약관 동의 (필수)", required: true },
  { id: "thirdParty", label: "개인정보 제3자 제공 동의 (선택)", required: false },
  { id: "marketing", label: "마케팅 및 이벤트 정보 수신 동의 (선택)", required: false },
];
[classification_tags]
🤖 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 `@src/components/base-ui/Join/steps/TermsAgreement/TermsAgreement.tsx` around
lines 15 - 27, `TermsAgreement.tsx` is using agreement IDs that don’t match the
raw `agreements` keys used by `SignupTerms.tsx` and `SignupContext`, so the
required selections may never resolve correctly. Update `TERMS_DATA` in
`TermsAgreement` to use the same standard IDs as the signup flow (`privacy` and
`terms` instead of `servicePrivacy` and `termsOfUse`), and keep
`TERMS_CONTENT_MAP` aligned with those identifiers so the checked state and
Next-button enablement work consistently.

@shinwokkang
shinwokkang merged commit a0534dd into main Jun 26, 2026
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 29, 2026
8 tasks

This branch was successfully deployed

1 active deployment
Preview — 3651a686 Deployed Jun 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📝 docs 문서 수정 ✨ feat 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant