- {/* 좌측: 로고 + 카피 (정중앙 정렬) */}
-
-

-
- 잡다와 함께 효율적인 채용을
-
- 경험해 보세요
-
+
+ 잡다와 함께 효율적인
+
+ 채용을 경험해보세요.
+ >
+ }
+ >
+
+
+
+
);
}
diff --git a/src/features/auth/pages/ResetPasswordEmailPage.tsx b/src/features/auth/pages/ResetPasswordEmailPage.tsx
index f1bd5156..01cb9a1f 100644
--- a/src/features/auth/pages/ResetPasswordEmailPage.tsx
+++ b/src/features/auth/pages/ResetPasswordEmailPage.tsx
@@ -1,22 +1,17 @@
-import { type FormEvent, useState } from 'react';
+import { type FormEvent } from 'react';
import { requestResetEmail } from '../api/auth.api.ts';
import AuthShell from '../components/AuthShell.tsx';
-import AlertModal from '@components/Alertmodal.tsx';
+import { useAuthedClient } from '@shared/hooks/useAuthClient.ts';
+import { useAlertStore } from '@shared/store/useAlertStore.ts';
export default function ResetPasswordEmailPage() {
- const [alertModal, setAlertModal] = useState({
- open: false,
- type: 'info' as 'success' | 'error' | 'info' | 'warning',
- message: '',
- });
+ const client = useAuthedClient();
- const showAlert = (message: string, type: 'success' | 'error' | 'info' | 'warning' = 'info') => {
- setAlertModal({ open: true, type, message });
- };
+ const openAlertModal = useAlertStore((s) => s.action.openAlertModal);
- const closeAlert = () => {
- setAlertModal((prev) => ({ ...prev, open: false }));
+ const showAlert = (message: string, type: 'success' | 'error' | 'info' | 'warning' = 'info') => {
+ openAlertModal({ type, message });
};
const onSubmit = async (e: FormEvent) => {
@@ -24,7 +19,7 @@ export default function ResetPasswordEmailPage() {
const form = new FormData(e.target as HTMLFormElement);
const email = String(form.get('email') || '');
try {
- await requestResetEmail(email); // 서버에 메일 발송 요청
+ await requestResetEmail(client, email); // 서버에 메일 발송 요청
showAlert('이메일을 확인해 주세요.\n비밀번호 재설정 링크가 발송되었습니다.', 'success');
} catch {
showAlert('이메일 전송에 실패했습니다.\n다시 시도해주세요.', 'error');
@@ -70,12 +65,6 @@ export default function ResetPasswordEmailPage() {
이메일 인증
-
);
}
diff --git a/src/features/auth/pages/ResetPasswordPage.tsx b/src/features/auth/pages/ResetPasswordPage.tsx
index f3a8caa1..af2ec789 100644
--- a/src/features/auth/pages/ResetPasswordPage.tsx
+++ b/src/features/auth/pages/ResetPasswordPage.tsx
@@ -4,6 +4,7 @@ import AuthShell from '../components/AuthShell';
import ReqBadge from '../components/ReqBadge';
import { buildPasswordChecks } from '../utils/passwordChecks';
import { resetPasswordByToken } from '../api/auth.api.ts';
+import { useAuthedClient } from '@shared/hooks/useAuthClient.ts';
export default function ResetPasswordPage() {
const [searchParams] = useSearchParams();
@@ -22,6 +23,8 @@ export default function ResetPasswordPage() {
});
const [didSubmit, setDidSubmit] = useState(false);
+ const client = useAuthedClient();
+
useEffect(() => {
if (!token) {
setServerError('유효하지 않은 링크입니다. 이메일의 버튼을 다시 눌러 주세요.');
@@ -50,7 +53,7 @@ export default function ResetPasswordPage() {
setSubmitting(true);
setServerError(null);
try {
- await resetPasswordByToken(token, password);
+ await resetPasswordByToken(client, token, password);
setSuccess(true);
setTimeout(() => navigate('/login'), 1800);
diff --git a/src/features/auth/pages/SignupCompanyEmailPage.tsx b/src/features/auth/pages/SignupCompanyEmailPage.tsx
index 76c3eabf..e2df4be0 100644
--- a/src/features/auth/pages/SignupCompanyEmailPage.tsx
+++ b/src/features/auth/pages/SignupCompanyEmailPage.tsx
@@ -3,6 +3,7 @@ import { useState } from 'react';
import AuthShell from '../components/AuthShell';
import { sendCompanyVerifyLink } from '../api/auth.api.ts';
import { ERROR_CODES } from '@features/constants/errorCodes.ts';
+import { useAuthedClient } from '@shared/hooks/useAuthClient.ts';
const PERSONAL_DOMAINS = new Set([
'gmail.com',
@@ -21,6 +22,7 @@ export default function SignupCompanyEmailPage() {
const [loading, setLoading] = useState(false); // 전송 중 중복 클릭 방지
const [sent, setSent] = useState(false); // 전송 완료 안내 표시
const [error, setError] = useState('');
+ const client = useAuthedClient();
const handleChange = (e: React.ChangeEvent
) => {
const value = e.target.value.trim();
@@ -38,7 +40,7 @@ export default function SignupCompanyEmailPage() {
if (invalid || !email || loading) return; // 회사메일 아닐 때/중복 클릭 방지
try {
setLoading(true); // 버튼 잠금
- await sendCompanyVerifyLink(email); // 서버에 “인증 링크 보내기” 요청
+ await sendCompanyVerifyLink(client, email); // 서버에 “인증 링크 보내기” 요청
setSent(true); // 안내 메시지 노출
setError('');
} catch (err: any) {
diff --git a/src/features/auth/pages/SignupFormPage.tsx b/src/features/auth/pages/SignupFormPage.tsx
index 07df53fc..05dcbc20 100644
--- a/src/features/auth/pages/SignupFormPage.tsx
+++ b/src/features/auth/pages/SignupFormPage.tsx
@@ -6,8 +6,9 @@ import PrivacyModal from '../components/PrivacyModal';
import ReqBadge from '../components/ReqBadge';
import { buildPasswordChecks } from '../utils/passwordChecks';
import { signup } from '../api/auth.api.ts';
-import AlertModal from '@components/Alertmodal.tsx';
+import AlertModal from '@shared/components/Alertmodal.tsx';
import type { PositionValue } from '@features/user/components/PositionSelect.tsx';
+import { useAuthedClient } from '@shared/hooks/useAuthClient.ts';
const POSITION_OPTIONS: { value: PositionValue; label: string }[] = [
{ value: 'OWNER', label: '대표 / Founder' },
@@ -21,14 +22,12 @@ export default function SignupFormPage() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
- // URL에서 email + token 둘 다 읽기
const token = searchParams.get('token'); // URL에서 토큰만 읽음
const emailFromUrl = searchParams.get('email') ?? ''; // 인증된 회사 이메일
const [companyEmail, setCompanyEmail] = useState(emailFromUrl);
const [loading, setLoading] = useState(true);
- // 필수 입력값 상태들 — 컴포넌트 "안"에서 선언
const [name, setName] = useState('');
const [companyName, setCompanyName] = useState('');
const [position, setPosition] = useState('');
@@ -37,12 +36,11 @@ export default function SignupFormPage() {
const [errors, setErrors] = useState<{ [key: string]: string }>({});
const [nickname, setNickname] = useState('');
- // 비밀번호 입력 상태
-
const [password, setPassword] = useState('');
const [passwordConfirm, setPasswordConfirm] = useState('');
- // 모달 트리거 버튼 ref
+ const client = useAuthedClient();
+
const openPrivacyBtnRef = useRef(null);
const [alertModal, setAlertModal] = useState({
@@ -175,15 +173,18 @@ export default function SignupFormPage() {
const finalPosition = position === 'OTHER' ? customPosition.trim() : (position as string);
try {
- await signup({
- token,
- email: companyEmail,
- name,
- nickname,
- position: finalPosition,
- companyName,
- password,
- });
+ await signup(
+ {
+ token,
+ email: companyEmail,
+ name,
+ nickname,
+ position: finalPosition,
+ companyName,
+ password,
+ },
+ client
+ );
showAlert('가입이 완료되었습니다.\n로그인 해주세요.', 'success');
navigate('/login', { replace: true });
diff --git a/src/features/dashboard/DetailSection.tsx b/src/features/dashboard/DetailSection.tsx
index 1347f957..838f0681 100644
--- a/src/features/dashboard/DetailSection.tsx
+++ b/src/features/dashboard/DetailSection.tsx
@@ -21,7 +21,6 @@ export default function DetailSection() {
title={'이번 주 캘린더'}
description={'주간 일정 개요'}
className="jd-LongViewContainerCard-RWD-full"
- detailUrl={'#'}
>
diff --git a/src/features/dashboard/card/ApplicantsStatCard.tsx b/src/features/dashboard/card/ApplicantsStatCard.tsx
index 9430d6d6..5f933446 100644
--- a/src/features/dashboard/card/ApplicantsStatCard.tsx
+++ b/src/features/dashboard/card/ApplicantsStatCard.tsx
@@ -2,9 +2,9 @@ import LongViewContainer from '@features/dashboard/container/LongViewContainer.t
import NumberSlotsCard, {
type NumberSlotsCardProps,
} from '@features/dashboard/card/NumberSlotCardUI.tsx';
-import useFetch from '@/hooks/useFetch.ts';
-import { Skeleton } from '@components/Skeleton.tsx';
-import BlankCard from '@components/dashboard/BlankCard.tsx';
+import useFetch from '@shared/hooks/useFetch.ts';
+import { Skeleton } from '@shared/components/Skeleton.tsx';
+import BlankCard from '@shared/components/BlankCard.tsx';
export default function ApplicantsStatCard() {
const { resData } = useFetch(
diff --git a/src/features/dashboard/card/DateInfoCardUI.tsx b/src/features/dashboard/card/DateInfoCardUI.tsx
index 691613e4..77394eea 100644
--- a/src/features/dashboard/card/DateInfoCardUI.tsx
+++ b/src/features/dashboard/card/DateInfoCardUI.tsx
@@ -1,4 +1,4 @@
-import SelectIcon from '@components/SelectIcon.tsx';
+import SelectIcon from '@shared/components/SelectIcon.tsx';
export type DataInfoCardProps = {
isOrganizer: boolean;
diff --git a/src/features/dashboard/card/InterviewStatusCard.tsx b/src/features/dashboard/card/InterviewStatusCard.tsx
index b80abd4a..08bb19b1 100644
--- a/src/features/dashboard/card/InterviewStatusCard.tsx
+++ b/src/features/dashboard/card/InterviewStatusCard.tsx
@@ -1,5 +1,5 @@
import LongViewContainer from '@features/dashboard/container/LongViewContainer.tsx';
-import useFetch from '@/hooks/useFetch.ts';
+import useFetch from '@shared/hooks/useFetch.ts';
import StatusCountCardUI from '@features/dashboard/card/StatusCountCardUI.tsx';
type NumByProgressStatus = {
diff --git a/src/features/dashboard/card/JobStatusCard.tsx b/src/features/dashboard/card/JobStatusCard.tsx
index 5904f28f..7be3cb36 100644
--- a/src/features/dashboard/card/JobStatusCard.tsx
+++ b/src/features/dashboard/card/JobStatusCard.tsx
@@ -1,5 +1,5 @@
import LongViewContainer from '@features/dashboard/container/LongViewContainer.tsx';
-import useFetch from '@/hooks/useFetch.ts';
+import useFetch from '@shared/hooks/useFetch.ts';
import StatusCountCardUI from '@features/dashboard/card/StatusCountCardUI.tsx';
type NumByProgressStatus = {
diff --git a/src/features/dashboard/card/NumberSlotCardUI.tsx b/src/features/dashboard/card/NumberSlotCardUI.tsx
index b6e4d726..3cb618f7 100644
--- a/src/features/dashboard/card/NumberSlotCardUI.tsx
+++ b/src/features/dashboard/card/NumberSlotCardUI.tsx
@@ -1,4 +1,4 @@
-import Badge, { type BadgeType } from '@components/dashboard/Badge.tsx';
+import Badge, { type BadgeType } from '@features/dashboard/components/Badge.tsx';
const STATUS_INFO = {
DOCUMENT: {
@@ -45,7 +45,7 @@ export default function NumberSlotsCard({ title, slotData, status }: NumberSlots
{['지원자', '북마크', '면접', '합격'].map((item, index) => {
- if (index == 1) return <>>;
+ if (index == 1) return null;
return ;
})}
diff --git a/src/features/dashboard/card/StatusCountCardUI.tsx b/src/features/dashboard/card/StatusCountCardUI.tsx
index 96cefbac..b918377a 100644
--- a/src/features/dashboard/card/StatusCountCardUI.tsx
+++ b/src/features/dashboard/card/StatusCountCardUI.tsx
@@ -1,5 +1,5 @@
-import Badge, { type BadgeType } from '@components/dashboard/Badge.tsx';
-import { Skeleton } from '@components/Skeleton.tsx';
+import Badge, { type BadgeType } from '@features/dashboard/components/Badge.tsx';
+import { Skeleton } from '@shared/components/Skeleton.tsx';
const STATUS_INFO = {
byJob: {
diff --git a/src/features/dashboard/card/SummationCardUI.tsx b/src/features/dashboard/card/SummationCardUI.tsx
index 1ee1a5e3..4f34ce34 100644
--- a/src/features/dashboard/card/SummationCardUI.tsx
+++ b/src/features/dashboard/card/SummationCardUI.tsx
@@ -1,8 +1,8 @@
-import DetailButton from '@components/dashboard/DetailButton.tsx';
+import DetailButton from '@features/dashboard/components/DetailButton.tsx';
-import useFetch from '@/hooks/useFetch.ts';
-import SelectIcon from '@components/SelectIcon.tsx';
-import { Skeleton } from '@components/Skeleton.tsx';
+import useFetch from '@shared/hooks/useFetch.ts';
+import SelectIcon from '@shared/components/SelectIcon.tsx';
+import { Skeleton } from '@shared/components/Skeleton.tsx';
type SummationCardProps = {
title: string;
diff --git a/src/features/dashboard/card/UpComingInterviewsCard.tsx b/src/features/dashboard/card/UpComingInterviewsCard.tsx
index dd37cb96..4f9e94d0 100644
--- a/src/features/dashboard/card/UpComingInterviewsCard.tsx
+++ b/src/features/dashboard/card/UpComingInterviewsCard.tsx
@@ -1,10 +1,10 @@
import LongViewContainer from '@features/dashboard/container/LongViewContainer.tsx';
-import useFetch from '@/hooks/useFetch.ts';
+import useFetch from '@shared/hooks/useFetch.ts';
import DateInfoCardUI, {
type DataInfoCardProps,
} from '@features/dashboard/card/DateInfoCardUI.tsx';
-import { Skeleton } from '@components/Skeleton.tsx';
-import BlankCard from '@components/dashboard/BlankCard.tsx';
+import { Skeleton } from '@shared/components/Skeleton.tsx';
+import BlankCard from '@shared/components/BlankCard.tsx';
export default function UpComingInterviewsCard() {
const { resData } = useFetch('/api/v1/dashboard/detail/upcoming-interview');
diff --git a/src/components/dashboard/Badge.tsx b/src/features/dashboard/components/Badge.tsx
similarity index 100%
rename from src/components/dashboard/Badge.tsx
rename to src/features/dashboard/components/Badge.tsx
diff --git a/src/components/dashboard/DetailButton.tsx b/src/features/dashboard/components/DetailButton.tsx
similarity index 89%
rename from src/components/dashboard/DetailButton.tsx
rename to src/features/dashboard/components/DetailButton.tsx
index d17849d3..66ecf92b 100644
--- a/src/components/dashboard/DetailButton.tsx
+++ b/src/features/dashboard/components/DetailButton.tsx
@@ -1,5 +1,5 @@
import cn from '@lib/utils/cn.ts';
-import SelectIcon from '@components/SelectIcon.tsx';
+import SelectIcon from '@shared/components/SelectIcon.tsx';
export default function DetailButton({ className, url }: { className?: string; url: string }) {
return (
diff --git a/src/components/dashboard/TimeSlot.tsx b/src/features/dashboard/components/TimeSlot.tsx
similarity index 88%
rename from src/components/dashboard/TimeSlot.tsx
rename to src/features/dashboard/components/TimeSlot.tsx
index c5b406ce..0db0b567 100644
--- a/src/components/dashboard/TimeSlot.tsx
+++ b/src/features/dashboard/components/TimeSlot.tsx
@@ -31,7 +31,11 @@ export default function TimeSlot({ time, title, type = 'INTERVIEW' }: TimeSlotPr
return (
<>
-
{time}
+ {type == 'INTERVIEW' ? (
+
{time}
+ ) : (
+
{title}
+ )}
{SLOT_STATUS[type]}
[{title}]
- 접수 마감
+ 공고 마감
)}
diff --git a/src/features/dashboard/container/LongViewContainer.tsx b/src/features/dashboard/container/LongViewContainer.tsx
index 8a2de75c..5ac143c7 100644
--- a/src/features/dashboard/container/LongViewContainer.tsx
+++ b/src/features/dashboard/container/LongViewContainer.tsx
@@ -1,4 +1,4 @@
-import DetailButton from '@components/dashboard/DetailButton.tsx';
+import DetailButton from '@features/dashboard/components/DetailButton.tsx';
import type { ReactNode } from 'react';
import cn from '@lib/utils/cn.ts';
@@ -11,7 +11,7 @@ export default function LongViewContainer({
}: {
title: string;
description: string;
- detailUrl: string;
+ detailUrl?: string;
className?: string;
children: ReactNode;
}) {
@@ -22,7 +22,7 @@ export default function LongViewContainer({
className
)}
>
-
+ {detailUrl &&
}
{title}
{description}
diff --git a/src/features/dashboard/container/WeekendContainer.tsx b/src/features/dashboard/container/WeekendContainer.tsx
index 5e7fe4b9..e73c166f 100644
--- a/src/features/dashboard/container/WeekendContainer.tsx
+++ b/src/features/dashboard/container/WeekendContainer.tsx
@@ -1,8 +1,8 @@
-import TimeSlot, { type TimeSlotType } from '@components/dashboard/TimeSlot.tsx';
-import useFetch from '@/hooks/useFetch.ts';
-import { Skeleton } from '@components/Skeleton.tsx';
+import TimeSlot, { type TimeSlotType } from '@features/dashboard/components/TimeSlot.tsx';
+import useFetch from '@shared/hooks/useFetch.ts';
+import { Skeleton } from '@shared/components/Skeleton.tsx';
import { useEffect, useState } from 'react';
-import BlankCard from '@components/dashboard/BlankCard.tsx';
+import BlankCard from '@shared/components/BlankCard.tsx';
const WeekName = {
mon: '월',
diff --git a/src/features/interview/api/evaluation.api.ts b/src/features/interview/api/evaluation.api.ts
index e0aa7b84..12025705 100644
--- a/src/features/interview/api/evaluation.api.ts
+++ b/src/features/interview/api/evaluation.api.ts
@@ -1,5 +1,5 @@
// src/features/interview/services/evaluation.api.ts
-import type { ApiResponse } from '@features/jd/services/jobApi';
+import type { ClientRequestType } from '@shared/hooks/useAuthClient.ts';
export type InterviewEvaluationDto = {
evaluationId: number;
@@ -9,72 +9,58 @@ export type InterviewEvaluationDto = {
comment: string;
};
-const API_BASE = import.meta.env.VITE_API_URL;
-
// 1) 면접 평가 조회 (GET)
export async function getInterviewEvaluation(
+ client: ClientRequestType,
interviewId: number
): Promise {
- const res = await fetch(`${API_BASE}/api/v1/interviews/${interviewId}/evaluation`, {
- credentials: 'include',
- });
-
- if (res.status === 404) return null;
- if (!res.ok) throw new Error('평가 조회 실패');
-
- const body: ApiResponse = await res.json();
+ const res = await client.request(
+ `/api/v1/interviews/${interviewId}/evaluation`,
+ {},
+ '평가 조회 실패'
+ );
- // data 없으면 null 넘기기
- return body.data ?? null;
+ return res ?? null;
}
// 2) 면접 평가 생성 (POST)
export async function createInterviewEvaluation(
+ client: ClientRequestType,
interviewId: number,
payload: { scoreTech: number; scoreComm: number; scoreOverall: number; comment: string }
): Promise {
- const res = await fetch(`${API_BASE}/api/v1/interviews/${interviewId}/evaluation`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- credentials: 'include',
- body: JSON.stringify(payload),
- });
-
- if (!res.ok) throw new Error('평가 생성 실패');
-
- const body: ApiResponse = await res.json();
+ const res = await client.request(
+ `/api/v1/interviews/${interviewId}/evaluation`,
+ {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: payload,
+ },
+ '평가 생성 실패'
+ );
- // 여기서 한 번 체크해서 TS 한테 "undefined 아님"이라고 확신 주기
- if (!body.data) {
- throw new Error('평가 생성 응답에 data가 없습니다.');
- }
+ if (!res) throw new Error('평가 생성 응답에 data가 없습니다.');
- return body.data;
+ return res;
}
// 3) 면접 평가 수정 (PATCH)
export async function updateInterviewEvaluation(
+ client: ClientRequestType,
interviewId: number,
evaluationId: number,
payload: { scoreTech: number; scoreComm: number; scoreOverall: number; comment: string }
): Promise {
- const res = await fetch(
- `${API_BASE}/api/v1/interviews/${interviewId}/evaluation/${evaluationId}`,
+ const res = await client.request(
+ `/api/v1/interviews/${interviewId}/evaluation/${evaluationId}`,
{
method: 'PATCH',
- headers: { 'Content-Type': 'application/json' },
- credentials: 'include',
- body: JSON.stringify(payload),
- }
+ body: payload,
+ },
+ '평가 수정 실패'
);
- if (!res.ok) throw new Error('평가 수정 실패');
-
- const body: ApiResponse = await res.json();
-
- if (!body.data) {
- throw new Error('평가 수정 응답에 data가 없습니다.');
- }
+ if (!res) throw new Error('평가 수정 응답에 data가 없습니다.');
- return body.data;
+ return res;
}
diff --git a/src/features/interview/api/file.api.ts b/src/features/interview/api/file.api.ts
index 82209cb4..99698a73 100644
--- a/src/features/interview/api/file.api.ts
+++ b/src/features/interview/api/file.api.ts
@@ -1,5 +1,6 @@
// src/features/interview/api/file.api.ts
-import { authedRequest } from '@/lib/utils/authedRequest';
+
+import type { ClientRequestType } from '@shared/hooks/useAuthClient.ts';
type CommonResponse = {
errorCode?: number;
@@ -8,12 +9,12 @@ type CommonResponse = {
};
/** 로그인 사용자(Authorization)로 presigned download url 받기 */
-export async function getPresignedDownloadUrlAuthed(fileKey: string): Promise {
- const raw = await authedRequest>(
+export async function getPresignedDownloadUrlAuthed(client: ClientRequestType, fileKey: string) {
+ const raw = await client.request>(
`/api/v1/files/download?fileKey=${encodeURIComponent(fileKey)}`,
{ method: 'GET' }
);
- return raw.data;
+ return raw?.data; // 너 코드 스타일에 맞춰 조정
}
/** 게스트(Interview-Token 헤더)로 presigned download url 받기 */
diff --git a/src/features/interview/api/interview-detail.api.ts b/src/features/interview/api/interview-detail.api.ts
index b79a52c3..b3add747 100644
--- a/src/features/interview/api/interview-detail.api.ts
+++ b/src/features/interview/api/interview-detail.api.ts
@@ -1,11 +1,5 @@
// src/features/interview/api/interview-detail.api.ts
-import { authedRequest } from '@/lib/utils/authedRequest';
-
-type CommonResponse = {
- errorCode?: number;
- message: string;
- data: T;
-};
+import type { ClientRequestType } from '@shared/hooks/useAuthClient.ts';
export type InterviewDetail = {
interviewId: number;
@@ -25,42 +19,28 @@ export type InterviewDetail = {
};
/** 로그인 사용자(Authorization)로 호출 */
-export async function getInterviewDetailAuthed(interviewId: number): Promise {
- const raw = await authedRequest>(
- `/api/v1/interviews/${interviewId}`,
- {
- method: 'GET',
- }
- );
- return raw.data;
+export async function getInterviewDetailAuthed(
+ client: ClientRequestType,
+ interviewId: number
+): Promise {
+ const raw = await client.request(`/api/v1/interviews/${interviewId}`, {
+ method: 'GET',
+ });
+ return raw!;
}
/** 게스트(Interview-Token 헤더)로 호출 */
export async function getInterviewDetailWithGuestToken(
+ client: ClientRequestType,
interviewId: number,
guestToken: string
): Promise {
- const BASE_URL = (import.meta.env.VITE_API_URL || '').replace(/\/$/, '');
- const url = `${BASE_URL}/api/v1/interviews/${interviewId}`;
-
- const res = await fetch(url, {
+ const res = await client.request(`/api/v1/interviews/${interviewId}`, {
method: 'GET',
headers: {
'Interview-Token': guestToken,
},
- credentials: 'include',
});
- let body: any = null;
- try {
- body = await res.json();
- } catch {
- body = null;
- }
-
- if (!res.ok) {
- throw new Error(body?.message ?? `요청 실패 (status=${res.status})`);
- }
-
- return (body?.data ?? body) as InterviewDetail;
+ return res!;
}
diff --git a/src/features/interview/api/interview.api.ts b/src/features/interview/api/interview.api.ts
index 68fec40c..52043808 100644
--- a/src/features/interview/api/interview.api.ts
+++ b/src/features/interview/api/interview.api.ts
@@ -1,13 +1,7 @@
-import { authedRequest } from '@/lib/utils/authedRequest';
+import type { ClientRequestType } from '@shared/hooks/useAuthClient.ts';
-type CommonResponse = {
- errorCode: number;
- message: string;
- data: T;
-};
-
-export async function endInterview(interviewId: number): Promise {
- await authedRequest>(`/api/v1/interviews/${interviewId}/end`, {
+export async function endInterview(client: ClientRequestType, interviewId: number): Promise {
+ await client.request(`/api/v1/interviews/${interviewId}/end`, {
method: 'PATCH',
});
}
diff --git a/src/features/interview/api/me.authed.api.ts b/src/features/interview/api/me.authed.api.ts
index 5693962e..15c69d49 100644
--- a/src/features/interview/api/me.authed.api.ts
+++ b/src/features/interview/api/me.authed.api.ts
@@ -1,10 +1,4 @@
-import { authedRequest } from '@/lib/utils/authedRequest';
-
-type CommonResponse = {
- errorCode: number;
- message: string;
- data: T;
-};
+import type { ClientRequestType } from '@shared/hooks/useAuthClient.ts';
export type Me = {
id: number;
@@ -12,7 +6,7 @@ export type Me = {
nickname?: string;
};
-export async function getMeAuthed(): Promise {
- const raw = await authedRequest>('/api/v1/users/me', { method: 'GET' });
- return raw.data;
+export async function getMeAuthed(client: ClientRequestType): Promise {
+ const raw = await client.request('/api/v1/users/me', { method: 'GET' });
+ return raw!;
}
diff --git a/src/features/interview/api/profile.api.ts b/src/features/interview/api/profile.api.ts
index 20f8e9b5..c52e12af 100644
--- a/src/features/interview/api/profile.api.ts
+++ b/src/features/interview/api/profile.api.ts
@@ -1,10 +1,5 @@
-// src/features/interview/api/profile.api.ts
-import { authedRequest } from '@/lib/utils/authedRequest';
-
-type CommonResponse = {
- message: string;
- data: T;
-};
+import type { ClientRequestType } from '@shared/hooks/useAuthClient.ts';
+import { unwrap } from '@lib/utils/base.ts';
export type UserProfile = {
id: number;
@@ -18,21 +13,30 @@ export type ResumeProfile = {
resumeFileUrl?: string;
};
-export async function getUserProfileAuthed(userId: number): Promise {
- const raw = await authedRequest>(`/api/v1/users/${userId}`, {
+export async function getUserProfileAuthed(
+ client: ClientRequestType,
+ userId: number
+): Promise {
+ const raw = await client.request(`/api/v1/users/${userId}`, {
method: 'GET',
});
- return raw.data;
+
+ return unwrap(raw);
}
-export async function getResumeAuthed(resumeId: number): Promise {
- const raw = await authedRequest>(`/api/v1/resumes/${resumeId}`, {
+export async function getResumeAuthed(
+ client: ClientRequestType,
+ resumeId: number
+): Promise {
+ const raw = await client.request(`/api/v1/resumes/${resumeId}`, {
method: 'GET',
});
- return raw.data;
+
+ return unwrap(raw);
}
/** ✅ 게스트(Interview-Token)로 resume 조회 */
+/* TODO : 미사용 로직 삭제
export async function getResumeWithGuestToken(
resumeId: number,
guestToken: string
@@ -59,3 +63,4 @@ export async function getResumeWithGuestToken(
return (body?.data ?? body) as ResumeProfile;
}
+*/
diff --git a/src/features/interview/api/question.api.ts b/src/features/interview/api/question.api.ts
index 48b76a16..2ec49baa 100644
--- a/src/features/interview/api/question.api.ts
+++ b/src/features/interview/api/question.api.ts
@@ -1,4 +1,4 @@
-import { authedRequest } from '@/lib/utils/authedRequest';
+import type { ClientRequestType } from '@shared/hooks/useAuthClient.ts';
export interface InterviewQuestion {
questionId: number;
@@ -26,90 +26,81 @@ export interface InterviewMemo {
updatedAt: string;
}
-type CommonResponse = {
- errorCode: number;
- message: string;
- data: T;
-};
-
-export async function getInterviewQuestions(interviewId: number): Promise {
- const res = await authedRequest>(
+export async function getInterviewQuestions(
+ client: ClientRequestType,
+ interviewId: number
+): Promise {
+ const res = await client.request(
`/api/v1/interviews/${interviewId}/questions`,
{ method: 'GET' }
);
- return res.data ?? [];
+
+ return res ?? [];
}
export async function toggleQuestionCheck(
+ client: ClientRequestType,
interviewId: number,
questionId: number
): Promise {
- const res = await authedRequest>(
+ const res = await client.request(
`/api/v1/interviews/${interviewId}/questions/${questionId}/toggle-check`,
{ method: 'PATCH' }
);
- return res.data;
+
+ return res!;
}
-export async function getChatHistory(interviewId: number): Promise {
- const res = await authedRequest>(
- `/api/v1/interviews/${interviewId}/chat`,
- { method: 'GET' }
- );
- return res.data ?? [];
+export async function getChatHistory(
+ client: ClientRequestType,
+ interviewId: number
+): Promise {
+ const res = await client.request(`/api/v1/interviews/${interviewId}/chat`, {
+ method: 'GET',
+ });
+
+ return res ?? [];
}
export async function getChatHistoryWithGuestToken(
+ client: ClientRequestType,
interviewId: number,
guestToken: string
): Promise {
- const BASE_URL = (import.meta.env.VITE_API_URL || '').replace(/\/$/, '');
- const url = `${BASE_URL}/api/v1/interviews/${interviewId}/chat`;
-
- const res = await fetch(url, {
+ const res = await client.request(`/api/v1/interviews/${interviewId}/chat`, {
method: 'GET',
headers: {
'Interview-Token': guestToken,
},
- credentials: 'include',
});
- let body: unknown = null;
- try {
- body = await res.json();
- } catch {
- body = null;
- }
-
- if (!res.ok) {
- const message = (body as any)?.message ?? `요청 실패 (status=${res.status})`;
- throw new Error(message);
- }
-
- const response = body as CommonResponse;
- return response.data ?? [];
+ return res ?? [];
}
-export async function getInterviewMemos(interviewId: number): Promise {
- const res = await authedRequest>(
- `/api/v1/interviews/${interviewId}/memos`,
- { method: 'GET' }
- );
- return res.data ?? [];
+export async function getInterviewMemos(
+ client: ClientRequestType,
+ interviewId: number
+): Promise {
+ const res = await client.request(`/api/v1/interviews/${interviewId}/memos`, {
+ method: 'GET',
+ });
+
+ return res ?? [];
}
export async function updateInterviewMemo(
+ client: ClientRequestType,
interviewId: number,
memoId: number,
content: string
): Promise<{ memoId: number; content: string; updatedAt: string }> {
- const res = await authedRequest<
- CommonResponse<{ memoId: number; content: string; updatedAt: string }>
- >(`/api/v1/interviews/${interviewId}/memos/${memoId}`, {
- method: 'PATCH', // 서버가 PUT이면 PUT으로 변경
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ content }),
- });
+ const res = await client.request<{ memoId: number; content: string; updatedAt: string }>(
+ `/api/v1/interviews/${interviewId}/memos/${memoId}`,
+ {
+ method: 'PATCH', // 서버가 PUT이면 PUT으로 변경
+ body: { content },
+ }
+ );
- return res.data;
+ return res!;
}
diff --git a/src/features/interview/components/chat/ChatSection.tsx b/src/features/interview/components/chat/ChatSection.tsx
index 673f2f21..b60b9f21 100644
--- a/src/features/interview/components/chat/ChatSection.tsx
+++ b/src/features/interview/components/chat/ChatSection.tsx
@@ -1,6 +1,6 @@
-// src/components/chat/ChatSection.tsx
import { useEffect, useRef, useState } from 'react';
import { DEFAULT_AVATAR } from '../../util/avatar';
+import { debugLog } from '@lib/utils/debugLog.ts';
type UiMsg = { id: number; text: string; senderId: number; isMine: boolean };
@@ -20,7 +20,8 @@ export default function ChatSection({
const messagesEndRef = useRef(null);
useEffect(() => {
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
+ debugLog('initialMessages : ', initialMessages);
}, [initialMessages]);
const handleSend = () => {
@@ -31,7 +32,7 @@ export default function ChatSection({
};
return (
-
+
{initialMessages.length === 0 ? (
diff --git a/src/features/interview/components/chat/InterviewSummarySection.tsx b/src/features/interview/components/chat/InterviewSummarySection.tsx
index a1c00de6..519f845b 100644
--- a/src/features/interview/components/chat/InterviewSummarySection.tsx
+++ b/src/features/interview/components/chat/InterviewSummarySection.tsx
@@ -68,7 +68,7 @@ export default function InterviewSummarySection({
);
return (
-
+
{/* 새 메모 작성 영역 */}
{!isWriting && !mySummary && (
diff --git a/src/features/interview/components/chat/QuestionNoteSection.tsx b/src/features/interview/components/chat/QuestionNoteSection.tsx
index 7142451e..8e9ff03a 100644
--- a/src/features/interview/components/chat/QuestionNoteSection.tsx
+++ b/src/features/interview/components/chat/QuestionNoteSection.tsx
@@ -1,6 +1,7 @@
import { useState } from 'react';
import { Check } from 'lucide-react';
import type { QuestionSection } from '../../types/chatroom';
+import BlankCard from '@shared/components/BlankCard.tsx';
interface QuestionNoteSectionProps {
questionNotes: QuestionSection[];
@@ -34,35 +35,39 @@ export default function QuestionNoteSection({
};
return (
-
+
질문 노트
- {questionNotes.map((section) => (
-
-
{section.topic}
-
- {section.questions.map((q) => (
- -
- {q.content}
-
+ ))}
+
+
+ ))
+ )}
);
diff --git a/src/features/interview/components/create/ApplicantProfileCard.tsx b/src/features/interview/components/create/ApplicantProfileCard.tsx
index 6b3e8b76..53250497 100644
--- a/src/features/interview/components/create/ApplicantProfileCard.tsx
+++ b/src/features/interview/components/create/ApplicantProfileCard.tsx
@@ -15,7 +15,7 @@ export default function ApplicantProfileCard({ applicant }: ApplicantProfile) {
const { name, email, phoneNumber, birthDate, avatar, jdTitle } = applicant;
return (
-
+

(
handleInvite(i)}
>
![]()
{i.email}
-
handleInvite(i)}
- className="rounded-md bg-[#E35B43] px-4 py-1.5 text-sm font-semibold text-white hover:bg-[#d44f3a]"
- >
- 선택
-
))
) : (
diff --git a/src/features/interview/components/create/InvitedList.tsx b/src/features/interview/components/create/InvitedList.tsx
index d73fc634..69a4093e 100644
--- a/src/features/interview/components/create/InvitedList.tsx
+++ b/src/features/interview/components/create/InvitedList.tsx
@@ -22,7 +22,10 @@ export default function InvitedList({ invited, handleRemove }: Props) {
alt={i.name}
className="h-10 w-10 rounded-full object-cover"
/>
-
{i.name}
+
+ {i.name}
+ {i.email}
+
{/* 오른쪽 상단 X 버튼 */}
diff --git a/src/features/interview/components/manage/InterviewActions.tsx b/src/features/interview/components/manage/InterviewActions.tsx
index 0b30356b..0754b42f 100644
--- a/src/features/interview/components/manage/InterviewActions.tsx
+++ b/src/features/interview/components/manage/InterviewActions.tsx
@@ -42,7 +42,7 @@ export default function InterviewActions({
const handleOpenInterviewNote = () => {
if (!interviewId) return;
navigate(`/interview/${interviewId}/note`, {
- state: { name, avatar, interviewId },
+ state: { name, avatar, date, time, interviewers, position, resumeId, interviewId },
});
};
diff --git a/src/features/interview/components/manage/InterviewCard.tsx b/src/features/interview/components/manage/InterviewCard.tsx
index aabea728..1bdbc591 100644
--- a/src/features/interview/components/manage/InterviewCard.tsx
+++ b/src/features/interview/components/manage/InterviewCard.tsx
@@ -32,25 +32,21 @@ export default function InterviewCard({
const [currResult, setResult] = useState
(result);
return (
-
- {/* × */}
+
+
- {/* 헤더 영역 */}
-
-
- {/* 인터뷰 정보 */}
-
-
- {/* 버튼 영역 */}
+
+
-
+
);
}
diff --git a/src/features/interview/components/manage/InterviewFilterTabs.tsx b/src/features/interview/components/manage/InterviewFilterTabs.tsx
index 37a08368..a454378b 100644
--- a/src/features/interview/components/manage/InterviewFilterTabs.tsx
+++ b/src/features/interview/components/manage/InterviewFilterTabs.tsx
@@ -1,4 +1,5 @@
import type { TabStatus } from '../../types/interviewer';
+import cn from '@lib/utils/cn.ts';
interface InterviewFilterTabsProps {
activeTab: TabStatus;
@@ -17,16 +18,17 @@ export default function InterviewFilterTabs({ activeTab, onChange }: InterviewFi
};
return (
-
+
{tabs.map((tab) => {
const isActive = activeTab === tab;
return (
onChange(tab)}
- className={`rounded-full px-6 py-2 text-sm font-semibold transition-all duration-200 ${
+ className={cn(
+ `rounded-full px-6 py-2 text-sm font-semibold whitespace-nowrap transition-all duration-200`,
isActive ? 'bg-jd-scarlet text-white shadow-sm' : 'text-gray-600 hover:bg-[#fdeae7]'
- } `}
+ )}
>
{tabLabels[tab]}
diff --git a/src/features/interview/components/manage/InterviewHeader.tsx b/src/features/interview/components/manage/InterviewHeader.tsx
index d0351f51..507323f9 100644
--- a/src/features/interview/components/manage/InterviewHeader.tsx
+++ b/src/features/interview/components/manage/InterviewHeader.tsx
@@ -4,6 +4,7 @@ import {
statusLabelMap,
resultStatusLabelMap,
} from '../../types/interviewer';
+import cn from '@lib/utils/cn.ts';
interface InterviewHeaderProps {
avatar: string;
@@ -46,9 +47,15 @@ export default function InterviewHeader({
{name}
-
{position}
+
{position}
-
+
{label}
diff --git a/src/features/interview/components/manage/InterviewSortDropdown.tsx b/src/features/interview/components/manage/InterviewSortDropdown.tsx
index cb333564..0d6ac485 100644
--- a/src/features/interview/components/manage/InterviewSortDropdown.tsx
+++ b/src/features/interview/components/manage/InterviewSortDropdown.tsx
@@ -39,16 +39,16 @@ export default function InterviewSortDropdown({ jobPosts, onSelect }: InterviewS
};
return (
-
+
setIsOpen(!isOpen)}
- className="text-jd-gray-black hover:text-jd-black translate-y-17 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm transition"
+ className="text-jd-gray-black hover:text-jd-black rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm transition"
>
{selected}
{isOpen && (
-
+
{/* 전체 보기 */}
handleSelect(null)}
diff --git a/src/features/interview/components/note/InterviewNoteSummarySection.tsx b/src/features/interview/components/note/InterviewNoteSummarySection.tsx
index 750ed59d..ee56b0a9 100644
--- a/src/features/interview/components/note/InterviewNoteSummarySection.tsx
+++ b/src/features/interview/components/note/InterviewNoteSummarySection.tsx
@@ -42,61 +42,66 @@ export default function InterviewNoteSummarySection({
};
return (
-
+
- {/* 여기에는 "+ 메모 작성하기" 버튼 없음! (노트 페이지에서는 추가 X) */}
+
면접관 노트
+ {summaryList.length > 0 ? (
+ summaryList.map((item, idx) => {
+ const isMine = item.authorId === currentUserId;
+ const isEditing = editingIdx === idx;
- {summaryList.map((item, idx) => {
- const isMine = item.authorId === currentUserId;
- const isEditing = editingIdx === idx;
-
- return (
-
- {isEditing ? (
- <>
-
{item.title}
-
+ );
+ })
+ ) : (
+
+ 작성된 면접 노트가 없습니다.
+
+ )}
);
diff --git a/src/features/interview/components/note/ScoreInputCard.tsx b/src/features/interview/components/note/ScoreInputCard.tsx
index 289d422e..ca02799b 100644
--- a/src/features/interview/components/note/ScoreInputCard.tsx
+++ b/src/features/interview/components/note/ScoreInputCard.tsx
@@ -4,6 +4,7 @@ import {
getInterviewEvaluation,
updateInterviewEvaluation,
} from '@features/interview/api/evaluation.api.ts';
+import { useAuthedClient } from '@shared/hooks/useAuthClient.ts';
interface Scores {
tech: string;
@@ -17,7 +18,7 @@ interface ScoreInputCardProps {
}
export default function ScoreInputCard({ interviewId }: ScoreInputCardProps) {
- const [isEditing, setIsEditing] = useState(true);
+ const [isEditing, setIsEditing] = useState(false);
const [evaluationId, setEvaluationId] = useState
(null);
const [scores, setScores] = useState({
tech: '',
@@ -25,12 +26,14 @@ export default function ScoreInputCard({ interviewId }: ScoreInputCardProps) {
total: '',
comment: '',
});
+ const client = useAuthedClient();
+
useEffect(() => {
if (!interviewId) return;
(async () => {
try {
- const data = await getInterviewEvaluation(interviewId);
+ const data = await getInterviewEvaluation(client, interviewId);
if (!data) {
// 아직 평가 없음 → 입력 모드
setIsEditing(true);
@@ -96,11 +99,11 @@ export default function ScoreInputCard({ interviewId }: ScoreInputCardProps) {
try {
if (evaluationId == null) {
// 처음 저장 (POST)
- const created = await createInterviewEvaluation(interviewId, payload);
+ const created = await createInterviewEvaluation(client, interviewId, payload);
setEvaluationId(created.evaluationId);
} else {
// 이미 있으면 수정 (PATCH)
- await updateInterviewEvaluation(interviewId, evaluationId, payload);
+ await updateInterviewEvaluation(client, interviewId, evaluationId, payload);
}
alert('면접 점수가 저장되었습니다.');
diff --git a/src/features/interview/components/question-create/ProfileCard.tsx b/src/features/interview/components/question-create/ProfileCard.tsx
index ec2edc06..34c93cdc 100644
--- a/src/features/interview/components/question-create/ProfileCard.tsx
+++ b/src/features/interview/components/question-create/ProfileCard.tsx
@@ -1,65 +1,105 @@
+import { Skeleton } from '@shared/components/Skeleton.tsx';
+import type { ProfileData } from '@features/interview/pages/InterviewNotePage.tsx';
+
interface ProfileCardProps {
- profileData: any;
+ profileData: ProfileData | null;
name?: string;
avatar?: string;
}
export default function ProfileCard({ profileData, name, avatar }: ProfileCardProps) {
return (
-
+
-

+ {profileData ? (
+

+ ) : (
+
+ )}
+
-
{name || profileData.name}
-
{profileData.title}
+ {profileData ? (
+
{profileData.name}
+ ) : (
+
+ )}
+
+ {profileData ? (
+
{profileData.title}
+ ) : (
+
+ )}
일자
- {profileData.date}
+ {profileData ? (
+ {profileData.date}
+ ) : (
+
+ )}
시간
- {profileData.time}
+ {profileData ? (
+ {profileData.time}
+ ) : (
+
+ )}
면접관
- {profileData.interviewers.join(', ')}
+ {profileData ? (
+ {profileData.interviewers.join(', ')}
+ ) : (
+
+ )}
보유 스킬
- {profileData.skills.map((skill: string) => (
-
- {skill}
-
- ))}
+ {profileData ? (
+ profileData.skills.map((skill: string) => (
+
+ {skill}
+
+ ))
+ ) : (
+
+ )}
부족 스킬
- {profileData.missingSkills.map((s: string) => (
-
- {s}
-
- ))}
+ {profileData ? (
+ profileData.missingSkills.map((s: string) => (
+
+ {s}
+
+ ))
+ ) : (
+
+ )}
- {profileData.experiences.map((exp: string, i: number) => (
- - {exp}
- ))}
+ {profileData
+ ? profileData.experiences.map((exp: string, i: number) => - {exp}
)
+ : null}
);
diff --git a/src/features/interview/pages/InterviewChatRoomGuest.tsx b/src/features/interview/pages/InterviewChatRoomGuest.tsx
index ed452625..e9347824 100644
--- a/src/features/interview/pages/InterviewChatRoomGuest.tsx
+++ b/src/features/interview/pages/InterviewChatRoomGuest.tsx
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import ChatSection from '../components/chat/ChatSection';
-import useInterviewSocket from '@/hooks/useInterviewSocket';
+import useInterviewSocket from '@shared/hooks/useInterviewSocket';
import { MessageType, type OutgoingChatMessage } from '@/features/interview/types/chatroom';
import { getInterviewDetailWithGuestToken } from '@/features/interview/api/interview-detail.api';
@@ -12,6 +12,7 @@ import {
} from '@/features/interview/api/question.api';
import { DEFAULT_AVATAR, normalizeAvatarUrl } from '../util/avatar';
+import { useAuthedClient } from '@shared/hooks/useAuthClient.ts';
type UiMsg = { id: number; text: string; senderId: number; isMine: boolean };
const GUEST_SENDER_ID = 0;
@@ -30,6 +31,8 @@ export default function InterviewChatRoomGuest() {
const [_candidateAvatar, setCandidateAvatar] = useState
(DEFAULT_AVATAR);
const [_resumeId, setResumeId] = useState(null);
+ const client = useAuthedClient();
+
const pendingRef = useRef<{ content: string; at: number }[]>([]);
const cleanupPending = useCallback(() => {
const now = Date.now();
@@ -51,7 +54,7 @@ export default function InterviewChatRoomGuest() {
(async () => {
try {
- const detail = await getInterviewDetailWithGuestToken(interviewId, interviewToken);
+ const detail = await getInterviewDetailWithGuestToken(client, interviewId, interviewToken);
console.log('[GUEST DETAIL]', detail);
setResumeId(detail.resumeId ?? null);
@@ -73,7 +76,7 @@ export default function InterviewChatRoomGuest() {
(async () => {
try {
- const history = await getChatHistoryWithGuestToken(interviewId, interviewToken);
+ const history = await getChatHistoryWithGuestToken(client, interviewId, interviewToken);
const mapped: UiMsg[] = history.map((m: ChatMessage) => ({
id: m.id,
diff --git a/src/features/interview/pages/InterviewChatRoomPage.tsx b/src/features/interview/pages/InterviewChatRoomPage.tsx
index 99c67618..9839c92e 100644
--- a/src/features/interview/pages/InterviewChatRoomPage.tsx
+++ b/src/features/interview/pages/InterviewChatRoomPage.tsx
@@ -20,7 +20,7 @@ import {
type InterviewMemo,
} from '@/features/interview/api/question.api';
-import useInterviewSocket from '@/hooks/useInterviewSocket';
+import useInterviewSocket from '@shared/hooks/useInterviewSocket';
import {
MessageType,
@@ -34,6 +34,7 @@ import { getResumeAuthed, getUserProfileAuthed } from '@/features/interview/api/
import { DEFAULT_AVATAR, normalizeAvatarUrl, toNumId } from '../util/avatar';
import { getInterviewDetailAuthed } from '@/features/interview/api/interview-detail.api';
import { getPresignedDownloadUrlAuthed } from '@/features/interview/api/file.api';
+import { useAuthedClient } from '@shared/hooks/useAuthClient.ts';
type UiMsg = { id: number; text: string; senderId: number; isMine: boolean };
@@ -53,10 +54,13 @@ export default function InterviewChatRoomPage() {
const [candidateAvatar, setCandidateAvatar] = useState(DEFAULT_AVATAR);
const [resumeId, setResumeId] = useState(null);
+ const client = useAuthedClient();
+
useEffect(() => {
+ console.log(avatarBySender);
(async () => {
try {
- const user = await getMeAuthed();
+ const user = await getMeAuthed(client);
console.log('[ME LOADED]', user);
setMe(user);
} catch (error) {
@@ -70,7 +74,7 @@ export default function InterviewChatRoomPage() {
(async () => {
try {
- const detail = await getInterviewDetailAuthed(interviewId);
+ const detail = await getInterviewDetailAuthed(client, interviewId);
console.log('[DETAIL]', detail);
setResumeId(detail.resumeId ?? null);
@@ -91,7 +95,7 @@ export default function InterviewChatRoomPage() {
(async () => {
try {
- const history = await getChatHistory(interviewId);
+ const history = await getChatHistory(client, interviewId);
console.log('[HISTORY RAW]', history);
const mapped: UiMsg[] = history.map((m: ChatMessage) => ({
@@ -134,7 +138,7 @@ export default function InterviewChatRoomPage() {
return;
}
- const resume = await getResumeAuthed(resumeId);
+ const resume = await getResumeAuthed(client, resumeId);
const fileKey = resume.resumeFileUrl;
console.log('[CANDIDATE AVATAR] resumeFileUrl', fileKey);
@@ -144,16 +148,16 @@ export default function InterviewChatRoomPage() {
return;
}
- const presigned = await getPresignedDownloadUrlAuthed(fileKey);
+ const presigned = await getPresignedDownloadUrlAuthed(client, fileKey);
console.log('[CANDIDATE AVATAR] presigned OK', presigned);
- setAvatarBySender((prev) => ({ ...prev, 0: presigned }));
+ if (presigned) setAvatarBySender((prev) => ({ ...prev, 0: presigned }));
} catch (e) {
console.error('[CANDIDATE AVATAR] FAIL', e);
setAvatarBySender((prev) => ({ ...prev, 0: DEFAULT_AVATAR }));
}
})();
- }, [interviewId, resumeId, candidateAvatar]);
+ }, [interviewId, resumeId, candidateAvatar, client]);
useEffect(() => {
if (messages.length === 0) return;
@@ -177,7 +181,7 @@ export default function InterviewChatRoomPage() {
await Promise.all(
missing.map(async (senderId) => {
try {
- const user = await getUserProfileAuthed(senderId);
+ const user = await getUserProfileAuthed(client, senderId);
console.log('[AVATAR] user raw', user);
updates[senderId] = normalizeAvatarUrl(user.profileUrl) ?? DEFAULT_AVATAR;
@@ -201,7 +205,7 @@ export default function InterviewChatRoomPage() {
(async () => {
try {
- const questions = await getInterviewQuestions(interviewId);
+ const questions = await getInterviewQuestions(client, interviewId);
const map = new Map();
questions.forEach((q: InterviewQuestion) => {
@@ -231,7 +235,7 @@ export default function InterviewChatRoomPage() {
(async () => {
try {
- const memos = await getInterviewMemos(interviewId);
+ const memos = await getInterviewMemos(client, interviewId);
const memoMap = new Map();
memos.forEach((memo: InterviewMemo) => {
@@ -328,7 +332,7 @@ export default function InterviewChatRoomPage() {
});
const handleToggleQuestionCheck = async (questionId: number) => {
- await toggleQuestionCheck(interviewId, questionId);
+ await toggleQuestionCheck(client, interviewId, questionId);
setQuestionNotes((prev) =>
prev.map((section) => ({
...section,
@@ -367,7 +371,7 @@ export default function InterviewChatRoomPage() {
};
const handleUpdateMemo = async (memoId: number, content: string) => {
- const data = await updateInterviewMemo(interviewId, memoId, content);
+ const data = await updateInterviewMemo(client, interviewId, memoId, content);
setSummaries((prev) =>
prev.map((s) => (s.id === data.memoId ? { ...s, content: data.content } : s))
);
@@ -375,7 +379,7 @@ export default function InterviewChatRoomPage() {
const handleEndInterview = async () => {
try {
- await endInterview(interviewId);
+ await endInterview(client, interviewId);
navigate('/interview/manage');
} catch (e) {
console.error('면접 종료 실패:', e);
@@ -383,8 +387,8 @@ export default function InterviewChatRoomPage() {
};
return (
-