Skip to content

feat(career): 未ログインのお試し入力動線とサイドバー/ヘッダーUIを刷新#312

Merged
yusuke0610 merged 2 commits into
mainfrom
feat/career-anonymous-draft-login
Jun 9, 2026
Merged

feat(career): 未ログインのお試し入力動線とサイドバー/ヘッダーUIを刷新#312
yusuke0610 merged 2 commits into
mainfrom
feat/career-anonymous-draft-login

Conversation

@yusuke0610

@yusuke0610 yusuke0610 commented Jun 8, 2026

Copy link
Copy Markdown
Owner

概要

未ログインユーザーが「ログイン強制」ではなく、まず職務経歴書を入力できる try before sign up 型の動線に刷新します。価値を体験してから(入力済みデータという sunk cost を抱えた状態で)ログインを促すことで登録への転換を狙います。あわせてヘッダー/フッターの UI を整理しました。

主な変更

未ログインのお試し入力

  • /career を認証有無の両対応に変更(AuthenticatedLayoutSidebarLayout に統合、/careerPrivateRoute 外へ)。
  • 未ログインでも職務経歴書を入力可能。保存 / プレビュー / PDF・Markdown 出力 / GitHub・ブログ連携への遷移を試みるとログイン促進モーダル(共有 LoginPromptProvider)を表示。
  • 入力ドラフトを sessionStorage に退避し、OAuth 往復後に復元。新規ユーザーは自動保存、既存ユーザーはフォーム復元のみ(上書きは本人操作)。

UI

  • 未ログインのフッターは username の位置に ログインボタン▲ メニュー(ダークモード切替 / GitHub Issue 報告 / © 表示)。認証時は従来の UserMenu
  • 職務経歴書ヘッダーの 保存 / プレビュー / PDF出力 / Markdown出力 / 削除 をアイコン化(日本語 aria-label / title を維持)。

共通フック

  • useDocumentFormskipLoad(匿名モードで loadLatest を呼ばない)を追加し、save()Promise<boolean> 化。

破壊的変更 / 設計メモ

  • AuthenticatedLayout.tsx を削除し SidebarLayout.tsx に統合。ルーティング構成を変更。
  • 匿名保存は氏名のみ必須(カジュアル優先)。残項目はログイン後にサーバ検証。
  • プレビュー/PDF 等のエンドポイントは認証必須のまま(backend 変更なし)。

テスト

  • make ci 通過(backend lint/test + frontend lint/test 288 + build)。
  • E2E(Playwright)30 passed。認証/ナビ/レイアウト変更のため実行。auth.spec.ts の旧「/login リダイレクト」アサーションを新フローへ置換し、連携/プレビュー押下→モーダル、ドラフト復元の往復テストを追加。
  • ユニットテスト追加: careerDraft / useCareerDraftRestore / LoginPromptModal / useDocumentForm

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Unauthenticated users can open the career form; save/preview/export actions show a login-prompt modal and persist a draft to sessionStorage for post-login restore.
  • Refactor

    • Reworked sidebar and user menu to surface login triggers and GitHub integration controls; shared login-prompt provider added.
  • Tests

    • Added/expanded E2E and unit tests covering unauthenticated flows, draft round-trip, and login prompt behavior.
  • UI

    • New icons and localized messages for login, preview, and export actions.

- 未ログインでも職務経歴書を入力可能にし、保存/プレビュー/ダウンロード/
  GitHub・ブログ連携への遷移時にログイン促進モーダルを表示する
- 入力ドラフトを sessionStorage に退避し OAuth 往復後に復元(新規ユーザーは
  自動保存、既存ユーザーはフォーム復元のみ)。useCareerDraftRestore / careerDraft を追加
- AuthenticatedLayout を SidebarLayout に統合し /career を認証有無の両対応に
  (未ログインは連携メニューを押下でログイン誘導、職務経歴書のみ入力可)
- 未ログインのフッターは username 位置にログインボタン+▲メニュー
  (ダークモード切替 / Issue報告 / 著作権表示)を配置
- ヘッダーの保存/プレビュー/PDF出力/Markdown出力/削除をアイコン化
  (日本語 aria-label を維持)
