[FEAT] Auth API 연동 및 로그인/회원가입 UI 개편#10
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 Walkthrough개요이 PR은 프로젝트 전역에 임포트 별칭 인프라( 변경 사항임포트 별칭 인프라 및 도구 설정
인증 및 사용자 모델 리팩토링
인증 페이지 및 UI 컴포넌트
네비게이션 및 레이아웃 재구성
지원 변경 사항
예상 코드 리뷰 노력🎯 4 (복잡함) | ⏱️ ~60분 관련 가능성이 있는 PR
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/pages/AdminStats.jsx (1)
20-27:⚠️ Potential issue | 🟠 Major | ⚡ Quick win어드민 토큰을
localStorage에 저장하면 XSS 공격 시 자격증명이 노출됩니다
adminToken을localStorage에 평문으로 저장하면, 앱 내 XSS 취약점이 존재할 경우 공격자가 스크립트로 토큰을 그대로 읽어갈 수 있습니다. 어드민 권한이 붙어있는 토큰이므로 탈취 시 피해 범위가 큽니다.sessionStorage또는 인메모리 상태(React state)만 사용하고 페이지 새로고침 시 재인증하도록 변경하는 것을 권장합니다.🔒 개선 제안: 인메모리 상태로 전환
- const [token, setToken] = useState(localStorage.getItem("adminToken") || ""); + const [token, setToken] = useState(""); ... const load = useCallback(async () => { if (!token) return; - localStorage.setItem("adminToken", token); setLoading(true);🤖 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/pages/AdminStats.jsx` around lines 20 - 27, The code persists the adminToken in localStorage (via localStorage.getItem("adminToken") and localStorage.setItem("adminToken", token")) which risks token theft via XSS; change AdminStats.jsx to keep the token in-memory only by initializing the token useState without reading from localStorage and remove the localStorage.setItem call from the load function, require re-authentication on page reload, and ensure any existing adminToken key is cleared on component mount (use effect) so tokens are not stored in localStorage; continue to use setToken and the load callback to operate on the in-memory token state.src/pages/Matches.jsx (1)
17-33:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
role이'seller'인 사용자에게companyId를buyerInput으로 자동 채우면 잘못된 매칭 요청이 발생합니다.현재 코드는
role과 관계없이companyId를 buyer ID 입력값의 기본값으로 사용합니다.role이'seller'인 사용자가/matches로 이동하면 자신의 sellercompanyId가 buyer ID 필드에 채워져, 유효한 ObjectId 형식이므로 검증을 통과한 뒤 의미 없는 매칭 결과를 반환하게 됩니다.또한 변수명
storedBuyerId는companyId를 가리키는데, 이는 buyer에만 해당하는 개념으로 오해될 수 있습니다.🐛 역할 기반 분기 제안
- const { companyId: storedBuyerId } = useAuthStore(); + const { companyId, role } = useAuthStore(); + const storedBuyerId = role === 'buyer' ? companyId : '';🤖 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/pages/Matches.jsx` around lines 17 - 33, The code incorrectly pre-fills buyerInput with companyId for all users; update the logic that derives storedBuyerId from useAuthStore() (rename it to storedCompanyId or similar) and only use that companyId to initialize buyerInput, buyerId in submittedParams, or companyFilter when the current user's role (from useAuthStore().role) indicates they are a buyer (i.e., role !== 'seller' or role === 'buyer'); adjust the initializers for buyerInput, submittedParams, and the stored variable name (e.g., storedCompanyId) so sellers are not auto-filled and Ensure isObjectId checks and limit defaults remain unchanged.
🧹 Nitpick comments (11)
src/components/AuthDropdown.jsx (1)
25-41: ⚡ Quick win트리거 버튼에
aria-expanded가 없고 Escape 키가 처리되지 않습니다
aria-expanded가 없으면 스크린 리더 사용자가 드롭다운 열림 상태를 알 수 없고, Escape 키로 닫히지 않아 키보드 사용성이 저하됩니다.♿ 수정 제안
useEffect(() => { function onOutside(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); } document.addEventListener("mousedown", onOutside); return () => document.removeEventListener("mousedown", onOutside); }, []); +useEffect(() => { + function onKeyDown(e) { + if (e.key === "Escape") setOpen(false); + } + if (open) document.addEventListener("keydown", onKeyDown); + return () => document.removeEventListener("keydown", onKeyDown); +}, [open]); ... <button type="button" + aria-haspopup="listbox" + aria-expanded={open} onClick={() => setOpen((v) => !v)} style={{ ... }} >🤖 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/AuthDropdown.jsx` around lines 25 - 41, The AuthDropdown trigger button lacks accessibility attributes and Escape handling: update the button in AuthDropdown (the element using onClick={() => setOpen((v) => !v)} and reading the open state) to include aria-expanded={open} and an appropriate aria-controls referencing the dropdown panel id; add a keydown listener (or onKeyDown on the button or document when the dropdown is open) that closes the menu by calling setOpen(false) when Escape is pressed, and ensure any document-level listener is cleaned up when the component unmounts or when open becomes false; keep the existing visual behavior and only add these ARIA and keyboard handlers to the AuthDropdown component.src/lib/i18n/I18nProvider.jsx (1)
23-35: ⚡ Quick win
setLang을 연속 호출하면 이전 transition이 취소되지 않아 중간 언어가 순간 노출됩니다첫 번째
setTimeout이 150ms 뒤에opacity=1을 복원하기 직전에 두 번째setLang이 호출된 경우, 첫 번째 timeout이setLangState(l1)을 실행하고 잠시 l1 화면이 보인 뒤에 두 번째 timeout이 l2로 전환됩니다.timeout ID를useRef로 추적하면 새로운 호출 시 이전 transition을 깔끔하게 취소할 수 있습니다.♻️ 수정 제안
export function I18nProvider({ children }) { const initial = normalize(localStorage.getItem("lang") || navigator.language); const [lang, setLangState] = useState(initial); + const transitionRef = useRef(null); + const setLang = (v) => { const l = normalize(v); + if (transitionRef.current !== null) { + clearTimeout(transitionRef.current); + transitionRef.current = null; + document.body.style.opacity = "1"; + } document.body.style.transition = "opacity 0.15s ease"; document.body.style.opacity = "0"; - setTimeout(() => { + transitionRef.current = setTimeout(() => { + transitionRef.current = null; localStorage.setItem("lang", l); setLangState(l); window.requestAnimationFrame(() => window.requestAnimationFrame(() => { document.body.style.opacity = "1"; }), ); }, 150); };
useRef는 이미AuthDropdown에 쓰인 패턴이므로I18nProvider상단의import구문에 추가가 필요합니다.-import React, { createContext, useContext, useMemo, useState } from "react"; +import React, { createContext, useContext, useMemo, useRef, useState } from "react";🤖 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/lib/i18n/I18nProvider.jsx` around lines 23 - 35, The setLang function can leave a previous timeout running when called repeatedly; track the timeout ID with a ref (useRef) at the top of I18nProvider, and on each setLang call clearTimeout(previousRef.current) before creating a new setTimeout so the previous transition/state change is cancelled; store the new timeout ID in the ref, then proceed to set localStorage.setItem("lang", l), call setLangState(l), and run the requestAnimationFrame sequence as before. Ensure you import useRef and initialize the ref (e.g., const langTimeoutRef = useRef(null)) so you can clear and replace langTimeoutRef.current.src/components/LanguageSwitcher.jsx (1)
27-28: ⚡ Quick win
onMouseEnter/onMouseLeave로 DOM 스타일을 직접 변경하는 방식을 피하세요.React의 재조정(reconciliation) 과정에서 이 명령형 스타일 변경이 덮어쓰여질 수 있으며, 프로젝트에서 사용 중인 Tailwind CSS의 관습과도 맞지 않습니다. CSS 또는 Tailwind hover 유틸리티로 대체하면 더 안전하고 선언적입니다.
♻️ Tailwind hover 클래스로 교체 제안
style={{ width: 40, height: 40, display: "flex", alignItems: "center", justifyContent: "center", background: "transparent", border: "none", borderRadius: 8, cursor: "pointer", color: "#080616", - transition: "background 0.15s", }} - onMouseEnter={(e) => (e.currentTarget.style.background = "#e8edf5")} - onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")} + className="hover:bg-[`#e8edf5`] transition-colors duration-150"🤖 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/LanguageSwitcher.jsx` around lines 27 - 28, The component is mutating DOM styles imperatively via the onMouseEnter/onMouseLeave handlers (e.currentTarget.style.background = ...) which conflicts with React reconciliation and Tailwind conventions; replace these handlers by removing the inline style mutations and instead apply Tailwind hover/conditional class names on the element (e.g., add a hover:bg-... utility or compute a className using state/clsx inside the LanguageSwitcher component) so hover styling is declarative and handled via CSS rather than direct DOM style changes.src/pages/CompanyList.jsx (1)
3-7: 💤 Low valueLGTM — 별칭 임포트 정상 적용
한 가지 사소한 스타일 불일치: 이 파일은
@/components/Button.jsx와 같이.jsx확장자를 명시하지만,SchedulePage.jsx는 동일 컴포넌트를@/components/Button으로 임포트합니다. Vite는 자동으로 확장자를 해석하므로 기능 문제는 없지만, 코드베이스 전반에 걸쳐 확장자 포함 여부를 통일하면 가독성이 향상됩니다.🤖 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/pages/CompanyList.jsx` around lines 3 - 7, Imports in CompanyList.jsx are inconsistent with the rest of the codebase (e.g., SchedulePage.jsx) because some components include the .jsx extension (CompanyCard, Modal, Button, Badge) while others omit it; pick the project's preferred style and make imports consistent — for example, remove the .jsx extension from the component imports in CompanyList.jsx (useCompanies, CompanyCard, Modal, Button, Badge) so they match imports like "@/components/Button" used elsewhere, or conversely add .jsx everywhere; update the import lines for CompanyCard, Modal, Button, and Badge to the chosen canonical form.src/pages/LandingPage.jsx (1)
14-19: 💤 Low value헤더 높이 68px을 하드코딩하면 유지보수에 취약합니다.
calc(100vh - 68px)의68px은.header .inner의padding: 14px 24px에서 도출된 값입니다. 헤더 패딩이 변경되면 이 값도 수동으로 수정해야 합니다. CSS 변수로 분리하는 것이 좋습니다.♻️ 제안 수정
src/index.css의:root블록에 추가:+ --header-height: 68px;
LandingPage.jsx:- minHeight: "calc(100vh - 68px)", + minHeight: "calc(100vh - var(--header-height))",🤖 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/pages/LandingPage.jsx` around lines 14 - 19, The component hardcodes the header height as minHeight: "calc(100vh - 68px)" in LandingPage.jsx which will break if .header .inner padding changes; define a CSS variable (e.g., --header-height) in :root (or wherever global CSS is defined) derived from the header styles and replace the hardcoded value by using calc(100vh - var(--header-height)) in the style prop on the LandingPage container so the layout adapts when .header .inner padding/height changes.src/hooks/useAuth.js (3)
16-16: ⚡ Quick win토스트 메시지가 한국어로 하드코딩되어 PR의 i18n 정책과 어긋납니다.
이 PR은 랜딩/로그인/회원가입 페이지 전반에 i18n(한/영)을 도입하지만, 정작 인증 결과 토스트(
"로그인됐습니다.","로그인에 실패했습니다.","회원가입이 완료됐습니다.","회원가입에 실패했습니다.")는 영어 로케일에서도 한국어 그대로 노출됩니다. 다른 UI 텍스트와 동일하게useI18n사전(auth_login_success,auth_login_failed,auth_register_success,auth_register_failed등)으로 옮겨 일관성을 맞추는 편이 좋습니다.Also applies to: 26-26, 44-44, 54-54
🤖 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/hooks/useAuth.js` at line 16, Replace hardcoded Korean toast strings in useAuth.js with i18n lookups using the existing useI18n hook: import/use the hook in the module (if not already) and replace toast.success("로그인됐습니다.") and the other toast calls (toast.error for login/register success/failure at the locations corresponding to the failed/success branches) with calls that pass translated keys such as i18n.t('auth_login_success'), i18n.t('auth_login_failed'), i18n.t('auth_register_success'), i18n.t('auth_register_failed') so the messages follow the app's i18n dictionary; ensure you reference the same keys used elsewhere and keep the toast invocation signatures unchanged.
9-9: 💤 Low valueZustand 셀렉터 패턴은 선택사항입니다(액션 함수의 경우).
현재 코드의
setAuth는 Zustand의 액션 함수로, 참조가 안정적이므로 스토어 상태 변경 시 리렌더를 유발하지 않습니다. 따라서 현재 구현은 이미 최적화되어 있습니다.다만 Zustand 5.0.12 환경에서 선호하는 패턴은 셀렉터 사용이므로, 코드 스타일 일관성을 위해 다음과 같이 변경할 수 있습니다:
♻️ 제안 패치
- const { setAuth } = useAuthStore(); + const setAuth = useAuthStore((s) => s.setAuth);🤖 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/hooks/useAuth.js` at line 9, 현재 useAuth.js에서 액션을 객체 비구조화로 가져오는 대신 스타일 일관성을 위해 Zustand 셀렉터 패턴을 사용하세요: locate the useAuthStore invocation (currently "const { setAuth } = useAuthStore();") and replace it so you select only the action via a selector (e.g. const setAuth = useAuthStore(state => state.setAuth)); this ensures the component references the stable action directly and follows the preferred selector pattern without changing behavior.
19-28: ⚡ Quick win에러 핸들러의 중복된 분기 로직을 헬퍼로 추출하세요. 단,
message.message이중 중첩은 제거하고 단일 레벨로 수정해야 합니다.
useLogin.onError(19–28)와useRegister.onError(47–56)의 분기 로직이 동일하여 중복됩니다. 하지만 현재 코드의err?.response?.data?.message?.message는src/apis/http.ts의 인터셉터(20행)에서 추출하는error.response?.data?.message구조와 맞지 않습니다. 또한ApiError타입(src/apis/types.ts)에서message: string으로 정의되어 있어, 이중 중첩은 불필요한 방어 코딩입니다.헬퍼 함수로 추출할 때 다음과 같이 단일 레벨로 수정하세요:
♻️ 수정 패치
+function showAuthError(err, fallback) { + const messages = err?.response?.data?.message; + if (Array.isArray(messages) && messages.length > 0) { + toast.error(messages[0]); + } else if (typeof messages === "string" && messages) { + toast.error(messages); + } else { + toast.error(fallback); + } +} ... - onError: (err) => { - const messages = err?.response?.data?.message?.message; - if (Array.isArray(messages) && messages.length > 0) { - toast.error(messages[0]); - } else if (typeof messages === "string" && messages) { - toast.error(messages); - } else { - toast.error("로그인에 실패했습니다."); - } - }, + onError: (err) => showAuthError(err, "로그인에 실패했습니다."),🤖 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/hooks/useAuth.js` around lines 19 - 28, Extract the duplicated error-display logic into a helper (e.g., handleApiErrorToast) and update both useLogin.onError and useRegister.onError to call it; the helper should read the single-level message at err?.response?.data?.message (remove the erroneous message.message nesting), handle Array vs string vs missing value by calling toast.error with messages[0], the string, or a default "로그인에 실패했습니다."/appropriate fallback, and live alongside or exported from the hooks (or a shared utils file) so both hooks reference the same function; ensure the helper aligns with ApiError's message: string and the interceptor that sets error.response?.data?.message.src/pages/RegisterPage.jsx (1)
149-176: ⚡ Quick win초기 폼 객체가 두 곳에서 중복 선언되어 동기화 위험이 있습니다.
useState의 초기값(149–159)과handleTypeChange내 리셋 객체(165–175)가 동일한 키 집합을 손으로 나열하고 있어, 추후 필드를 추가/제거할 때 한쪽만 갱신되면 타입 전환 시 잔존 값이 남거나 폼이 망가지기 쉽습니다. 상수로 추출해 양쪽에서 공유하는 것을 권장합니다.♻️ 제안 패치
+const INITIAL_FORM = { + companyName: "", + representativeName: "", + email: "", + phone: "", + password: "", + keywords: "", + companyIntro: "", + productIntro: "", + websiteUrl: "", +}; + export default function RegisterPage() { ... - const [form, setForm] = useState({ - companyName: "", - ... - websiteUrl: "", - }); + const [form, setForm] = useState(INITIAL_FORM); ... const handleTypeChange = (type) => { setCompanyType(type); - setForm({ - companyName: "", - ... - websiteUrl: "", - }); + setForm(INITIAL_FORM); };🤖 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/pages/RegisterPage.jsx` around lines 149 - 176, Extract the initial form object into a shared constant (e.g., INITIAL_FORM) and use it for both the useState initialization and in handleTypeChange reset instead of duplicating the literal object; update the useState call that currently sets form to the inline object and replace the object in handleTypeChange with a reference to INITIAL_FORM so add/remove fields only need to be changed in one place (refer to symbols: useState, form, setForm, handleTypeChange, set).src/App.jsx (1)
14-31: 💤 Low value
useEffect의존성과 레이아웃 분기 둘 다location.pathname로 좁힐 수 있습니다.
- 14–16:
useEffect의 deps가location객체 전체로 잡혀 있어 같은 경로 안에서state/key만 바뀌어도track("page_view", ...)가 추가로 발화합니다.pathname이 실제 추적 대상이므로[location.pathname]으로 좁히는 편이 안전합니다.- 26–30: 레이아웃을 비활성화할 경로 목록(
/,/login,/register)을 인라인 배열로 직접 비교하고 있어 라우트가 늘어나면 누락되기 쉽습니다. 모듈 상단의 상수(예:FULL_BLEED_ROUTES)로 추출하면 라우터 정의와 함께 관리하기 쉽습니다.♻️ 제안 패치
+const FULL_BLEED_ROUTES = ["/", "/login", "/register"]; + export default function App() { const { t } = useI18n(); const location = useLocation(); useEffect(() => { track("page_view", { path: location.pathname }); - }, [location]); + }, [location.pathname]); ... - <main - id="main-content" - className={ - ["/", "/login", "/register"].includes(location.pathname) - ? "" - : "container" - } - > + <main + id="main-content" + className={FULL_BLEED_ROUTES.includes(location.pathname) ? "" : "container"} + >🤖 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.jsx` around lines 14 - 31, Change the useEffect to depend only on location.pathname (so track("page_view", { path: location.pathname }) runs only when the path actually changes) by replacing the deps array [location] with [location.pathname]; also extract the inline route array used in the main element className into a module-level constant (e.g. FULL_BLEED_ROUTES) and use FULL_BLEED_ROUTES.includes(location.pathname) instead of the inline ["/", "/login", "/register"]. These changes touch the useEffect/track call and the main element className logic in App.jsx.src/pages/LoginPage.jsx (1)
11-134: ⚡ Quick win
HeroText컴포넌트가RegisterPage.jsx와 거의 동일하게 중복되어 있습니다.
src/pages/LoginPage.jsx의HeroText(11–134)와src/pages/RegisterPage.jsx의HeroText(21–142)는 사실상 동일한 마크업/스타일을 갖고 있어 한쪽만 변경되면 두 페이지의 히어로 표현이 어긋날 위험이 큽니다. 공통 컴포넌트(src/components/AuthHero.jsx등)로 추출하고 두 페이지에서 import 하여 재사용하시길 권장합니다.🤖 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/pages/LoginPage.jsx` around lines 11 - 134, The HeroText component is duplicated between LoginPage and RegisterPage; extract it into a single reusable component (e.g., AuthHero) and import it from both pages. Create a new component named AuthHero (or export the HeroText as default) that contains the current markup/usage of useI18n() and all styles, export it, then replace the HeroText definitions in both LoginPage.jsx and RegisterPage.jsx with an import of AuthHero; ensure props or internal useI18n remain consistent so the translations (t) and conditional prefix/suffix logic behave the same.
🤖 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/apis/modules/auth.ts`:
- Around line 19-27: The DTO field names in RegisterBuyerDto are incorrect:
change sellerName → buyerName and sellerIntroduction → buyerIntroduction to
match the backend contract and what authApi.registerBuyer() expects (verify
RegisterPage.jsx handleSubmit mapping and useAuth.js passthrough). Also
reconcile the industry/industries type mismatch between RegisterSellerDto and
RegisterBuyerDto by making the same shape the backend expects (either change
RegisterSellerDto.industry to industries: string[] or
RegisterBuyerDto.industries to industry: string, depending on API contract) and
update all usages of those types accordingly (look for RegisterBuyerDto,
RegisterSellerDto, sellerName, sellerIntroduction, buyerName, buyerIntroduction,
industry, industries).
In `@src/components/AuthInput.jsx`:
- Around line 79-93: The password visibility toggle button rendered when
isPassword is true lacks an accessible label; update the button in AuthInput.jsx
(the element using onClick={() => setShowPw((v) => !v)}) to include an
aria-label that reflects the current state (e.g., "Show password" when password
is hidden and "Hide password" when visible) and consider adding aria-pressed or
aria-expanded if appropriate for screen readers so the control's purpose and
state are announced.
- Around line 40-69: The label span in the AuthInput component is not associated
with the input, so replace the span with a proper <label> element (or keep it
but add htmlFor) and ensure the <input> has a stable unique id: use React's
useId inside AuthInput to generate id (or accept an id prop and fall back to
useId) and set that id on the input and set htmlFor on the label/span; update
onFocus/onBlur handlers and styling only — keep existing props (isPassword,
showPw, type, placeholder, value, onChange, onKeyDown) unchanged.
In `@src/components/LanguageSwitcher.jsx`:
- Line 13: The aria-label "Toggle language" in the LanguageSwitcher.jsx button
is static; update the LanguageSwitcher component to compute a dynamic accessible
name that includes either the current language or the target language (e.g.,
"Current language: English, switch to Korean" or "Switch language to Korean")
and assign that string to the button's aria-label so screen reader users know
current state and action; locate the toggle handler (e.g., toggleLanguage) and
the locale/currentLanguage state or i18n.getLocale call inside
LanguageSwitcher.jsx and build the aria-label from those values, then replace
the static aria-label="Toggle language" with the computed label.
In `@src/hooks/useAuth.js`:
- Around line 13-18: Responses from useLogin and useRegister destructure
different shapes (one uses res.user, the other res) causing undefined auth
values; confirm axios interceptor (http.ts:17) returns res.data and then make
both hooks normalize the same shape before calling setAuth (either extract {
_id, name, type } from res.data or from res.data.user consistently), update
useLogin and useRegister to use that same destructuring, and ensure setAuth is
only called with defined values.
In `@src/lib/i18n/dict.js`:
- Line 539: The ko dictionary entry for register_company_type_label currently
contains the English "Company Type"; update the ko translation for the key
register_company_type_label inside the ko object in dict.js to the proper Korean
text (e.g., "회사 유형") so the UI shows Korean instead of English.
In `@src/pages/LandingPage.jsx`:
- Around line 91-168: Replace the visual hero paragraphs with a semantic
heading: change the primary hero element rendering t("landing_hero_line1") from
a <p> to an <h1> (preserving the inline styles), and wrap the prefix, highlight
box, and suffix (t("landing_hero_prefix"), t("landing_hero_highlight"),
t("landing_hero_suffix")) inside that same <h1> so they form one accessible
heading; keep the highlight box markup (the blue background div) and use a
<span> inside it for the highlighted text (instead of a separate <p>) to
preserve semantics and styles, and ensure the smaller description below remains
a paragraph.
- Around line 173-176: The i18n key nav_partner_matching is used in both
Header.jsx (linking to /partners) and LandingPage.jsx's SquareButton (linking to
/matches), causing inconsistent destinations; either change the CTA's navigate
target to /partners in LandingPage.jsx (SquareButton onClick
navigate("/partners")) to match Header.jsx, or create a new i18n key (e.g.,
landing_partner_matching_cta) in your dict and use that key in LandingPage.jsx
while keeping its /matches route—update the translation files accordingly and
ensure SquareButton and Header.jsx reference the intended keys.
In `@src/pages/LoginPage.jsx`:
- Around line 143-149: The Enter key path currently calls handleSubmit without
checking isPending or validating inputs, allowing duplicate submissions via
handleKeyDown; update handleKeyDown (and/or handleSubmit) to first check and
return early if isPending is true or if email/password are empty/invalid before
calling login, and ensure the same guards are applied where PillButton uses
disabled={isPending} so useLogin cannot be invoked multiple times while pending.
In `@src/pages/RegisterPage.jsx`:
- Around line 199-201: The Enter-key handler handleKeyDown currently calls
handleSubmit unguarded, allowing repeated submissions; update handleKeyDown to
first check isPending and return early if true, and also validate required form
fields (e.g., email, password, name or the component's required inputs) for
non-empty/format before calling handleSubmit so empty/invalid data isn't sent to
useRegister; keep the same call to handleSubmit when checks pass. Ensure this
aligns with the existing PillButton disabled={isPending} behavior so both
keyboard and click paths share the same guards.
In `@src/router.jsx`:
- Line 7: Remove the unused import PartnerSearch from the top of the module:
locate the import statement "import PartnerSearch from
'@/pages/PartnerSearch.jsx';" and delete it (or replace with a used symbol if
you intended to add a Route). Ensure no references to PartnerSearch remain in
the router (e.g., Route definitions) and run the linter to confirm the warning
is resolved.
In `@src/stores/authStore.js`:
- Around line 7-14: When handling 401s in the HTTP interceptor in
src/apis/http.ts, call the auth store's clearAuth() before redirecting so
persistent isLoggedIn is cleared; import or access the authStore (the clearAuth
function from the store that defines clearAuth/setAuth) and invoke clearAuth()
in the same branch where you check status === 401 && window.location.pathname
!== "/login", then perform the existing window.location.href = "/login"
redirect.
---
Outside diff comments:
In `@src/pages/AdminStats.jsx`:
- Around line 20-27: The code persists the adminToken in localStorage (via
localStorage.getItem("adminToken") and localStorage.setItem("adminToken",
token")) which risks token theft via XSS; change AdminStats.jsx to keep the
token in-memory only by initializing the token useState without reading from
localStorage and remove the localStorage.setItem call from the load function,
require re-authentication on page reload, and ensure any existing adminToken key
is cleared on component mount (use effect) so tokens are not stored in
localStorage; continue to use setToken and the load callback to operate on the
in-memory token state.
In `@src/pages/Matches.jsx`:
- Around line 17-33: The code incorrectly pre-fills buyerInput with companyId
for all users; update the logic that derives storedBuyerId from useAuthStore()
(rename it to storedCompanyId or similar) and only use that companyId to
initialize buyerInput, buyerId in submittedParams, or companyFilter when the
current user's role (from useAuthStore().role) indicates they are a buyer (i.e.,
role !== 'seller' or role === 'buyer'); adjust the initializers for buyerInput,
submittedParams, and the stored variable name (e.g., storedCompanyId) so sellers
are not auto-filled and Ensure isObjectId checks and limit defaults remain
unchanged.
---
Nitpick comments:
In `@src/App.jsx`:
- Around line 14-31: Change the useEffect to depend only on location.pathname
(so track("page_view", { path: location.pathname }) runs only when the path
actually changes) by replacing the deps array [location] with
[location.pathname]; also extract the inline route array used in the main
element className into a module-level constant (e.g. FULL_BLEED_ROUTES) and use
FULL_BLEED_ROUTES.includes(location.pathname) instead of the inline ["/",
"/login", "/register"]. These changes touch the useEffect/track call and the
main element className logic in App.jsx.
In `@src/components/AuthDropdown.jsx`:
- Around line 25-41: The AuthDropdown trigger button lacks accessibility
attributes and Escape handling: update the button in AuthDropdown (the element
using onClick={() => setOpen((v) => !v)} and reading the open state) to include
aria-expanded={open} and an appropriate aria-controls referencing the dropdown
panel id; add a keydown listener (or onKeyDown on the button or document when
the dropdown is open) that closes the menu by calling setOpen(false) when Escape
is pressed, and ensure any document-level listener is cleaned up when the
component unmounts or when open becomes false; keep the existing visual behavior
and only add these ARIA and keyboard handlers to the AuthDropdown component.
In `@src/components/LanguageSwitcher.jsx`:
- Around line 27-28: The component is mutating DOM styles imperatively via the
onMouseEnter/onMouseLeave handlers (e.currentTarget.style.background = ...)
which conflicts with React reconciliation and Tailwind conventions; replace
these handlers by removing the inline style mutations and instead apply Tailwind
hover/conditional class names on the element (e.g., add a hover:bg-... utility
or compute a className using state/clsx inside the LanguageSwitcher component)
so hover styling is declarative and handled via CSS rather than direct DOM style
changes.
In `@src/hooks/useAuth.js`:
- Line 16: Replace hardcoded Korean toast strings in useAuth.js with i18n
lookups using the existing useI18n hook: import/use the hook in the module (if
not already) and replace toast.success("로그인됐습니다.") and the other toast calls
(toast.error for login/register success/failure at the locations corresponding
to the failed/success branches) with calls that pass translated keys such as
i18n.t('auth_login_success'), i18n.t('auth_login_failed'),
i18n.t('auth_register_success'), i18n.t('auth_register_failed') so the messages
follow the app's i18n dictionary; ensure you reference the same keys used
elsewhere and keep the toast invocation signatures unchanged.
- Line 9: 현재 useAuth.js에서 액션을 객체 비구조화로 가져오는 대신 스타일 일관성을 위해 Zustand 셀렉터 패턴을
사용하세요: locate the useAuthStore invocation (currently "const { setAuth } =
useAuthStore();") and replace it so you select only the action via a selector
(e.g. const setAuth = useAuthStore(state => state.setAuth)); this ensures the
component references the stable action directly and follows the preferred
selector pattern without changing behavior.
- Around line 19-28: Extract the duplicated error-display logic into a helper
(e.g., handleApiErrorToast) and update both useLogin.onError and
useRegister.onError to call it; the helper should read the single-level message
at err?.response?.data?.message (remove the erroneous message.message nesting),
handle Array vs string vs missing value by calling toast.error with messages[0],
the string, or a default "로그인에 실패했습니다."/appropriate fallback, and live alongside
or exported from the hooks (or a shared utils file) so both hooks reference the
same function; ensure the helper aligns with ApiError's message: string and the
interceptor that sets error.response?.data?.message.
In `@src/lib/i18n/I18nProvider.jsx`:
- Around line 23-35: The setLang function can leave a previous timeout running
when called repeatedly; track the timeout ID with a ref (useRef) at the top of
I18nProvider, and on each setLang call clearTimeout(previousRef.current) before
creating a new setTimeout so the previous transition/state change is cancelled;
store the new timeout ID in the ref, then proceed to set
localStorage.setItem("lang", l), call setLangState(l), and run the
requestAnimationFrame sequence as before. Ensure you import useRef and
initialize the ref (e.g., const langTimeoutRef = useRef(null)) so you can clear
and replace langTimeoutRef.current.
In `@src/pages/CompanyList.jsx`:
- Around line 3-7: Imports in CompanyList.jsx are inconsistent with the rest of
the codebase (e.g., SchedulePage.jsx) because some components include the .jsx
extension (CompanyCard, Modal, Button, Badge) while others omit it; pick the
project's preferred style and make imports consistent — for example, remove the
.jsx extension from the component imports in CompanyList.jsx (useCompanies,
CompanyCard, Modal, Button, Badge) so they match imports like
"@/components/Button" used elsewhere, or conversely add .jsx everywhere; update
the import lines for CompanyCard, Modal, Button, and Badge to the chosen
canonical form.
In `@src/pages/LandingPage.jsx`:
- Around line 14-19: The component hardcodes the header height as minHeight:
"calc(100vh - 68px)" in LandingPage.jsx which will break if .header .inner
padding changes; define a CSS variable (e.g., --header-height) in :root (or
wherever global CSS is defined) derived from the header styles and replace the
hardcoded value by using calc(100vh - var(--header-height)) in the style prop on
the LandingPage container so the layout adapts when .header .inner
padding/height changes.
In `@src/pages/LoginPage.jsx`:
- Around line 11-134: The HeroText component is duplicated between LoginPage and
RegisterPage; extract it into a single reusable component (e.g., AuthHero) and
import it from both pages. Create a new component named AuthHero (or export the
HeroText as default) that contains the current markup/usage of useI18n() and all
styles, export it, then replace the HeroText definitions in both LoginPage.jsx
and RegisterPage.jsx with an import of AuthHero; ensure props or internal
useI18n remain consistent so the translations (t) and conditional prefix/suffix
logic behave the same.
In `@src/pages/RegisterPage.jsx`:
- Around line 149-176: Extract the initial form object into a shared constant
(e.g., INITIAL_FORM) and use it for both the useState initialization and in
handleTypeChange reset instead of duplicating the literal object; update the
useState call that currently sets form to the inline object and replace the
object in handleTypeChange with a reference to INITIAL_FORM so add/remove fields
only need to be changed in one place (refer to symbols: useState, form, setForm,
handleTypeChange, set).
🪄 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: CHILL
Plan: Pro
Run ID: b1712680-9395-4bc6-b538-e5b62f50782c
⛔ Files ignored due to path filters (3)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsrc/assets/logo_typo.pngis excluded by!**/*.pngsrc/assets/world-map.pngis excluded by!**/*.png
📒 Files selected for processing (60)
jsconfig.jsonpackage.jsonsrc/App.jsxsrc/apis/http.tssrc/apis/index.tssrc/apis/modules/admin.tssrc/apis/modules/analytics.tssrc/apis/modules/auth.tssrc/apis/modules/buyers.tssrc/apis/modules/companies.tssrc/apis/modules/consultations.tssrc/apis/modules/matches.tssrc/apis/modules/partners.tssrc/apis/modules/payments.tssrc/components/AuthDropdown.jsxsrc/components/AuthInput.jsxsrc/components/CompanyCard.jsxsrc/components/CompanyResultCard.jsxsrc/components/CurrencySelect.jsxsrc/components/FeedbackButton.jsxsrc/components/Footer.jsxsrc/components/Header.jsxsrc/components/IssuedCurrencyGuide.jsxsrc/components/LanguageSwitcher.jsxsrc/components/PillButton.jsxsrc/components/SquareButton.jsxsrc/hooks/useAnalytics.jssrc/hooks/useAuth.jssrc/hooks/useBuyers.jssrc/hooks/useCompanies.jssrc/hooks/useConsultations.jssrc/hooks/useMatches.jssrc/hooks/usePayments.jssrc/index.csssrc/lib/guide.jssrc/lib/i18n/I18nProvider.jsxsrc/lib/i18n/dict.jssrc/main.jsxsrc/pages/About.jsxsrc/pages/AdminMatches.jsxsrc/pages/AdminPayments.jsxsrc/pages/AdminStats.jsxsrc/pages/BuyerForm.jsxsrc/pages/CompanyInputForm.jsxsrc/pages/CompanyList.jsxsrc/pages/ContactPage.jsxsrc/pages/LandingPage.jsxsrc/pages/LoginPage.jsxsrc/pages/Matches.jsxsrc/pages/Overview.jsxsrc/pages/PartnerSearch.jsxsrc/pages/Partners.jsxsrc/pages/PaymentCheckout.jsxsrc/pages/PaymentStatus.jsxsrc/pages/PaymentsPage.jsxsrc/pages/RegisterPage.jsxsrc/pages/SchedulePage.jsxsrc/router.jsxsrc/stores/authStore.jsvite.config.js
| sellerName: string; | ||
| representativeName: string; | ||
| representativeEmail: string; | ||
| representativePhone: string; | ||
| password: string; | ||
| needs: string[]; | ||
| industries?: string[]; | ||
| tags?: string[]; | ||
| companyIntroduction: string; | ||
| sellerIntroduction: string; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: 바이어 등록 API 호출부에서 실제로 매핑되는 필드명을 확인합니다.
rg -n -C 5 "registerBuyer\|sellerName\|buyerName" --type=js --type=jsx --type=ts --type=tsxRepository: K-Statra/frontend
Length of output: 1956
🏁 Script executed:
# Attempt to search for RegisterBuyerDto usage patterns and backend API references
rg -n "RegisterBuyerDto|registerBuyer|/auth/register/buyer" --type ts --type tsx -A 3 -B 1Repository: K-Statra/frontend
Length of output: 89
🏁 Script executed:
# Search for any API request/response mappings or transformations related to buyer registration
rg -n "buyerName|sellerName.*buyer\|buyer.*sellerName" --type ts --type tsx -A 2 -B 2Repository: K-Statra/frontend
Length of output: 89
🏁 Script executed:
# Check for backend API contract documentation or constants
fd -e ts -e tsx -e json | xargs grep -l "registerBuyer\|register/buyer" 2>/dev/null | head -20Repository: K-Statra/frontend
Length of output: 108
🏁 Script executed:
# Read the auth.ts file to examine RegisterBuyerDto field names
cat -n src/apis/modules/auth.ts | head -50Repository: K-Statra/frontend
Length of output: 1550
🏁 Script executed:
# Read endpoints.ts to check API endpoint definitions for buyer registration
cat -n src/apis/endpoints.tsRepository: K-Statra/frontend
Length of output: 1139
🏁 Script executed:
# Search for registerBuyer usage without file type specification
rg -n "registerBuyer\|sellerName\|buyerName" -A 3 -B 1Repository: K-Statra/frontend
Length of output: 43
🏁 Script executed:
# Search for where RegisterBuyerDto is imported and used
rg -n "RegisterBuyerDto" -B 2 -A 5Repository: K-Statra/frontend
Length of output: 908
🏁 Script executed:
# Search for buyer registration form to see what fields are being collected
rg -n "buyerName\|sellerName.*buyer\|registerBuyer" -B 3 -A 3Repository: K-Statra/frontend
Length of output: 43
🏁 Script executed:
# Check if there are any request interceptors or transformations
rg -n "registerBuyer\|/auth/register/buyer" -B 5 -A 10Repository: K-Statra/frontend
Length of output: 43
🏁 Script executed:
# Search for files containing "buyer" and "form" or "register"
fd -e ts -e js | xargs grep -l "buyer.*form\|register.*buyer\|BuyerRegister" 2>/dev/null | head -20Repository: K-Statra/frontend
Length of output: 104
🏁 Script executed:
# List the pages/components directory structure
find . -type d \( -name "pages" -o -name "components" -o -name "forms" -o -name "views" \) | head -10Repository: K-Statra/frontend
Length of output: 90
🏁 Script executed:
# Broader search for any file mentioning buyerName or buyer registration forms
rg -n "form.*buyer\|buyer.*form\|BuyerRegister" --max-count=5Repository: K-Statra/frontend
Length of output: 43
🏁 Script executed:
# List files in pages directory
fd . src/pages -type f | head -30Repository: K-Statra/frontend
Length of output: 232
🏁 Script executed:
# List files in components directory
fd . src/components -type f | head -50Repository: K-Statra/frontend
Length of output: 232
🏁 Script executed:
# Search for registration page or component files
find src -type f \( -name "*register*" -o -name "*Register*" -o -name "*auth*" -o -name "*Auth*" \)Repository: K-Statra/frontend
Length of output: 219
🏁 Script executed:
# Read the RegisterPage.jsx file
cat src/pages/RegisterPage.jsxRepository: K-Statra/frontend
Length of output: 12547
🏁 Script executed:
# Also check the size first to see if we need to read in sections
wc -l src/pages/RegisterPage.jsxRepository: K-Statra/frontend
Length of output: 92
🏁 Script executed:
# Read the useAuth hook to see if there's any additional transformation
cat src/hooks/useAuth.jsRepository: K-Statra/frontend
Length of output: 1853
🏁 Script executed:
# Check the authStore to see if there's any transformation
cat src/stores/authStore.jsRepository: K-Statra/frontend
Length of output: 744
RegisterBuyerDto의 sellerName · sellerIntroduction 필드명이 잘못되었습니다.
RegisterPage.jsx의 handleSubmit()에서 companyName → sellerName, companyIntro → sellerIntroduction으로 매핑되고 있으며, 이 데이터는 useAuth.js를 거쳐 변환 없이 직접 authApi.registerBuyer()로 전달됩니다. 백엔드 /auth/register/buyer 엔드포인트가 buyerName과 buyerIntroduction 필드를 기대한다면 현재 구현으로는 바이어 회원가입이 실패하거나 잘못된 값이 저장됩니다.
또한 RegisterSellerDto.industry?: string(단수)과 RegisterBuyerDto.industries?: string[](복수) 간의 타입 불일치도 있습니다.
🐛 수정 제안
export interface RegisterBuyerDto {
- sellerName: string;
+ buyerName: string;
representativeName: string;
representativeEmail: string;
representativePhone: string;
password: string;
needs: string[];
industries?: string[];
tags?: string[];
- sellerIntroduction: string;
+ buyerIntroduction: string;
productIntroduction: string;
websiteUrl?: string;
}백엔드 API 계약을 확인하고 DTO 필드명을 수정하세요.
📝 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.
| sellerName: string; | |
| representativeName: string; | |
| representativeEmail: string; | |
| representativePhone: string; | |
| password: string; | |
| needs: string[]; | |
| industries?: string[]; | |
| tags?: string[]; | |
| companyIntroduction: string; | |
| sellerIntroduction: string; | |
| buyerName: string; | |
| representativeName: string; | |
| representativeEmail: string; | |
| representativePhone: string; | |
| password: string; | |
| needs: string[]; | |
| industries?: string[]; | |
| tags?: string[]; | |
| buyerIntroduction: string; |
🤖 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/apis/modules/auth.ts` around lines 19 - 27, The DTO field names in
RegisterBuyerDto are incorrect: change sellerName → buyerName and
sellerIntroduction → buyerIntroduction to match the backend contract and what
authApi.registerBuyer() expects (verify RegisterPage.jsx handleSubmit mapping
and useAuth.js passthrough). Also reconcile the industry/industries type
mismatch between RegisterSellerDto and RegisterBuyerDto by making the same shape
the backend expects (either change RegisterSellerDto.industry to industries:
string[] or RegisterBuyerDto.industries to industry: string, depending on API
contract) and update all usages of those types accordingly (look for
RegisterBuyerDto, RegisterSellerDto, sellerName, sellerIntroduction, buyerName,
buyerIntroduction, industry, industries).
| {isPassword && ( | ||
| <button | ||
| type="button" | ||
| onClick={() => setShowPw((v) => !v)} | ||
| style={{ | ||
| background: "transparent", | ||
| border: "none", | ||
| padding: 0, | ||
| cursor: "pointer", | ||
| display: "flex", | ||
| alignItems: "center", | ||
| color: focused ? "#0056ee" : "#a2a0a0", | ||
| flexShrink: 0, | ||
| transition: "color 0.15s", | ||
| }} |
There was a problem hiding this comment.
비밀번호 토글 버튼에 aria-label이 없습니다
스크린 리더 사용자는 이 버튼이 비밀번호 가시성을 제어한다는 것을 알 수 없습니다.
♿ 수정 제안
<button
type="button"
+ aria-label={showPw ? "비밀번호 숨기기" : "비밀번호 표시"}
onClick={() => setShowPw((v) => !v)}
style={{ ... }}
>📝 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.
| {isPassword && ( | |
| <button | |
| type="button" | |
| onClick={() => setShowPw((v) => !v)} | |
| style={{ | |
| background: "transparent", | |
| border: "none", | |
| padding: 0, | |
| cursor: "pointer", | |
| display: "flex", | |
| alignItems: "center", | |
| color: focused ? "#0056ee" : "#a2a0a0", | |
| flexShrink: 0, | |
| transition: "color 0.15s", | |
| }} | |
| {isPassword && ( | |
| <button | |
| type="button" | |
| aria-label={showPw ? "비밀번호 숨기기" : "비밀번호 표시"} | |
| onClick={() => setShowPw((v) => !v)} | |
| style={{ | |
| background: "transparent", | |
| border: "none", | |
| padding: 0, | |
| cursor: "pointer", | |
| display: "flex", | |
| alignItems: "center", | |
| color: focused ? "#0056ee" : "#a2a0a0", | |
| flexShrink: 0, | |
| transition: "color 0.15s", | |
| }} |
🤖 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/AuthInput.jsx` around lines 79 - 93, The password visibility
toggle button rendered when isPassword is true lacks an accessible label; update
the button in AuthInput.jsx (the element using onClick={() => setShowPw((v) =>
!v)}) to include an aria-label that reflects the current state (e.g., "Show
password" when password is hidden and "Hide password" when visible) and consider
adding aria-pressed or aria-expanded if appropriate for screen readers so the
control's purpose and state are announced.
| <button | ||
| type="button" | ||
| onClick={toggle} | ||
| aria-label="Toggle language" |
There was a problem hiding this comment.
aria-label에 현재 언어 상태 또는 전환 대상을 명시하세요.
현재 "Toggle language"는 정적 문자열로, 스크린 리더 사용자가 현재 설정된 언어나 클릭 시 전환될 언어를 알 수 없습니다. WCAG 2.1 SC 4.1.2에서 요구하는 명확한 이름(accessible name)을 위해 동적 라벨을 사용하세요.
♿ 접근성 개선 제안
- aria-label="Toggle language"
+ aria-label={lang === "ko" ? "Switch to English" : "한국어로 전환"}📝 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.
| aria-label="Toggle language" | |
| aria-label={lang === "ko" ? "Switch to English" : "한국어로 전환"} |
🤖 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/LanguageSwitcher.jsx` at line 13, The aria-label "Toggle
language" in the LanguageSwitcher.jsx button is static; update the
LanguageSwitcher component to compute a dynamic accessible name that includes
either the current language or the target language (e.g., "Current language:
English, switch to Korean" or "Switch language to Korean") and assign that
string to the button's aria-label so screen reader users know current state and
action; locate the toggle handler (e.g., toggleLanguage) and the
locale/currentLanguage state or i18n.getLocale call inside LanguageSwitcher.jsx
and build the aria-label from those values, then replace the static
aria-label="Toggle language" with the computed label.
| onSuccess: (res) => { | ||
| const { _id: companyId, name: companyName, type: role } = res.user; | ||
| setAuth(companyId, companyName, role); | ||
| toast.success("로그인됐습니다."); | ||
| navigate("/matches"); | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 백엔드 응답 형태 확인을 위한 스니펫: authApi.login / registerBuyer / registerSeller 정의와 호출부의 응답 처리 방식
fd -a -e ts -e tsx -e js -e jsx 'auth' src/apis | xargs rg -nP -C8 '\b(login|registerBuyer|registerSeller)\b'
rg -nP -C3 '\bres(\.user)?\b' src/hooks/useAuth.jsRepository: K-Statra/frontend
Length of output: 1172
🏁 Script executed:
cat -n src/apis/http.tsRepository: K-Statra/frontend
Length of output: 1332
🏁 Script executed:
rg -nP -B5 -A10 'response.*interceptor|interceptors.*response' src/apis/http.tsRepository: K-Statra/frontend
Length of output: 591
🏁 Script executed:
rg -nP 'interface.*Response|type.*Response|type.*Res' src/apis/Repository: K-Statra/frontend
Length of output: 292
🏁 Script executed:
rg -nP -C5 'setAuth' src/hooks/useAuth.jsRepository: K-Statra/frontend
Length of output: 1308
🏁 Script executed:
rg -nP 'LoginResponse|RegisterResponse|auth.*interface|auth.*type' src/ --type ts --type tsxRepository: K-Statra/frontend
Length of output: 89
🏁 Script executed:
fd -a 'types' src/apis/modules/ -exec cat {} \;Repository: K-Statra/frontend
Length of output: 208
🏁 Script executed:
cat -n src/apis/modules/auth.tsRepository: K-Statra/frontend
Length of output: 1550
🏁 Script executed:
find src -name '*.test.*' -o -name '*.spec.*' | head -20Repository: K-Statra/frontend
Length of output: 43
🏁 Script executed:
rg -nP 'setAuth' src/stores/ -B3 -A10Repository: K-Statra/frontend
Length of output: 892
🏁 Script executed:
rg -nP 'export.*authStore|useAuthStore' src/stores/ -A30Repository: K-Statra/frontend
Length of output: 1278
🏁 Script executed:
rg -nP 'user\..*_id|company.*_id' src/hooks/useAuth.js -B2 -A2Repository: K-Statra/frontend
Length of output: 43
useLogin과 useRegister의 응답 언패킹 형태가 비대칭입니다 — 잠재적 런타임 오류 가능성
- 14:
const { _id, name, type } = res.user;→res가.user프로퍼티를 가져야 함 - 42:
const { _id, name, type } = res;→res가 평탄 구조(_id,name,type직접)여야 함
axios 응답 인터셉터(http.ts:17)가 res.data를 반환하므로, 백엔드에서 동일한 응답 스키마를 사용한다면 둘 중 한쪽은 undefined 값으로 setAuth(undefined, undefined, undefined)를 호출합니다. 이는 persist된 authStore에 빈 값을 저장하고, 이후 인증 요청에서 반복적인 401 오류를 야기할 수 있습니다. 백엔드 스펙을 확인하여 두 엔드포인트의 응답 형태를 통일해 주세요.
🤖 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/hooks/useAuth.js` around lines 13 - 18, Responses from useLogin and
useRegister destructure different shapes (one uses res.user, the other res)
causing undefined auth values; confirm axios interceptor (http.ts:17) returns
res.data and then make both hooks normalize the same shape before calling
setAuth (either extract { _id, name, type } from res.data or from res.data.user
consistently), update useLogin and useRegister to use that same destructuring,
and ensure setAuth is only called with defined values.
| {isLoggedIn ? ( | ||
| <SquareButton variant="solid" onClick={() => navigate("/matches")}> | ||
| {t("nav_partner_matching")} | ||
| </SquareButton> |
There was a problem hiding this comment.
nav_partner_matching 키가 헤더와 랜딩 CTA에서 서로 다른 경로로 연결됩니다.
Header.jsx에서 nav_partner_matching 키는 /partners(파트너 탐색 페이지)로 연결되지만, 이 CTA는 /matches(매칭 검색 페이지)로 이동합니다. 동일한 레이블이 다른 목적지를 가리키면 사용자가 헤더와 CTA 버튼에서 같은 텍스트를 보고 다른 페이지에 도달하는 혼란이 발생합니다.
의도한 동작이라면 CTA 전용 i18n 키(landing_partner_matching_cta 등)를 별도로 정의하거나, 경로를 /partners로 통일하는 것을 권장합니다.
✏️ 제안 수정 (별도 키 사용)
- <SquareButton variant="solid" onClick={() => navigate("/matches")}>
- {t("nav_partner_matching")}
+ <SquareButton variant="solid" onClick={() => navigate("/partners")}>
+ {t("nav_partner_matching")}또는 dict.js에 전용 키 추가:
- {t("nav_partner_matching")}
+ {t("landing_cta_partner_matching")}📝 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.
| {isLoggedIn ? ( | |
| <SquareButton variant="solid" onClick={() => navigate("/matches")}> | |
| {t("nav_partner_matching")} | |
| </SquareButton> | |
| {isLoggedIn ? ( | |
| <SquareButton variant="solid" onClick={() => navigate("/partners")}> | |
| {t("nav_partner_matching")} | |
| </SquareButton> |
🤖 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/pages/LandingPage.jsx` around lines 173 - 176, The i18n key
nav_partner_matching is used in both Header.jsx (linking to /partners) and
LandingPage.jsx's SquareButton (linking to /matches), causing inconsistent
destinations; either change the CTA's navigate target to /partners in
LandingPage.jsx (SquareButton onClick navigate("/partners")) to match
Header.jsx, or create a new i18n key (e.g., landing_partner_matching_cta) in
your dict and use that key in LandingPage.jsx while keeping its /matches
route—update the translation files accordingly and ensure SquareButton and
Header.jsx reference the intended keys.
| import LoginPage from "@/pages/LoginPage.jsx"; | ||
| import RegisterPage from "@/pages/RegisterPage.jsx"; | ||
| import Overview from "@/pages/Overview.jsx"; | ||
| import PartnerSearch from "@/pages/PartnerSearch.jsx"; |
There was a problem hiding this comment.
미사용 PartnerSearch import를 제거해야 합니다.
PartnerSearch는 어떤 <Route>에도 사용되지 않으며 lint 경고로도 확인됩니다.
🗑️ 제안 수정
- import PartnerSearch from "@/pages/PartnerSearch.jsx";📝 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.
| import PartnerSearch from "@/pages/PartnerSearch.jsx"; |
🧰 Tools
🪛 GitHub Check: Frontend Build & Lint
[warning] 7-7:
'PartnerSearch' is defined but never used. Allowed unused vars must match /^_/u
🤖 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/router.jsx` at line 7, Remove the unused import PartnerSearch from the
top of the module: locate the import statement "import PartnerSearch from
'@/pages/PartnerSearch.jsx';" and delete it (or replace with a used symbol if
you intended to add a Route). Ensure no references to PartnerSearch remain in
the router (e.g., Route definitions) and run the linter to confirm the warning
is resolved.
🔍 PR 요약
🧾 관련 이슈
🛠️ 주요 변경 사항
useAuth훅 구현 — 로그인/회원가입useMutation연동, 성공·실패 toast 알림authStore리팩터링 —role,isLoggedIn필드 추가, localStorage persist 적용/login에서도 리다이렉트하던 버그 수정form태그 제거 및 Enter 키 제출 지원🧠 의도 및 배경
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선사항
기타