Skip to content

[FEAT] Auth API 연동 및 로그인/회원가입 UI 개편#10

Merged
BaeSeong-min merged 34 commits into
developfrom
feat/#9
May 8, 2026
Merged

[FEAT] Auth API 연동 및 로그인/회원가입 UI 개편#10
BaeSeong-min merged 34 commits into
developfrom
feat/#9

Conversation

@BaeSeong-min

@BaeSeong-min BaeSeong-min commented May 8, 2026

Copy link
Copy Markdown
Contributor

🔍 PR 요약

  • 로그인/회원가입 Auth API 연동 및 관련 UI 개편

🧾 관련 이슈

🛠️ 주요 변경 사항

  • useAuth 훅 구현 — 로그인/회원가입 useMutation 연동, 성공·실패 toast 알림
  • authStore 리팩터링 — role, isLoggedIn 필드 추가, localStorage persist 적용
  • 로그인 실패 시 페이지 새로고침 현상 수정 — 401 인터셉터가 /login에서도 리다이렉트하던 버그 수정
  • 회원가입 form 태그 제거 및 Enter 키 제출 지원
  • 회원가입 Company Type 드롭다운으로 변경 — TypeToggle 제거, Buyer/Seller에 따라 Sourcing Interests/Export Items 필드 분기
  • 랜딩·로그인·회원가입 페이지 i18n 전체 적용 (한국어/영어), 언어 전환 fade 트랜지션 추가
  • 랜딩 페이지 로그인 상태에 따른 버튼 분기 — 로그인 시 "파트너 매칭" 단일 CTA 표시
  • 헤더 로고 이미지 교체, 지구본 아이콘 언어 전환 버튼으로 교체

🧠 의도 및 배경

  • 기존 하드코딩된 로그인/회원가입 UI에 실제 Auth API를 연동하여 서비스 사용 가능한 상태로 전환
  • 서버 유효성 에러 메세지를 toast로 노출해 사용자 피드백 개선
  • 피그마 디자인 기준으로 헤더 및 랜딩 페이지 UI 통일

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 로그인 및 회원가입 페이지 추가
    • 새로운 랜딩 페이지 구현
    • 글로브 아이콘으로 언어 전환 기능 개선
    • 헤더 네비게이션 컴포넌트 추가
  • 개선사항

    • 인증 흐름 개선 (자동 로그인 페이지 리다이렉트)
    • 언어 변경 시 페이드 트랜지션 효과 추가
    • 라우팅 구조 재구성 (로그인/회원가입/랜딩 경로 정리)
  • 기타

    • 아이콘 라이브러리(lucide-react) 추가
    • 프로젝트 경로 설정 최적화

@BaeSeong-min BaeSeong-min self-assigned this May 8, 2026
@vercel

vercel Bot commented May 8, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
k-statra-frontend Ready Ready Preview, Comment May 8, 2026 4:28pm

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Rate limit exceeded

@BaeSeong-min has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 10 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b41b7752-cc75-481e-8e9f-12317799f6cc

📥 Commits

Reviewing files that changed from the base of the PR and between 875bbeb and 14d6a24.

📒 Files selected for processing (5)
  • src/apis/http.ts
  • src/apis/modules/auth.ts
  • src/components/AuthInput.jsx
  • src/pages/LoginPage.jsx
  • src/pages/RegisterPage.jsx
📝 Walkthrough

개요

이 PR은 프로젝트 전역에 임포트 별칭 인프라(@)를 도입하고, 인증 모델을 리팩토링하며, 랜딩/로그인/회원가입 페이지를 구현하고, 헤더 네비게이션을 재구성합니다.

변경 사항

임포트 별칭 인프라 및 도구 설정

레이어 / 파일(들) 요약
빌드 및 IDE 도구 설정
jsconfig.json, vite.config.js
@./src 별칭을 정의하여 IDE와 번들러에서 절대 경로 해석을 활성화합니다.
의존성 추가
package.json
아이콘 렌더링을 위해 lucide-react@^1.14.0을 추가합니다.
임포트 경로 마이그레이션
src/apis/*, src/hooks/*, src/components/*, src/lib/*, src/pages/*, src/stores/*, src/main.jsx
모든 상대 경로 임포트를 @/ 별칭 기반 절대 경로로 일관되게 치환합니다.

인증 및 사용자 모델 리팩토링

레이어 / 파일(들) 요약
인증 저장소 모델
src/stores/authStore.js
buyerId, buyerName, loginModalOpencompanyId, companyName, role, isLoggedIn으로 리팩토링하고 setAuth 시그니처를 업데이트합니다.
등록 API DTO
src/apis/modules/auth.ts
RegisterSellerDtoRegisterBuyerDto 필드를 이름 변경(companyNamesellerName, companyIntroductionsellerIntroduction)하고 구매자 DTO에 productIntroduction, websiteUrl? 필드를 추가합니다.
HTTP 401 에러 처리
src/apis/http.ts
401 응답을 모달 열기에서 /login 리다이렉트로 변경하고 useAuthStore 임포트를 제거합니다.
로그인/회원가입 훅
src/hooks/useAuth.js
React Query 뮤테이션으로 API 호출, 인증 상태 업데이트, 토스트, 라우팅을 처리하는 useLoginuseRegister 훅을 구현합니다.

인증 페이지 및 UI 컴포넌트

레이어 / 파일(들) 요약
폼 컴포넌트
src/components/AuthInput.jsx, src/components/AuthDropdown.jsx, src/components/PillButton.jsx, src/components/SquareButton.jsx
포커스 상태, 비밀번호 가시성 토글, 외부 클릭 닫기, 스타일 기반 변형을 지원하는 재사용 가능한 컴포넌트를 추가합니다.
랜딩페이지
src/pages/LandingPage.jsx
배경 이미지, 현지화된 텍스트, 로그인 상태에 따른 조건부 CTA 버튼(/matches 또는 /login//register)을 렌더링합니다.
로그인페이지
src/pages/LoginPage.jsx
분할 레이아웃(좌: 히어로, 우: 폼)으로 이메일/비밀번호 입력, Enter 제출, 보류 상태 버튼 비활성화를 지원합니다.
회원가입페이지
src/pages/RegisterPage.jsx
구매자/판매자 모드 선택, 키워드 배열 파싱, 조건부 DTO 생성을 포함하며 모드별 라벨을 전환합니다.

네비게이션 및 레이아웃 재구성

레이어 / 파일(들) 요약
헤더 컴포넌트
src/components/Header.jsx
로고, navItems 배열 기반 NavLink 네비게이션, 클릭 추적, 현지화 및 LanguageSwitcher를 렌더링합니다.
언어 전환기 리팩토링
src/components/LanguageSwitcher.jsx
<select> 기반 다국어 선택을 globe 아이콘 <button> 기반 koen 토글로 변경하고 lucide-react Globe 아이콘을 사용합니다.
App.jsx 위임
src/App.jsx
모놀리식 셸을 Header, AppRouter, Footer 컴포넌트 렌더링 최소 래퍼로 리팩토링하고 track("page_view", {path})만 유지합니다.
라우터 업데이트
src/router.jsx
페이지 임포트를 별칭 경로로 전환하고, / 라우트를 LandingPage로 변경하며, /login, /register 라우트를 추가합니다.

지원 변경 사항

레이어 / 파일(들) 요약
국제화 업데이트
src/lib/i18n/dict.js, src/lib/i18n/I18nProvider.jsx
영문/한문 사전에 login_*, register_*, landing_* 키를 추가하고, setLang에 body 불투명도 페이드 전환 효과를 추가합니다.
CSS 스타일링
src/index.css
scrollbar 숨김 규칙을 추가하고 sticky .header 배경을 #f4f7fc로 변경합니다.
결제 생성 확장
src/pages/PaymentCheckout.jsx
useCreatePayment 훅을 사용하여 결제 만료 시 새 결제 생성을 지원합니다.
Matches 저장소 통합
src/pages/Matches.jsx
buyerId 대신 companyId를 사용하도록 저장소 필드 참조를 업데이트합니다.

예상 코드 리뷰 노력

🎯 4 (복잡함) | ⏱️ ~60분

관련 가능성이 있는 PR

  • K-Strata/frontend#8: API 레이어와 인증 저장소 동작을 변경하여 직접 관련됨—같은 모듈(http.ts, index.ts, auth.ts)을 변경합니다.
  • K-Strata/frontend#4: 같은 대규모 프로젝트 구조 재구성—도메인 폴더 이동과 임포트 경로 별칭 전환을 수행합니다.

🐰 별칭과 함께 춤을 추네!
@는 마법, 경로는 짧아지고,
로그인, 회원가입 페이지 준비 완료,
랜딩에 발을 디디며 전진하는 모습!
헤더가 춤을 추고, 라우터가 노래 부르네~

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 이슈 번호(#9)를 명시하고 핵심 변경사항인 Auth API 연동 및 로그인/회원가입 UI 개편을 명확하게 요약합니다.
Description check ✅ Passed PR 설명이 템플릿 구조(요약, 관련 이슈, 주요 변경 사항, 의도)를 충실히 따르며 구현된 모든 주요 기능이 상세히 기록되어 있습니다.
Linked Issues check ✅ Passed PR은 이슈 #9의 모든 핵심 요구사항을 충족합니다: 헤더 네비게이션 디자인 적용, 랜딩페이지/로그인/회원가입 UI 구현, Auth API 연동 및 i18n 지원.
Out of Scope Changes check ✅ Passed 모든 변경사항은 이슈 #9의 범위 내에 있습니다. 경로 별칭 설정(jsconfig.json, vite.config.js)은 프로젝트 인프라 개선으로서 적절하며, 의존성 추가(lucide-react)도 구현된 UI 컴포넌트를 지원합니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#9

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.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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: 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 공격 시 자격증명이 노출됩니다

adminTokenlocalStorage에 평문으로 저장하면, 앱 내 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'인 사용자에게 companyIdbuyerInput으로 자동 채우면 잘못된 매칭 요청이 발생합니다.

현재 코드는 role과 관계없이 companyId를 buyer ID 입력값의 기본값으로 사용합니다. role'seller'인 사용자가 /matches로 이동하면 자신의 seller companyId가 buyer ID 필드에 채워져, 유효한 ObjectId 형식이므로 검증을 통과한 뒤 의미 없는 매칭 결과를 반환하게 됩니다.

또한 변수명 storedBuyerIdcompanyId를 가리키는데, 이는 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 IDuseRef로 추적하면 새로운 호출 시 이전 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 value

LGTM — 별칭 임포트 정상 적용

한 가지 사소한 스타일 불일치: 이 파일은 @/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 .innerpadding: 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 value

Zustand 셀렉터 패턴은 선택사항입니다(액션 함수의 경우).

현재 코드의 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?.messagesrc/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.jsxHeroText(11–134)와 src/pages/RegisterPage.jsxHeroText(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

📥 Commits

Reviewing files that changed from the base of the PR and between 7503e12 and 875bbeb.

⛔ Files ignored due to path filters (3)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src/assets/logo_typo.png is excluded by !**/*.png
  • src/assets/world-map.png is excluded by !**/*.png
📒 Files selected for processing (60)
  • jsconfig.json
  • package.json
  • src/App.jsx
  • src/apis/http.ts
  • src/apis/index.ts
  • src/apis/modules/admin.ts
  • src/apis/modules/analytics.ts
  • src/apis/modules/auth.ts
  • src/apis/modules/buyers.ts
  • src/apis/modules/companies.ts
  • src/apis/modules/consultations.ts
  • src/apis/modules/matches.ts
  • src/apis/modules/partners.ts
  • src/apis/modules/payments.ts
  • src/components/AuthDropdown.jsx
  • src/components/AuthInput.jsx
  • src/components/CompanyCard.jsx
  • src/components/CompanyResultCard.jsx
  • src/components/CurrencySelect.jsx
  • src/components/FeedbackButton.jsx
  • src/components/Footer.jsx
  • src/components/Header.jsx
  • src/components/IssuedCurrencyGuide.jsx
  • src/components/LanguageSwitcher.jsx
  • src/components/PillButton.jsx
  • src/components/SquareButton.jsx
  • src/hooks/useAnalytics.js
  • src/hooks/useAuth.js
  • src/hooks/useBuyers.js
  • src/hooks/useCompanies.js
  • src/hooks/useConsultations.js
  • src/hooks/useMatches.js
  • src/hooks/usePayments.js
  • src/index.css
  • src/lib/guide.js
  • src/lib/i18n/I18nProvider.jsx
  • src/lib/i18n/dict.js
  • src/main.jsx
  • src/pages/About.jsx
  • src/pages/AdminMatches.jsx
  • src/pages/AdminPayments.jsx
  • src/pages/AdminStats.jsx
  • src/pages/BuyerForm.jsx
  • src/pages/CompanyInputForm.jsx
  • src/pages/CompanyList.jsx
  • src/pages/ContactPage.jsx
  • src/pages/LandingPage.jsx
  • src/pages/LoginPage.jsx
  • src/pages/Matches.jsx
  • src/pages/Overview.jsx
  • src/pages/PartnerSearch.jsx
  • src/pages/Partners.jsx
  • src/pages/PaymentCheckout.jsx
  • src/pages/PaymentStatus.jsx
  • src/pages/PaymentsPage.jsx
  • src/pages/RegisterPage.jsx
  • src/pages/SchedulePage.jsx
  • src/router.jsx
  • src/stores/authStore.js
  • vite.config.js

Comment thread src/apis/modules/auth.ts
Comment on lines +19 to +27
sellerName: string;
representativeName: string;
representativeEmail: string;
representativePhone: string;
password: string;
needs: string[];
industries?: string[];
tags?: string[];
companyIntroduction: string;
sellerIntroduction: string;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: 바이어 등록 API 호출부에서 실제로 매핑되는 필드명을 확인합니다.
rg -n -C 5 "registerBuyer\|sellerName\|buyerName" --type=js --type=jsx --type=ts --type=tsx

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

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

Repository: 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 -20

Repository: 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 -50

Repository: 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.ts

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

Repository: K-Statra/frontend

Length of output: 43


🏁 Script executed:

# Search for where RegisterBuyerDto is imported and used
rg -n "RegisterBuyerDto" -B 2 -A 5

Repository: 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 3

Repository: 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 10

Repository: 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 -20

Repository: 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 -10

Repository: 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=5

Repository: K-Statra/frontend

Length of output: 43


🏁 Script executed:

# List files in pages directory
fd . src/pages -type f | head -30

Repository: K-Statra/frontend

Length of output: 232


🏁 Script executed:

# List files in components directory
fd . src/components -type f | head -50

Repository: 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.jsx

Repository: 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.jsx

Repository: 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.js

Repository: K-Statra/frontend

Length of output: 1853


🏁 Script executed:

# Check the authStore to see if there's any transformation
cat src/stores/authStore.js

Repository: K-Statra/frontend

Length of output: 744


RegisterBuyerDtosellerName · sellerIntroduction 필드명이 잘못되었습니다.

RegisterPage.jsx의 handleSubmit()에서 companyNamesellerName, companyIntrosellerIntroduction으로 매핑되고 있으며, 이 데이터는 useAuth.js를 거쳐 변환 없이 직접 authApi.registerBuyer()로 전달됩니다. 백엔드 /auth/register/buyer 엔드포인트가 buyerNamebuyerIntroduction 필드를 기대한다면 현재 구현으로는 바이어 회원가입이 실패하거나 잘못된 값이 저장됩니다.

또한 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.

Suggested change
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).

Comment thread src/components/AuthInput.jsx Outdated
Comment on lines +79 to +93
{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",
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

비밀번호 토글 버튼에 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.

Suggested change
{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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

Comment thread src/hooks/useAuth.js
Comment on lines +13 to +18
onSuccess: (res) => {
const { _id: companyId, name: companyName, type: role } = res.user;
setAuth(companyId, companyName, role);
toast.success("로그인됐습니다.");
navigate("/matches");
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 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.js

Repository: K-Statra/frontend

Length of output: 1172


🏁 Script executed:

cat -n src/apis/http.ts

Repository: K-Statra/frontend

Length of output: 1332


🏁 Script executed:

rg -nP -B5 -A10 'response.*interceptor|interceptors.*response' src/apis/http.ts

Repository: 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.js

Repository: K-Statra/frontend

Length of output: 1308


🏁 Script executed:

rg -nP 'LoginResponse|RegisterResponse|auth.*interface|auth.*type' src/ --type ts --type tsx

Repository: 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.ts

Repository: K-Statra/frontend

Length of output: 1550


🏁 Script executed:

find src -name '*.test.*' -o -name '*.spec.*' | head -20

Repository: K-Statra/frontend

Length of output: 43


🏁 Script executed:

rg -nP 'setAuth' src/stores/ -B3 -A10

Repository: K-Statra/frontend

Length of output: 892


🏁 Script executed:

rg -nP 'export.*authStore|useAuthStore' src/stores/ -A30

Repository: K-Statra/frontend

Length of output: 1278


🏁 Script executed:

rg -nP 'user\..*_id|company.*_id' src/hooks/useAuth.js -B2 -A2

Repository: K-Statra/frontend

Length of output: 43


useLoginuseRegister의 응답 언패킹 형태가 비대칭입니다 — 잠재적 런타임 오류 가능성

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

Comment thread src/pages/LandingPage.jsx
Comment on lines +173 to +176
{isLoggedIn ? (
<SquareButton variant="solid" onClick={() => navigate("/matches")}>
{t("nav_partner_matching")}
</SquareButton>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
{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.

Comment thread src/pages/LoginPage.jsx
Comment thread src/pages/RegisterPage.jsx
Comment thread src/router.jsx
import LoginPage from "@/pages/LoginPage.jsx";
import RegisterPage from "@/pages/RegisterPage.jsx";
import Overview from "@/pages/Overview.jsx";
import PartnerSearch from "@/pages/PartnerSearch.jsx";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

미사용 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.

Suggested change
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.

Comment thread src/stores/authStore.js
@BaeSeong-min BaeSeong-min merged commit 72bee49 into develop May 8, 2026
4 checks passed
@BaeSeong-min BaeSeong-min changed the title [#9] Auth API 연동 및 로그인/회원가입 UI 개편 [FEAT] Auth API 연동 및 로그인/회원가입 UI 개편 May 9, 2026
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.

[Feat] 랜딩페이지 및 회원가입 페이지 구현

1 participant