- useDocumentForm に skipLoad を追加し save() を Promise<boolean> 化
- 関連ユニットテスト(careerDraft / useCareerDraftRestore / LoginPromptModal /
  useDocumentForm)と認証フロー E2E を追加・更新

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5800f18f-682a-48c0-97f6-357094875478

📥 Commits

Reviewing files that changed from the base of the PR and between 4da8ada and 2f550fe.

📒 Files selected for processing (5)
  • frontend/src/components/SidebarLayout.tsx
  • frontend/src/components/UserMenu.tsx
  • frontend/src/constants/messages.ts
  • frontend/src/utils/careerDraft.test.ts
  • frontend/src/utils/careerDraft.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • frontend/src/utils/careerDraft.ts
  • frontend/src/utils/careerDraft.test.ts
  • frontend/src/components/SidebarLayout.tsx
  • frontend/src/components/UserMenu.tsx
  • frontend/src/constants/messages.ts

📝 Walkthrough

Walkthrough

This PR implements unauthenticated trial access to the career form with draft persistence across OAuth flow. It adds a shared login-prompt modal system, refactors the sidebar layout to support both auth states, implements sessionStorage-backed draft utilities and a restore hook, updates form loading/save for anonymous mode, and adds E2E tests covering the full round-trip.

Changes

Unauthenticated Career Form with Draft Persistence

