Skip to content

Commit 27bc7f7

Browse files
committed
Feat: 정적배포용 api client 구현
1 parent e6eda95 commit 27bc7f7

5 files changed

Lines changed: 156 additions & 16 deletions

File tree

src/app/admin/layout.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
import MenuHeader from '@/components/ui/common/MenuHeader';
5+
import ApiCodeGuard from '@/components/auth/ApiCodeGuard.jsx';
56

67
export const metadata = {
78
title: "Admin",
@@ -10,9 +11,11 @@ export const metadata = {
1011

1112
export default function AdminLayout({ children }) {
1213
return (
13-
<>
14-
<MenuHeader />
15-
{children}
16-
</>
14+
<ApiCodeGuard>
15+
<>
16+
<MenuHeader />
17+
{children}
18+
</>
19+
</ApiCodeGuard>
1720
);
1821
}

src/app/admin/page.jsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,16 @@ const statusColorMap = {
3333
};
3434

3535
export default function Page() {
36-
const [isLoading, setIsLoading] = useState(true);
36+
const [isLoading, setIsLoading] = useState(false);
3737
const router = useRouter();
3838

39-
useEffect(() => {
40-
if(!document.referrer) {
41-
router.push('/')
42-
} else {
43-
setIsLoading(false);
44-
}
45-
}, []);
39+
// useEffect(() => {
40+
// if(!document.referrer) {
41+
// router.push('/')
42+
// } else {
43+
// setIsLoading(false);
44+
// }
45+
// }, []);
4646
// 추후 users 를 사용해 데이터를 받아오고, totalUsers를 사용해 페이지네이션 만들 예정
4747
const [page, setPage] = React.useState(1); // 현재 페이지 상태 (추후 페이지 상태에 따라 api 통신으로 데이터 불러오기)
4848

src/app/main/layout.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//import { Suspense } from "react";
22
import MenuHeader from "@/components/ui/common/MenuHeader";
3+
import ApiCodeGuard from '@/components/auth/ApiCodeGuard.jsx';
34

45
export const metadata = {
56
title: "Home",
@@ -8,9 +9,11 @@ export const metadata = {
89

910
export default function HomeLayout({ children }) {
1011
return (
11-
<>
12-
<MenuHeader />
13-
{children}
14-
</>
12+
<ApiCodeGuard>
13+
<>
14+
<MenuHeader />
15+
{children}
16+
</>
17+
</ApiCodeGuard>
1518
);
1619
}

src/app/test/page.jsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'use client'
2+
3+
import { useState, useMemo } from 'react';
4+
import { useAuthenticatedApi } from '@/hooks/useAuthenticatedApi';
5+
import { useAuth } from '@/hooks/useAuth';
6+
7+
export default function TestAuthPage() {
8+
const { apiClient } = useAuthenticatedApi();
9+
const { accessToken } = useAuth();
10+
11+
const isLoggedIn = Boolean(accessToken);
12+
const maskedToken = useMemo(() => {
13+
if (!accessToken) return '-';
14+
const head = String(accessToken).slice(0, 8);
15+
const tail = String(accessToken).slice(-6);
16+
return `${head}...${tail}`;
17+
}, [accessToken]);
18+
19+
const [loading, setLoading] = useState(false);
20+
const [httpStatus, setHttpStatus] = useState(null);
21+
const [payload, setPayload] = useState(null);
22+
const [errorText, setErrorText] = useState('');
23+
24+
const handleCallAuthApi = async () => {
25+
setLoading(true);
26+
setErrorText('');
27+
setPayload(null);
28+
setHttpStatus(null);
29+
try {
30+
const res = await apiClient.get('/recruit/members', {
31+
params: { page: 0, size: 20, sort: 'createdAt', dir: 'DESC' },
32+
});
33+
setHttpStatus(res.status);
34+
setPayload(res.data);
35+
} catch (err) {
36+
const status = err?.response?.status ?? 'NETWORK_ERROR';
37+
setHttpStatus(status);
38+
if (err?.response?.data) setPayload(err.response.data);
39+
setErrorText(String(err?.message || 'request failed'));
40+
} finally {
41+
setLoading(false);
42+
}
43+
};
44+
45+
return (
46+
<div className='min-h-screen w-full px-6 py-10 text-white'>
47+
<h1 className='text-2xl font-bold mb-6'>로그인 테스트</h1>
48+
49+
<section className='mb-6'>
50+
<div className='mb-1'>현재 로그인 상태</div>
51+
<div className={`inline-block rounded px-3 py-1 ${isLoggedIn ? 'bg-green-600' : 'bg-red-600'}`}>
52+
{isLoggedIn ? '로그인됨' : '미로그인'}
53+
</div>
54+
<div className='mt-2 text-sm text-gray-300'>액세스 토큰: {maskedToken}</div>
55+
</section>
56+
57+
<section className='mb-6'>
58+
<button
59+
onClick={handleCallAuthApi}
60+
disabled={loading}
61+
className={`rounded px-4 py-2 ${loading ? 'bg-gray-500' : 'bg-blue-600 hover:bg-blue-500'}`}
62+
>
63+
{loading ? '요청 중...' : '인증 API 호출 (/api/v1/recruit/members)'}
64+
</button>
65+
</section>
66+
67+
<section>
68+
<div className='mb-2 font-semibold'>응답</div>
69+
<div className='mb-1 text-sm'>HTTP Status: {httpStatus ?? '-'}</div>
70+
{errorText && (
71+
<div className='mb-2 text-red-400 text-sm'>에러: {errorText}</div>
72+
)}
73+
<pre className='bg-black/40 rounded p-3 overflow-auto max-h-[50vh] text-sm'>
74+
{JSON.stringify(payload, null, 2) || '-'}
75+
</pre>
76+
</section>
77+
</div>
78+
);
79+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use client'
2+
3+
import { useEffect, useState } from 'react';
4+
import { useRouter } from 'next/navigation';
5+
6+
import { useAuthenticatedApi } from '@/hooks/useAuthenticatedApi';
7+
import Loader from '@/components/ui/common/Loader';
8+
9+
export default function ApiCodeGuard({ children }) {
10+
const router = useRouter();
11+
const { apiClient } = useAuthenticatedApi();
12+
13+
const [checking, setChecking] = useState(true);
14+
const [allowed, setAllowed] = useState(false);
15+
16+
useEffect(() => {
17+
let cancelled = false;
18+
19+
const verifyAccess = async () => {
20+
try {
21+
const res = await apiClient.get('/recruit/members', {
22+
params: { page: 0, size: 20, sort: 'createdAt', dir: 'DESC' },
23+
});
24+
25+
const code = res?.data?.code;
26+
if (!cancelled) {
27+
if (code === 200) {
28+
setAllowed(true);
29+
} else {
30+
router.replace('/unauthorized');
31+
}
32+
}
33+
} catch (error) {
34+
if (!cancelled) {
35+
// 인터셉터에서 401/403 처리로 리다이렉트가 발생할 수 있으므로, 보조적으로 차단
36+
router.replace('/unauthorized');
37+
}
38+
} finally {
39+
if (!cancelled) {
40+
setChecking(false);
41+
}
42+
}
43+
};
44+
45+
verifyAccess();
46+
47+
return () => {
48+
cancelled = true;
49+
};
50+
}, [apiClient, router]);
51+
52+
if (checking) return <Loader isLoading={true} />;
53+
if (!allowed) return null;
54+
return children;
55+
}

0 commit comments

Comments
 (0)