Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
reactStrictMode: true,
|
||||
transpilePackages: ["@common/assets"],
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
ACCESS_TOKEN,
|
||||
AUTH_USER_ID,
|
||||
CLAIM_NAME,
|
||||
COOKIE_DOMAIN,
|
||||
REFRESH_TOKEN,
|
||||
SERVER_API_URL,
|
||||
SITE_ID,
|
||||
@@ -135,6 +136,7 @@ async function handler(
|
||||
cookieStore.set(REFRESH_TOKEN, refreshToken, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
domain: COOKIE_DOMAIN,
|
||||
path: "/",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@common/assets": "workspace:*",
|
||||
"@common/auth": "workspace:*",
|
||||
"@common/components": "workspace:*",
|
||||
"@common/config": "workspace:*",
|
||||
"@common/network": "workspace:*",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
|
||||
@@ -16,48 +16,32 @@ import {
|
||||
setupRefreshTimer,
|
||||
tokenManager,
|
||||
} from "../lib/token-manager";
|
||||
import { clearDeviceId } from "../lib/device-id";
|
||||
|
||||
function resolveLoginError(message: string | undefined): string {
|
||||
switch (message) {
|
||||
case "Invalid Credentials":
|
||||
return "아이디와 비밀번호가 일치하지 않습니다.";
|
||||
case "User not found":
|
||||
return "존재하지 않는 아이디입니다.";
|
||||
case "User is not active":
|
||||
return "활성화되지 않은 계정입니다.";
|
||||
case "User is not verified":
|
||||
return "이메일 인증이 필요합니다.";
|
||||
default:
|
||||
return message ?? "로그인에 실패했습니다.";
|
||||
}
|
||||
}
|
||||
import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id";
|
||||
|
||||
function completeSession(accessToken: string, tokenId: string): void {
|
||||
setToken(CLAIM_NAME, accessToken);
|
||||
setToken(AUTH_USER_ID, tokenId);
|
||||
|
||||
const remainingMs = getRemainingMs(accessToken);
|
||||
if (remainingMs > 0) {
|
||||
setupRefreshTimer(silentRefresh, remainingMs);
|
||||
}
|
||||
// const remainingMs = getRemainingMs(accessToken);
|
||||
// if (remainingMs > 0) {
|
||||
// setupRefreshTimer(silentRefresh, remainingMs);
|
||||
// }
|
||||
}
|
||||
|
||||
async function login(payload: AuthPayload): Promise<void> {
|
||||
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Device-Id": getOrCreateDeviceId(),
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(resolveLoginError(data?.message));
|
||||
}
|
||||
|
||||
const accessToken = data[ACCESS_TOKEN] as string | undefined;
|
||||
const tokenId = data[AUTH_USER_ID] as string | undefined;
|
||||
const accessToken = data[ACCESS_TOKEN];
|
||||
const tokenId = data[AUTH_USER_ID];
|
||||
|
||||
if (!accessToken || !tokenId) {
|
||||
throw new Error("인증 토큰을 받지 못했습니다.");
|
||||
@@ -71,7 +55,12 @@ async function silentRefresh(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`,
|
||||
{ method: "PUT" },
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"X-Device-Id": getOrCreateDeviceId(),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
@@ -80,8 +69,8 @@ async function silentRefresh(): Promise<void> {
|
||||
throw new Error(data?.message ?? "토큰 갱신에 실패했습니다.");
|
||||
}
|
||||
|
||||
const accessToken = data[ACCESS_TOKEN] as string | undefined;
|
||||
const tokenId = data[AUTH_USER_ID] as string | undefined;
|
||||
const accessToken = data[ACCESS_TOKEN];
|
||||
const tokenId = data[AUTH_USER_ID];
|
||||
|
||||
if (!accessToken || !tokenId) {
|
||||
throw new Error("갱신된 인증 토큰을 받지 못했습니다.");
|
||||
|
||||
@@ -22,12 +22,8 @@
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"@common/components": ["../../../packages/common/components/src/"],
|
||||
"@common/config": ["../../../packages/common/config/dist/"],
|
||||
"@common/network": ["../../../packages/common/network/"]
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
|
||||
@@ -70,7 +70,7 @@ async function handler(
|
||||
response.cookies.set(REFRESH_TOKEN, refreshToken, {
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
domain: getCookieDomain(),
|
||||
domain: COOKIE_DOMAIN,
|
||||
path: "/",
|
||||
});
|
||||
}
|
||||
@@ -105,14 +105,3 @@ export async function PUT(
|
||||
const params = await props.params;
|
||||
return handler(request, params, "PUT");
|
||||
}
|
||||
|
||||
function getCookieDomain(): string | undefined {
|
||||
if (typeof window === "undefined") return undefined;
|
||||
|
||||
if (COOKIE_DOMAIN) return COOKIE_DOMAIN;
|
||||
|
||||
const { hostname } = window.location;
|
||||
if (hostname === "localhost" || hostname === "127.0.0.1") return undefined;
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export function middleware(req: NextRequest) {
|
||||
|
||||
logMessages.push(` Rewritten URL: ${newUrl}`);
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
// console.log(logMessages.join("\n"));
|
||||
console.log(logMessages.join("\n"));
|
||||
|
||||
const response = NextResponse.rewrite(new URL(newUrl));
|
||||
return addSecurityHeaders(response);
|
||||
@@ -58,7 +58,7 @@ export function middleware(req: NextRequest) {
|
||||
|
||||
logMessages.push(` Rewritten URL: ${newUrl}`);
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
// console.log(logMessages.join("\n"));
|
||||
console.log(logMessages.join("\n"));
|
||||
|
||||
const response = NextResponse.rewrite(new URL(newUrl), {
|
||||
headers: requestHeaders,
|
||||
@@ -83,7 +83,7 @@ export function middleware(req: NextRequest) {
|
||||
|
||||
logMessages.push(` Link API Rewritten URL: ${newUrl}`);
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
// console.log(logMessages.join("\n"));
|
||||
console.log(logMessages.join("\n"));
|
||||
|
||||
const response = NextResponse.rewrite(new URL(newUrl), {
|
||||
headers: requestHeaders,
|
||||
@@ -97,7 +97,7 @@ export function middleware(req: NextRequest) {
|
||||
}
|
||||
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
// console.log(logMessages.join("\n"));
|
||||
console.log(logMessages.join("\n"));
|
||||
|
||||
const response = NextResponse.next();
|
||||
return addSecurityHeaders(response);
|
||||
|
||||
@@ -18,7 +18,7 @@ const nextConfig: NextConfig = {
|
||||
fetches: {
|
||||
fullUrl: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -4,35 +4,15 @@ import {
|
||||
CLAIM_NAME,
|
||||
ENDPOINT,
|
||||
} from "@/shared/config";
|
||||
import {
|
||||
type AuthPayload,
|
||||
type AuthService,
|
||||
getRemainingMs,
|
||||
} from "@common/auth";
|
||||
import { type AuthPayload, type AuthService } from "@common/auth";
|
||||
|
||||
import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id";
|
||||
import {
|
||||
clearRefreshTimer,
|
||||
setToken,
|
||||
setupRefreshTimer,
|
||||
tokenManager,
|
||||
} from "../lib/token-manager";
|
||||
|
||||
function resolveLoginError(message: string | undefined): string {
|
||||
switch (message) {
|
||||
case "Invalid Credentials":
|
||||
return "아이디와 비밀번호가 일치하지 않습니다.";
|
||||
case "User not found":
|
||||
return "존재하지 않는 아이디입니다.";
|
||||
case "User is not active":
|
||||
return "활성화되지 않은 계정입니다.";
|
||||
case "User is not verified":
|
||||
return "이메일 인증이 필요합니다.";
|
||||
default:
|
||||
return message ?? "로그인에 실패했습니다.";
|
||||
}
|
||||
}
|
||||
|
||||
async function login(payload: AuthPayload): Promise<void> {
|
||||
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
|
||||
method: "POST",
|
||||
@@ -45,12 +25,8 @@ async function login(payload: AuthPayload): Promise<void> {
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(resolveLoginError(data?.message));
|
||||
}
|
||||
|
||||
const accessToken = data[ACCESS_TOKEN] as string | undefined;
|
||||
const tokenId = data[AUTH_USER_ID] as string | undefined;
|
||||
const accessToken = data[ACCESS_TOKEN];
|
||||
const tokenId = data[AUTH_USER_ID];
|
||||
|
||||
if (!accessToken || !tokenId) {
|
||||
throw new Error("인증 토큰을 받지 못했습니다.");
|
||||
@@ -59,10 +35,10 @@ async function login(payload: AuthPayload): Promise<void> {
|
||||
setToken(CLAIM_NAME, accessToken);
|
||||
setToken(AUTH_USER_ID, tokenId);
|
||||
|
||||
const remainingMs = getRemainingMs(accessToken);
|
||||
if (remainingMs > 0) {
|
||||
setupRefreshTimer(silentRefresh, remainingMs);
|
||||
}
|
||||
// const remainingMs = getRemainingMs(accessToken);
|
||||
// if (remainingMs > 0) {
|
||||
// setupRefreshTimer(silentRefresh, remainingMs);
|
||||
// }
|
||||
}
|
||||
|
||||
async function silentRefresh(): Promise<void> {
|
||||
@@ -84,8 +60,8 @@ async function silentRefresh(): Promise<void> {
|
||||
throw new Error(data?.message ?? "토큰 갱신에 실패했습니다.");
|
||||
}
|
||||
|
||||
const accessToken = data[ACCESS_TOKEN] as string | undefined;
|
||||
const tokenId = data[AUTH_USER_ID] as string | undefined;
|
||||
const accessToken = data[ACCESS_TOKEN];
|
||||
const tokenId = data[AUTH_USER_ID];
|
||||
|
||||
if (!accessToken || !tokenId) {
|
||||
throw new Error("갱신된 인증 토큰을 받지 못했습니다.");
|
||||
@@ -94,10 +70,10 @@ async function silentRefresh(): Promise<void> {
|
||||
setToken(CLAIM_NAME, accessToken);
|
||||
setToken(AUTH_USER_ID, tokenId);
|
||||
|
||||
const remainingMs = getRemainingMs(accessToken);
|
||||
if (remainingMs > 0) {
|
||||
setupRefreshTimer(silentRefresh, remainingMs);
|
||||
}
|
||||
// const remainingMs = getRemainingMs(accessToken);
|
||||
// if (remainingMs > 0) {
|
||||
// setupRefreshTimer(silentRefresh, remainingMs);
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[silentRefresh] Failed at ${new Date().toISOString()}`,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ACCESS_TOKEN,
|
||||
AUTH_USER_ID,
|
||||
CLAIM_NAME,
|
||||
COOKIE_DOMAIN,
|
||||
REFRESH_TOKEN,
|
||||
SERVER_API_URL,
|
||||
SITE_ID,
|
||||
@@ -75,6 +76,7 @@ async function handler(
|
||||
cookieStore.set(REFRESH_TOKEN, refreshToken, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
domain: COOKIE_DOMAIN,
|
||||
path: "/",
|
||||
});
|
||||
}
|
||||
|
||||
+37
-37
@@ -1,47 +1,47 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const subDomain =
|
||||
process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13000";
|
||||
process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13000";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
// output: "standalone",
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: "/(.*)",
|
||||
headers: [
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: "img-src 'self' data: https: blob:;",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
async rewrites() {
|
||||
return {
|
||||
beforeFiles: [
|
||||
{
|
||||
source: "/_next/static/:path*",
|
||||
destination: `${subDomain}/_next/static/:path*`,
|
||||
has: [
|
||||
output: "standalone",
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
type: "header",
|
||||
key: "referer",
|
||||
value: "(.*)/cms(.*)",
|
||||
source: "/(.*)",
|
||||
headers: [
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: "img-src 'self' data: https: blob:;",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
afterFiles: [
|
||||
{
|
||||
source: "/cms/:path*",
|
||||
destination: `${subDomain}/cms/:path*`,
|
||||
},
|
||||
],
|
||||
fallback: [],
|
||||
};
|
||||
},
|
||||
];
|
||||
},
|
||||
async rewrites() {
|
||||
return {
|
||||
beforeFiles: [
|
||||
{
|
||||
source: "/_next/static/:path*",
|
||||
destination: `${subDomain}/_next/static/:path*`,
|
||||
has: [
|
||||
{
|
||||
type: "header",
|
||||
key: "referer",
|
||||
value: "(.*)/cms(.*)",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
afterFiles: [
|
||||
{
|
||||
source: "/cms/:path*",
|
||||
destination: `${subDomain}/cms/:path*`,
|
||||
},
|
||||
],
|
||||
fallback: [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -357,16 +357,16 @@ const VerificationSelectionReport = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<span className="font-medium">
|
||||
<div className="mb-2">
|
||||
<div className="font-medium">
|
||||
{formatReportItemTitle(item)}
|
||||
</span>
|
||||
</div>
|
||||
<Badge
|
||||
color={
|
||||
TYPE_CODE_COLOR_MAP[item.vrfcMthdCd] ??
|
||||
"gray"
|
||||
}
|
||||
className="text-sm"
|
||||
className="text-sm float-end"
|
||||
>
|
||||
{TYPE_CODE_NAME_MAP[item.vrfcMthdCd] ??
|
||||
item.vrfcMthdNm}
|
||||
@@ -397,8 +397,8 @@ const VerificationSelectionReport = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-1/5 bg-container p-4 !pb-2 flex border-r border-border">
|
||||
<div className="card card-border flex-1 bg-background !px-0 max-h-[1000px] flex flex-col">
|
||||
<div className="w-1/5 min-w-0 bg-container p-4 !pb-2 flex border-r border-border">
|
||||
<div className="card card-border min-w-0 flex-1 bg-background !px-0 max-h-[1000px] flex flex-col">
|
||||
<div className="border-b border-border flex justify-between">
|
||||
<div className="flex justify-center items-center text-[2.4rem] ">
|
||||
<h2 className="font-semibold text-title p-4 pr-1">이상치 선별 보고서</h2>
|
||||
@@ -434,14 +434,14 @@ const VerificationSelectionReport = ({
|
||||
<i className="ri-close-line" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 pt-2.5 px-4">
|
||||
<div className="flex min-w-0 items-center gap-2 pt-2.5 px-4">
|
||||
<Label className="text-label shrink-0">통계항목</Label>
|
||||
<CodeSelectWithAll
|
||||
upCd="STITEM"
|
||||
value={statsCd}
|
||||
onValueChange={handleStatsCdChange}
|
||||
placeholder="전체"
|
||||
className="w-full"
|
||||
className="min-w-0 flex-1 overflow-hidden"
|
||||
/>
|
||||
</div>
|
||||
<div className="card-body p-2.5 flex-1 flex flex-col overflow-hidden relative min-h-0">
|
||||
|
||||
@@ -5,17 +5,12 @@ import {
|
||||
ENDPOINT,
|
||||
} from "@/shared/config";
|
||||
import { getCookie } from "@/shared/lib";
|
||||
import {
|
||||
type AuthPayload,
|
||||
type AuthService,
|
||||
getRemainingMs,
|
||||
} from "@common/auth";
|
||||
import { type AuthPayload, type AuthService } from "@common/auth";
|
||||
|
||||
import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id";
|
||||
import {
|
||||
clearRefreshTimer,
|
||||
setToken,
|
||||
setupRefreshTimer,
|
||||
tokenManager,
|
||||
} from "../lib/token-manager";
|
||||
|
||||
@@ -48,10 +43,10 @@ async function login(payload: AuthPayload): Promise<void> {
|
||||
setToken(CLAIM_NAME, accessToken);
|
||||
setToken(AUTH_USER_ID, tokenId);
|
||||
|
||||
const remainingMs = getRemainingMs(accessToken);
|
||||
if (remainingMs > 0) {
|
||||
setupRefreshTimer(silentRefresh, remainingMs);
|
||||
}
|
||||
// const remainingMs = getRemainingMs(accessToken);
|
||||
// if (remainingMs > 0) {
|
||||
// setupRefreshTimer(silentRefresh, remainingMs);
|
||||
// }
|
||||
}
|
||||
|
||||
async function silentRefresh(): Promise<void> {
|
||||
@@ -83,10 +78,10 @@ async function silentRefresh(): Promise<void> {
|
||||
setToken(CLAIM_NAME, accessToken);
|
||||
setToken(AUTH_USER_ID, tokenId);
|
||||
|
||||
const remainingMs = getRemainingMs(accessToken);
|
||||
if (remainingMs > 0) {
|
||||
setupRefreshTimer(silentRefresh, remainingMs);
|
||||
}
|
||||
// const remainingMs = getRemainingMs(accessToken);
|
||||
// if (remainingMs > 0) {
|
||||
// setupRefreshTimer(silentRefresh, remainingMs);
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[silentRefresh] Failed at ${new Date().toISOString()}`,
|
||||
|
||||
@@ -84,7 +84,7 @@ export function ScheduleInfoPanel({ params }: ScheduleInfoPanelProps) {
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between rounded-sm border border-border p-3">
|
||||
<Badge size="lg" className="font-semibold">
|
||||
{data.inptSeCd}차 입력기간
|
||||
입력기간
|
||||
</Badge>
|
||||
<p className="text-2xl">
|
||||
{dayjs(data.inptBgngYmd).format(
|
||||
@@ -102,7 +102,7 @@ export function ScheduleInfoPanel({ params }: ScheduleInfoPanelProps) {
|
||||
color="gray"
|
||||
className="font-semibold"
|
||||
>
|
||||
{data.vrfcSeCd}차 검증기간
|
||||
검증기간
|
||||
</Badge>
|
||||
<p className="text-2xl">
|
||||
{dayjs(data.vrfcBgngYmd).format(
|
||||
|
||||
@@ -1,35 +1,31 @@
|
||||
import { getApiClient } from "./client";
|
||||
import { getConfig } from "@common/config";
|
||||
import { ENDPOINT } from "../config";
|
||||
import { getAuthHeaders } from "../lib/auth-header";
|
||||
|
||||
export interface Banner {
|
||||
bnrSn: number;
|
||||
bnrTypeCd: string;
|
||||
bnrTypeNm: string;
|
||||
bnrTtl: string;
|
||||
lnkgUrlAddr: string;
|
||||
useYn: string;
|
||||
atflCd: string;
|
||||
mdfcnDt: string;
|
||||
mdfr: string;
|
||||
mdfrNm: string;
|
||||
}
|
||||
bnrSn: number;
|
||||
bnrTypeCd: string;
|
||||
bnrTypeNm: string;
|
||||
bnrTtl: string;
|
||||
lnkgUrlAddr: string;
|
||||
useYn: string;
|
||||
atflCd: string;
|
||||
mdfcnDt: string;
|
||||
mdfr: string;
|
||||
mdfrNm: string;
|
||||
}
|
||||
|
||||
const { PROXY_HOST } = getConfig();
|
||||
const BASE_URL = `${PROXY_HOST}/server`;
|
||||
|
||||
const { PROXY_HOST } = getConfig();
|
||||
const BASE_URL = `${PROXY_HOST}/server`;
|
||||
|
||||
const client = getApiClient();
|
||||
|
||||
export const getBannerList = async (siteId: string) => {
|
||||
const response = await client.get<Banner[]>(
|
||||
export const getBannerList = async (siteId: string): Promise<Banner[]> => {
|
||||
const response = await fetch(
|
||||
`${BASE_URL}${ENDPOINT.CMS_SERVICE.BANNERS}/${siteId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: getAuthHeaders(),
|
||||
credentials: "include",
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getApiClient } from "./client";
|
||||
import { getAuthHeaders } from "../lib/auth-header";
|
||||
import { ENDPOINT } from "../config";
|
||||
import { getConfig } from "@common/config";
|
||||
|
||||
@@ -27,22 +27,33 @@ export interface TreeMenu extends MenuItem {
|
||||
menuHlpCn: string;
|
||||
}
|
||||
|
||||
const client = getApiClient();
|
||||
const { PROXY_HOST } = getConfig();
|
||||
const BASE_URL = `${PROXY_HOST}/server`;
|
||||
|
||||
export const getMenuList = async (siteId: string) => {
|
||||
const response = await client.get<MenuItem[]>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENU_ROLES}/${siteId}`,
|
||||
export const getMenuList = async (siteId: string): Promise<MenuItem[]> => {
|
||||
const response = await fetch(
|
||||
`${BASE_URL}${ENDPOINT.CMS_SERVICE.MENU_ROLES}/${siteId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: getAuthHeaders(),
|
||||
credentials: "include",
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const getPreviewTreeMenuList = async (siteId: string) => {
|
||||
const response = await client.get<TreeMenu[]>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree/preview`,
|
||||
export const getPreviewTreeMenuList = async (
|
||||
siteId: string,
|
||||
): Promise<TreeMenu[]> => {
|
||||
const response = await fetch(
|
||||
`${BASE_URL}${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree/preview`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: getAuthHeaders(),
|
||||
credentials: "include",
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const getAnonymouseMenuList = async (
|
||||
@@ -52,9 +63,8 @@ export const getAnonymouseMenuList = async (
|
||||
`${BASE_URL}${ENDPOINT.CMS_SERVICE.MENU_ANONYMOUS}/user/${siteId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: getAuthHeaders(),
|
||||
credentials: "include",
|
||||
},
|
||||
);
|
||||
return response.json();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getApiClient } from "./client";
|
||||
import { getConfig } from "@common/config";
|
||||
import { ENDPOINT } from "../config";
|
||||
import { getAuthHeaders } from "../lib/auth-header";
|
||||
|
||||
export interface SiteInfo {
|
||||
siteId: string;
|
||||
@@ -62,25 +62,18 @@ export const getSiteInfo = async (siteId: string) => {
|
||||
`${BASE_URL}${ENDPOINT.CMS_SERVICE.SITES}/${siteId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: getAuthHeaders(),
|
||||
credentials: "include",
|
||||
},
|
||||
);
|
||||
return response.json() as Promise<SiteInfo>;
|
||||
};
|
||||
|
||||
const client = getApiClient();
|
||||
|
||||
export const getSiteList = async () => {
|
||||
const response = await client.get<SiteListResponse>(
|
||||
`${BASE_URL}${ENDPOINT.CMS_SERVICE.SITE_LIST}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
export const getSiteList = async (): Promise<SiteListResponse> => {
|
||||
const response = await fetch(`${BASE_URL}${ENDPOINT.CMS_SERVICE.SITE_LIST}`, {
|
||||
method: "GET",
|
||||
headers: getAuthHeaders(),
|
||||
credentials: "include",
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import Cookies from "js-cookie";
|
||||
import { getConfig } from "@common/config";
|
||||
|
||||
export function getAuthHeaders(): HeadersInit {
|
||||
const { CLAIM_NAME, AUTH_USER_ID } = getConfig();
|
||||
|
||||
if (!CLAIM_NAME || !AUTH_USER_ID) {
|
||||
throw new Error("Required environment variables are not set");
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
const token = Cookies.get(CLAIM_NAME);
|
||||
const userId = Cookies.get(AUTH_USER_ID);
|
||||
|
||||
if (token) headers[CLAIM_NAME] = token;
|
||||
if (userId) headers[AUTH_USER_ID] = userId;
|
||||
|
||||
return headers;
|
||||
}
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
CarouselPrevious,
|
||||
} from "../carousel";
|
||||
|
||||
const siteId = getCookie("site-id-key") ?? "";
|
||||
|
||||
type BannerImageProps = {
|
||||
atflCd: string;
|
||||
bnrSn: number;
|
||||
@@ -64,6 +62,8 @@ const BannerImage = ({ atflCd, lnkgUrlAddr }: BannerImageProps) => {
|
||||
};
|
||||
|
||||
export function Footer() {
|
||||
const siteId = getCookie("site-id-key") ?? "";
|
||||
|
||||
const { data: siteInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.SITES, siteId],
|
||||
queryFn: () => getSiteInfo(siteId),
|
||||
|
||||
@@ -29,7 +29,6 @@ import { ScrollArea, ScrollBar } from "../scroll-area";
|
||||
|
||||
const appConfig = getConfig();
|
||||
const userId = getCookie(appConfig.AUTH_USER_ID ?? "");
|
||||
const siteId = getCookie("site-id-key") ?? "";
|
||||
const isLoggedIn = !!userId;
|
||||
|
||||
function getRootDomain(hostname: string): string {
|
||||
@@ -100,7 +99,8 @@ async function legacyLogout(tokenManager?: TokenManager) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatRemainingMs(remainingMs: number): string {
|
||||
function formatRemainingMs(remainingMs: number | null): string {
|
||||
if (remainingMs === null) return "-";
|
||||
if (remainingMs <= 0) return "만료됨";
|
||||
|
||||
const totalSeconds = Math.floor(remainingMs / 1000);
|
||||
@@ -110,15 +110,18 @@ function formatRemainingMs(remainingMs: number): string {
|
||||
return `${minutes}분 ${seconds}초`;
|
||||
}
|
||||
|
||||
function useTokenRemainingMs(enabled: boolean): number {
|
||||
const [remainingMs, setRemainingMs] = useState(0);
|
||||
function useTokenRemainingMs(enabled: boolean): number | null {
|
||||
const [remainingMs, setRemainingMs] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
if (!enabled) {
|
||||
setRemainingMs(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
const token = getCookie(appConfig.CLAIM_NAME ?? "");
|
||||
setRemainingMs(token ? getRemainingMs(token) : 0);
|
||||
setRemainingMs(token ? getRemainingMs(token, 0) : 0);
|
||||
};
|
||||
|
||||
update();
|
||||
@@ -143,8 +146,25 @@ export function Header({
|
||||
const [logoWidth, setLogoWidth] = useState(0);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const logoRef = useRef<HTMLImageElement>(null);
|
||||
const siteId = getCookie("site-id-key") ?? "";
|
||||
|
||||
const remainingMs = useTokenRemainingMs(mounted && isLoggedIn && !viewOnly);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!mounted ||
|
||||
!isLoggedIn ||
|
||||
viewOnly ||
|
||||
!authService ||
|
||||
remainingMs === null ||
|
||||
remainingMs > 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
void authService.logout();
|
||||
}, [mounted, viewOnly, authService, remainingMs]);
|
||||
|
||||
useSessionExtendConfirm({
|
||||
enabled:
|
||||
mounted &&
|
||||
@@ -395,7 +415,7 @@ function UserMenuContent({
|
||||
remainingMs,
|
||||
onLogout,
|
||||
}: {
|
||||
remainingMs: number;
|
||||
remainingMs: number | null;
|
||||
onLogout: () => void;
|
||||
}) {
|
||||
return (
|
||||
|
||||
@@ -8,15 +8,18 @@ import { SidebarProvider } from "../sidebar";
|
||||
import { LNB } from "../lnb";
|
||||
import { Header } from "../header";
|
||||
import "./common.css";
|
||||
import type { AuthService } from "@common/auth";
|
||||
|
||||
export function NoFooterLayout({
|
||||
children,
|
||||
queryClient,
|
||||
authService,
|
||||
currentPath,
|
||||
handleMenuClick,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
queryClient?: QueryClient;
|
||||
authService?: AuthService;
|
||||
currentPath?: string;
|
||||
handleMenuClick?: (item: MenuItem) => void;
|
||||
}) {
|
||||
@@ -26,19 +29,19 @@ export function NoFooterLayout({
|
||||
<PopupProvider>
|
||||
<HelpDialogProvider>
|
||||
<SidebarProvider>
|
||||
<div className="flex-1">
|
||||
<Header />
|
||||
<div className="flex">
|
||||
<LNB
|
||||
currentPath={currentPath}
|
||||
handleMenuClick={handleMenuClick}
|
||||
/>
|
||||
<div className="flex min-h-[calc(100vh-50px)] flex-1 flex-col overflow-auto">
|
||||
<div className="flex-1">{children}</div>
|
||||
<div className="flex-1">
|
||||
<Header authService={authService} />
|
||||
<div className="flex">
|
||||
<LNB
|
||||
currentPath={currentPath}
|
||||
handleMenuClick={handleMenuClick}
|
||||
/>
|
||||
<div className="flex min-h-[calc(100vh-50px)] flex-1 flex-col overflow-auto">
|
||||
<div className="flex-1">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</SidebarProvider>
|
||||
</HelpDialogProvider>
|
||||
</PopupProvider>
|
||||
</ClientIpGuard>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { HelpDialogProvider } from "../../provider/help-dialog-context";
|
||||
import { ClientIpGuard } from "../../lib/client-ip-guard";
|
||||
import type { MenuItem } from "../../api";
|
||||
import { SidebarProvider } from "../sidebar";
|
||||
import { LNB } from "../lnb";
|
||||
import { Header } from "../header";
|
||||
import { Footer } from "../footer";
|
||||
import "./common.css";
|
||||
@@ -17,7 +16,6 @@ export function NoLNBLayout({
|
||||
authService,
|
||||
tokenManager,
|
||||
currentPath,
|
||||
handleMenuClick,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
queryClient?: QueryClient;
|
||||
@@ -33,19 +31,19 @@ export function NoLNBLayout({
|
||||
<PopupProvider>
|
||||
<HelpDialogProvider>
|
||||
<SidebarProvider>
|
||||
<div className="flex-1">
|
||||
<Header
|
||||
authService={authService}
|
||||
tokenManager={authService ? undefined : tokenManager}
|
||||
/>
|
||||
<div className="flex">
|
||||
<div className="flex min-h-[calc(100vh-50px)] flex-1 flex-col overflow-auto">
|
||||
<div className="flex-1">{children}</div>
|
||||
<Footer />
|
||||
<div className="flex-1">
|
||||
<Header
|
||||
authService={authService}
|
||||
tokenManager={authService ? undefined : tokenManager}
|
||||
/>
|
||||
<div className="flex">
|
||||
<div className="flex min-h-[calc(100vh-50px)] flex-1 flex-col overflow-auto">
|
||||
<div className="flex-1">{children}</div>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</SidebarProvider>
|
||||
</HelpDialogProvider>
|
||||
</PopupProvider>
|
||||
</ClientIpGuard>
|
||||
|
||||
@@ -42,7 +42,6 @@ import {
|
||||
|
||||
const appConfig = getConfig();
|
||||
const userId = getCookie(appConfig.AUTH_USER_ID ?? "");
|
||||
const appSiteId = getCookie("site-id-key") ?? "";
|
||||
const isLoggedIn = !!userId;
|
||||
|
||||
export function LNB({
|
||||
@@ -74,6 +73,7 @@ export function LNB({
|
||||
helpContent,
|
||||
} = useHelpDialog();
|
||||
|
||||
const appSiteId = getCookie("site-id-key") ?? "";
|
||||
const siteId = propSiteId ?? appSiteId;
|
||||
|
||||
const { data: menuListData } = useQuery({
|
||||
|
||||
Generated
+2
-2
@@ -247,9 +247,9 @@ importers:
|
||||
'@common/components':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/common/components
|
||||
'@common/config':
|
||||
'@common/network':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/common/config
|
||||
version: link:../../packages/common/network
|
||||
'@dnd-kit/core':
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
|
||||
Reference in New Issue
Block a user