Layer / File(s) Summary
Login Prompt Modal System
frontend/src/components/auth/loginPromptContext.ts, frontend/src/components/auth/LoginPromptProvider.tsx, frontend/src/components/auth/LoginPromptModal.tsx, frontend/src/components/auth/LoginPromptModal.test.tsx, frontend/src/components/auth/LoginPromptModal.module.css
Context + provider expose requestLogin(); modal renders GitHub CTA, handles initiateGitHubLogin(origin) and loading state; tests validate rendering, click behavior, and close action.
Draft Persistence Utilities & Hook
frontend/src/utils/careerDraft.ts, frontend/src/utils/careerDraft.test.ts, frontend/src/hooks/career/useCareerDraftRestore.ts, frontend/src/hooks/career/useCareerDraftRestore.test.ts
SessionStorage-backed save/load/clear utilities with runtime validation; hook consumes persisted draft after login: auto-saves for new users or restores to form and notifies for existing users; tests cover edge cases and deferral while loading.
Document Form Enhancement
frontend/src/hooks/useDocumentForm.ts, frontend/src/hooks/useDocumentForm.test.ts
Add skipLoad option to skip initial load for anonymous mode; extend save(overrideForm?) to accept override payload and return boolean success; tests added for skipLoad and override save behavior.
SidebarLayout & UserMenu Refactor
frontend/src/components/AuthenticatedLayout.tsx, frontend/src/components/SidebarLayout.tsx, frontend/src/components/UserMenu.tsx, frontend/src/components/UserMenu.module.css
Removed old AuthenticatedLayout; added SidebarLayout supporting authenticated and guest UI; UserMenu now supports guest login trigger and conditional logout rendering; CSS for guest trigger added.
Career Form Unauthenticated Mode
frontend/src/components/forms/CareerResumeForm.tsx, frontend/src/components/forms/CareerResumeForm.module.css
CareerResumeForm accepts isAuthenticated; unauthenticated path validates minimally, persists career_draft to sessionStorage, and triggers login prompt for protected actions; restore toast and draft-restore wiring added; preview/export/save handlers gated by login prompt.
Routing and App Integration
frontend/src/App.tsx, frontend/src/pages/CareerPage.tsx, frontend/src/router/routes.tsx
Wrap AppRoutes with LoginPromptProvider; add CareerRoute that renders /career for both auth states (shows LoadingOverlay while authLoading) and forwards isAuthenticated to CareerPage.
Icons and Message Constants
frontend/src/components/icons/*.tsx, frontend/src/constants/messages.ts
Added GitHubMarkIcon, MarkdownDownloadIcon, PdfDownloadIcon, SaveIcon; updated EyeIcon to accept className; added AUTH_PROMPT_MESSAGES and extended UI_MESSAGES with resume/export/footer/menu labels.
E2E Tests and Styling
frontend/e2e/auth.spec.ts, frontend/src/App.module.css, frontend/src/styles.css
E2E tests updated/added to cover unauthenticated trial flow, draft storage and OAuth login-url assertions, authenticated draft round-trip; small CSS/comment spacing tweaks applied.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • yusuke0610/devforge#308: Modifies CareerResumeForm and draft-restore/submit messaging flows; overlaps in form refactoring and toast-based feedback.
  • yusuke0610/devforge#310: Also modifies CareerResumeForm save/preview/edit flows; likely overlaps at the same component and flows.
  • yusuke0610/devforge#288: Related sidebar GitHub-link behavior changes; both PRs adjust how the sidebar triggers GitHub連携 actions.

Poem

🐰 A small draft hops into session store,
Saved through OAuth, it worries no more,
Sidebar splits with a friendly clue,
Modal asks kindly, "ログインしますか?" — anew,
Resume restored — the rabbit cheers, encore!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main changes: introducing an unauthenticated trial input flow for the career form and refreshing the sidebar/header UI, which aligns with the extensive changes across routing, layout components, and form handling documented in the raw summary.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
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.

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

✨ 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 feat/career-anonymous-draft-login

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 and usage tips.

@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

Caution

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

⚠️ Outside diff range comments (2)
frontend/e2e/auth.spec.ts (1)

24-250: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use shared message constants in E2E selectors/assertions instead of inline literals.

The changed tests repeatedly hardcode UI labels/headings (e.g., ログイン, GitHub連携, ログインして保存しましょう, GitHubでログイン). This drifts from SSoT and makes tests brittle when message constants are updated centrally.

As per coding guidelines, "In frontend code, follow message management rules from .claude/rules/frontend/messages.md - message literals are forbidden and must use Single Source of Truth (SSoT)".

🤖 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 `@frontend/e2e/auth.spec.ts` around lines 24 - 250, Replace hardcoded UI text
in these tests (e.g., the headings/buttons/placeholders used in the tests "ルート /
..." and "ログイン画面" scenarios) with the project's shared message constants: import
the centralized messages module and use the appropriate constants in all
getByRole/getByPlaceholder assertions and clicks instead of inline literals like
"ログイン", "GitHub連携", "ブログ連携", "ログインして保存しましょう", "GitHubでログイン", "プレビュー", and "例: 山田
太郎". Update every occurrence inside the tests (references include the test
blocks with getByRole and getByPlaceholder calls and the sessionStorage key
"career_draft") to reference the SSoT constant names from the messages export so
tests follow the message management rules.

Source: Coding guidelines

frontend/src/components/UserMenu.tsx (1)

49-103: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace remaining inline menu labels with centralized messages.

Line 50 (ダークモード), Line 64 (ログアウト), and Line 101 fallback (Menu) are hardcoded strings in a frontend component. These should come from SSoT constants like the rest of the menu text.

As per coding guidelines, "In frontend code, follow message management rules from .claude/rules/frontend/messages.md - message literals are forbidden and must use Single Source of Truth (SSoT)".

🤖 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 `@frontend/src/components/UserMenu.tsx` around lines 49 - 103, Replace the
remaining hardcoded menu labels in UserMenu.tsx with SSoT message constants:
change "ダークモード" -> UI_MESSAGES.DARK_MODE where used in the theme toggle (near
onToggleTheme), "ログアウト" -> UI_MESSAGES.LOGOUT in the logout button (onLogout),
and the fallback "Menu" -> UI_MESSAGES.MENU (used with username || ...) in the
triggerName; ensure UI_MESSAGES is imported and add the corresponding keys to
the centralized messages file if they don't exist.

Source: Coding guidelines

🧹 Nitpick comments (2)
frontend/src/utils/careerDraft.ts (1)

28-39: 💤 Low value

Consider adding runtime validation for the loaded draft.

The type assertion as CareerFormState on line 33 casts the parsed JSON without validating its structure. If the stored data is malformed (e.g., from a previous version or manual tampering), downstream code may encounter unexpected runtime errors when accessing expected fields.

Consider adding a lightweight schema check or version marker to ensure the loaded draft matches the expected structure.

🛡️ Example validation approach
 export function loadCareerDraft(): CareerFormState | null {
   const raw = sessionStorage.getItem(CAREER_DRAFT_KEY);
   if (!raw) return null;
   try {
-    return JSON.parse(raw) as CareerFormState;
+    const parsed = JSON.parse(raw);
+    // Basic structural validation
+    if (!parsed || typeof parsed !== 'object' || typeof parsed.full_name !== 'string') {
+      console.warn("職務経歴書ドラフトの形式が不正です");
+      clearCareerDraft();
+      return null;
+    }
+    return parsed as CareerFormState;
   } catch (error) {
🤖 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 `@frontend/src/utils/careerDraft.ts` around lines 28 - 39, The loadCareerDraft
function currently casts parsed JSON to CareerFormState without validation,
which can cause runtime errors for malformed drafts; update loadCareerDraft to
perform a lightweight runtime schema check (or version marker) after JSON.parse
— verify required top-level fields and their types (e.g., expected strings,
arrays, nested objects) and any draft version key, call clearCareerDraft() and
return null if validation fails, otherwise return the parsed value typed as
CareerFormState; reference loadCareerDraft, CAREER_DRAFT_KEY, CareerFormState,
and clearCareerDraft when locating where to add the checks.
frontend/src/components/forms/CareerResumeForm.tsx (1)

99-125: ⚖️ Poor tradeoff

Consider debouncing the draft save effect to reduce sessionStorage writes.

The effect on lines 118-125 writes to sessionStorage on every form change when unauthenticated. For users entering large amounts of data quickly, this could result in many synchronous storage writes.

While the current implementation is functional and safe (with try/catch), consider debouncing the saveCareerDraft call to reduce write frequency (e.g., every 500ms after the last change) for improved performance.

🤖 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 `@frontend/src/components/forms/CareerResumeForm.tsx` around lines 99 - 125,
The effect currently writes to sessionStorage on every form change; debounce the
save to reduce synchronous writes by wrapping the saveCareerDraft call in a
debounced timer (e.g., 500ms) inside the existing useEffect that watches
isAuthenticated and form: when !isAuthenticated and form.full_name.trim()
start/refresh a setTimeout to call saveCareerDraft(form) after 500ms, clear the
timeout on subsequent runs and in the effect cleanup to avoid duplicate writes,
and still call clearCareerDraft immediately when full_name is empty; reference
saveCareerDraft, clearCareerDraft, useEffect, form, and isAuthenticated when
locating the change.
🤖 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 `@frontend/src/components/SidebarLayout.tsx`:
- Around line 96-181: The sidebar contains hardcoded UI strings in SidebarLayout
(e.g., NavLink children "職務経歴書", "GitHub連携", "ブログ連携", the button aria-label
"GitHub連携オプション", tooltip titles using AUTH_PROMPT_MESSAGES.LOGIN_REQUIRED_HINT,
the subbutton "連携実行", and the checkbox label "フォークしたリポジトリを含む"); replace each
literal with references to centralized message constants (import from your
messages/messages.ts or the agreed SSoT module) and use those constants in the
NavLink text, button aria-label, checkbox label, and title props (e.g., replace
literal strings used in NavLink, the Chevron button aria-label, the
triggerGitHubLink button text, and the checkbox text with
MESSAGE_KEYS.GITHUB_LINK, MESSAGE_KEYS.GITHUB_OPTIONS_LABEL,
MESSAGE_KEYS.INCLUDE_FORKS, etc.) so all UI text originates from the single
source of truth.

---

Outside diff comments:
In `@frontend/e2e/auth.spec.ts`:
- Around line 24-250: Replace hardcoded UI text in these tests (e.g., the
headings/buttons/placeholders used in the tests "ルート / ..." and "ログイン画面"
scenarios) with the project's shared message constants: import the centralized
messages module and use the appropriate constants in all
getByRole/getByPlaceholder assertions and clicks instead of inline literals like
"ログイン", "GitHub連携", "ブログ連携", "ログインして保存しましょう", "GitHubでログイン", "プレビュー", and "例: 山田
太郎". Update every occurrence inside the tests (references include the test
blocks with getByRole and getByPlaceholder calls and the sessionStorage key
"career_draft") to reference the SSoT constant names from the messages export so
tests follow the message management rules.

In `@frontend/src/components/UserMenu.tsx`:
- Around line 49-103: Replace the remaining hardcoded menu labels in
UserMenu.tsx with SSoT message constants: change "ダークモード" ->
UI_MESSAGES.DARK_MODE where used in the theme toggle (near onToggleTheme),
"ログアウト" -> UI_MESSAGES.LOGOUT in the logout button (onLogout), and the fallback
"Menu" -> UI_MESSAGES.MENU (used with username || ...) in the triggerName;
ensure UI_MESSAGES is imported and add the corresponding keys to the centralized
messages file if they don't exist.

---

Nitpick comments:
In `@frontend/src/components/forms/CareerResumeForm.tsx`:
- Around line 99-125: The effect currently writes to sessionStorage on every
form change; debounce the save to reduce synchronous writes by wrapping the
saveCareerDraft call in a debounced timer (e.g., 500ms) inside the existing
useEffect that watches isAuthenticated and form: when !isAuthenticated and
form.full_name.trim() start/refresh a setTimeout to call saveCareerDraft(form)
after 500ms, clear the timeout on subsequent runs and in the effect cleanup to
avoid duplicate writes, and still call clearCareerDraft immediately when
full_name is empty; reference saveCareerDraft, clearCareerDraft, useEffect,
form, and isAuthenticated when locating the change.

In `@frontend/src/utils/careerDraft.ts`:
- Around line 28-39: The loadCareerDraft function currently casts parsed JSON to
CareerFormState without validation, which can cause runtime errors for malformed
drafts; update loadCareerDraft to perform a lightweight runtime schema check (or
version marker) after JSON.parse — verify required top-level fields and their
types (e.g., expected strings, arrays, nested objects) and any draft version
key, call clearCareerDraft() and return null if validation fails, otherwise
return the parsed value typed as CareerFormState; reference loadCareerDraft,
CAREER_DRAFT_KEY, CareerFormState, and clearCareerDraft when locating where to
add the checks.
🪄 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 Plus

Run ID: 587a2ed8-8c7b-4be4-9c8d-0b4133029545

📥 Commits

Reviewing files that changed from the base of the PR and between 73dd18a and 4da8ada.

📒 Files selected for processing (29)
  • frontend/e2e/auth.spec.ts
  • frontend/src/App.module.css
  • frontend/src/App.tsx
  • frontend/src/components/AuthenticatedLayout.tsx
  • frontend/src/components/SidebarLayout.tsx
  • frontend/src/components/UserMenu.module.css
  • frontend/src/components/UserMenu.tsx
  • frontend/src/components/auth/LoginPromptModal.module.css
  • frontend/src/components/auth/LoginPromptModal.test.tsx
  • frontend/src/components/auth/LoginPromptModal.tsx
  • frontend/src/components/auth/LoginPromptProvider.tsx
  • frontend/src/components/auth/loginPromptContext.ts
  • frontend/src/components/forms/CareerResumeForm.module.css
  • frontend/src/components/forms/CareerResumeForm.tsx
  • frontend/src/components/icons/EyeIcon.tsx
  • frontend/src/components/icons/GitHubMarkIcon.tsx
  • frontend/src/components/icons/MarkdownDownloadIcon.tsx
  • frontend/src/components/icons/PdfDownloadIcon.tsx
  • frontend/src/components/icons/SaveIcon.tsx
  • frontend/src/constants/messages.ts
  • frontend/src/hooks/career/useCareerDraftRestore.test.ts
  • frontend/src/hooks/career/useCareerDraftRestore.ts
  • frontend/src/hooks/useDocumentForm.test.ts
  • frontend/src/hooks/useDocumentForm.ts
  • frontend/src/pages/CareerPage.tsx
  • frontend/src/router/routes.tsx
  • frontend/src/styles.css
  • frontend/src/utils/careerDraft.test.ts
  • frontend/src/utils/careerDraft.ts
💤 Files with no reviewable changes (1)
  • frontend/src/components/AuthenticatedLayout.tsx

Comment thread frontend/src/components/SidebarLayout.tsx
- SidebarLayout / UserMenu のハードコード UI 文言を UI_MESSAGES に集約
- loadCareerDraft にドラフト形式の軽量バリデーションを追加し、
  旧フォーマット/破損データは破棄して null を返す(復元時のクラッシュ防止)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant