@@ -0,0 +1,100 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token";
|
||||
const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
|
||||
const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
|
||||
const REFRESH_TOKEN = process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token";
|
||||
const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL || "";
|
||||
const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "SN";
|
||||
|
||||
async function handler(
|
||||
request: Request,
|
||||
params: { url: string[] },
|
||||
method: "GET" | "POST" | "PUT" | "DELETE",
|
||||
) {
|
||||
const cookieStore = await cookies();
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Site-Id": SITE_ID,
|
||||
};
|
||||
let body = {};
|
||||
const isRefreshHandler = params.url.includes("refresh");
|
||||
|
||||
// silent refresh
|
||||
if (isRefreshHandler) {
|
||||
const refreshToken = cookieStore.get(REFRESH_TOKEN);
|
||||
headers[CLAIM_NAME] = refreshToken?.value ?? "";
|
||||
|
||||
if (!headers[CLAIM_NAME]) {
|
||||
return NextResponse.json({ message: "Can't refresh" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (!isRefreshHandler) {
|
||||
body = await request.json();
|
||||
}
|
||||
|
||||
const result = await fetch(`${SERVER_API_URL}/${params.url.join("/")}`, {
|
||||
method,
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
console.log("Invalid Credentials ");
|
||||
return NextResponse.json(
|
||||
{ message: "Invalid Credentials" },
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
const accessToken = result.headers.get(ACCESS_TOKEN);
|
||||
const refreshToken = result.headers.get(REFRESH_TOKEN);
|
||||
const userId = result.headers.get(AUTH_USER_ID);
|
||||
|
||||
if (accessToken && userId) {
|
||||
const response = NextResponse.json({
|
||||
[ACCESS_TOKEN]: accessToken,
|
||||
[AUTH_USER_ID]: userId,
|
||||
});
|
||||
|
||||
if (refreshToken) {
|
||||
cookieStore.set(REFRESH_TOKEN, refreshToken, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ message: "Invalid Credentials" },
|
||||
{ status: 401 },
|
||||
);
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ message: "Server Error", error },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
props: { params: Promise<{ url: string[] }> },
|
||||
) {
|
||||
const params = await props.params;
|
||||
return handler(request, params, "POST");
|
||||
}
|
||||
|
||||
export async function PUT(
|
||||
request: Request,
|
||||
props: { params: Promise<{ url: string[] }> },
|
||||
) {
|
||||
const params = await props.params;
|
||||
return handler(request, params, "PUT");
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { AxiosError } from "axios";
|
||||
import { tokenManager } from "../lib/token-manager";
|
||||
|
||||
const ENDPOINT = {
|
||||
USER_SERVICE: {
|
||||
LOGIN: "/user-service/login",
|
||||
USERS: "/user-service/api/v1/users",
|
||||
SILENT_REFRESH: "/user-service/api/v1/users/token/refresh",
|
||||
AUTHORIZATIONS: "/user-service/api/v1/authorizations",
|
||||
ROLE_AUTHORIZATIONS: "/user-service/api/v1/role-authorizations",
|
||||
},
|
||||
};
|
||||
const JWT_EXPIRATION_TIME = 1800000;
|
||||
const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
|
||||
const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
|
||||
const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token";
|
||||
|
||||
|
||||
export interface ILogin {
|
||||
userId: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface LoginService {
|
||||
login(payload: ILogin): Promise<void>;
|
||||
silentRefresh(): Promise<void>;
|
||||
}
|
||||
|
||||
export class LoginApiService implements LoginService {
|
||||
private static instance: LoginApiService;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): LoginApiService {
|
||||
if (!LoginApiService.instance) {
|
||||
LoginApiService.instance = new LoginApiService();
|
||||
}
|
||||
return LoginApiService.instance;
|
||||
}
|
||||
|
||||
public async login(payload: ILogin): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
userId: payload.userId,
|
||||
password: payload.password,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const accessToken = data[ACCESS_TOKEN];
|
||||
const tokenId = data[AUTH_USER_ID];
|
||||
|
||||
if (!accessToken || !tokenId) {
|
||||
throw new Error("Missing required authentication tokens");
|
||||
}
|
||||
|
||||
tokenManager.setToken(CLAIM_NAME, accessToken);
|
||||
tokenManager.setToken(AUTH_USER_ID, tokenId);
|
||||
|
||||
tokenManager.setupRefreshTimer(
|
||||
() => this.silentRefresh(),
|
||||
JWT_EXPIRATION_TIME,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new Error(`Login failed: ${error.response?.data?.message}`);
|
||||
}
|
||||
throw new Error(`Login failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async silentRefresh(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`,
|
||||
{
|
||||
method: "PUT",
|
||||
},
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
const accessToken = response.headers.get("access-token");
|
||||
const tokenId = response.headers.get("token-id");
|
||||
|
||||
if (!accessToken || !tokenId) {
|
||||
throw new Error("Missing required authentication tokens");
|
||||
}
|
||||
|
||||
tokenManager.setToken(CLAIM_NAME, accessToken);
|
||||
tokenManager.setToken(AUTH_USER_ID, tokenId);
|
||||
} catch (error) {
|
||||
tokenManager.clearAuthToken();
|
||||
if (error instanceof AxiosError) {
|
||||
throw new Error(
|
||||
`Silent refresh failed: ${error.response?.data?.message}`,
|
||||
);
|
||||
}
|
||||
throw new Error(`Silent refresh failed: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const LoginApi = LoginApiService.getInstance();
|
||||
@@ -0,0 +1,2 @@
|
||||
export { LoginApi } from "./api/login-service";
|
||||
export { AuthWrapper } from "./lib/auth-wrapper";
|
||||
@@ -0,0 +1,87 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import axios from "axios";
|
||||
import Cookies from "js-cookie";
|
||||
import { useUserStore, type IUserInfo } from "@store";
|
||||
import { getCookie } from "@utils";
|
||||
import { LoginApi } from "../api/login-service";
|
||||
import { tokenManager } from "../lib/token-manager";
|
||||
|
||||
const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
|
||||
const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
|
||||
|
||||
const ENDPOINT = {
|
||||
USER_SERVICE: {
|
||||
LOGIN: "/user-service/login",
|
||||
USERS: "/user-service/api/v1/users",
|
||||
SILENT_REFRESH: "/user-service/api/v1/users/token/refresh",
|
||||
AUTHORIZATIONS: "/user-service/api/v1/authorizations",
|
||||
ROLE_AUTHORIZATIONS: "/user-service/api/v1/role-authorizations",
|
||||
},
|
||||
};
|
||||
|
||||
const AuthWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname(); // 현재 경로
|
||||
const { userInfo, setUserInfo } = useUserStore((state) => state);
|
||||
|
||||
async function authCheck() {
|
||||
const checkToken = getCookie(AUTH_USER_ID);
|
||||
|
||||
|
||||
if (!checkToken) {
|
||||
//TODO 나중에 수정해야....
|
||||
const userId = "admin1gmail";
|
||||
const password = "test1234!";
|
||||
await LoginApi.login({ userId, password });
|
||||
// window.location.href = "http://dev.onasys.co.kr/auth/login";
|
||||
// throw new Error("Missing required authentication tokens");
|
||||
}
|
||||
|
||||
const accessToken = tokenManager.getToken(CLAIM_NAME);
|
||||
const tokenId = tokenManager.getToken(AUTH_USER_ID);
|
||||
|
||||
const response = await axios.get<IUserInfo>(
|
||||
`${ENDPOINT.USER_SERVICE.USERS}/info/me`,
|
||||
{
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
[CLAIM_NAME]: accessToken,
|
||||
[AUTH_USER_ID]: tokenId ?? "",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!userInfo) setUserInfo(response.data);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
await authCheck();
|
||||
} catch (error) {
|
||||
console.error("Auth check failed:", error);
|
||||
|
||||
try {
|
||||
await LoginApi.silentRefresh();
|
||||
await authCheck();
|
||||
} catch (refreshError) {
|
||||
console.error("Token refresh failed:", refreshError);
|
||||
setUserInfo(null);
|
||||
|
||||
// TODO 운영에 반영 할때 수정해야.....
|
||||
// 현재 경로가 로그인 페이지가 아닐 때만 redirect
|
||||
// if (!pathname.startsWith("/auth/login")) {
|
||||
// window.location.href = "http://dev.onasys.co.kr/auth/login";
|
||||
// }
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [pathname]); // pathname도 의존성에 포함
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export { AuthWrapper };
|
||||
@@ -0,0 +1,61 @@
|
||||
import { getCookie, setCookie, removeCookie } from "@utils";
|
||||
|
||||
const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
|
||||
const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
|
||||
|
||||
export class TokenManager {
|
||||
private static instance: TokenManager;
|
||||
private refreshTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): TokenManager {
|
||||
if (!TokenManager.instance) {
|
||||
TokenManager.instance = new TokenManager();
|
||||
}
|
||||
return TokenManager.instance;
|
||||
}
|
||||
|
||||
public getToken(key: string): string | undefined {
|
||||
return getCookie(key);
|
||||
}
|
||||
|
||||
public setToken(key: string, value: string): void {
|
||||
setCookie(key, value, {
|
||||
secure: window.location.protocol === "https:",
|
||||
sameSite: "lax",
|
||||
});
|
||||
}
|
||||
|
||||
public removeToken(key: string): void {
|
||||
removeCookie(key);
|
||||
}
|
||||
|
||||
public setupRefreshTimer(
|
||||
callback: () => Promise<void>,
|
||||
expirationTime: number,
|
||||
): void {
|
||||
if (this.refreshTimer) {
|
||||
clearTimeout(this.refreshTimer);
|
||||
}
|
||||
|
||||
this.refreshTimer = setTimeout(() => {
|
||||
callback().catch((error) => {
|
||||
console.error("Silent refresh failed:", error);
|
||||
this.clearAuthToken();
|
||||
});
|
||||
}, expirationTime - 60000);
|
||||
}
|
||||
|
||||
public clearAuthToken(): void {
|
||||
if (this.refreshTimer) {
|
||||
clearTimeout(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
|
||||
this.removeToken(CLAIM_NAME);
|
||||
this.removeToken(AUTH_USER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
export const tokenManager = TokenManager.getInstance();
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface ILogin {
|
||||
userId: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface LoginService {
|
||||
login(payload: ILogin): Promise<void>;
|
||||
silentRefresh(): Promise<void>;
|
||||
}
|
||||
+10
-10
@@ -1,9 +1,10 @@
|
||||
import './styles/base.css'
|
||||
import './styles/components.css'
|
||||
import "./styles/base.css";
|
||||
import "./styles/components.css";
|
||||
import type { Metadata } from "next";
|
||||
import { cookies } from "next/headers";
|
||||
import { StoreProvider } from './store'
|
||||
import { ClientLayout } from './clientLayout'
|
||||
import { StoreProvider } from "./store/store-provider";
|
||||
import { AuthWrapper } from "./components/lib/auth";
|
||||
import { ClientLayout } from "./clientLayout";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "디지털 하수관로",
|
||||
@@ -20,18 +21,17 @@ export default async function RootLayout({
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
|
||||
const theme = (await cookies()).get("site-theme-key");
|
||||
|
||||
return (
|
||||
<html lang="en" data-theme={theme?.value}>
|
||||
<body>
|
||||
<StoreProvider>
|
||||
<ClientLayout>
|
||||
{children}
|
||||
</ClientLayout>
|
||||
<StoreProvider>
|
||||
<AuthWrapper>
|
||||
<ClientLayout>{children}</ClientLayout>
|
||||
</AuthWrapper>
|
||||
</StoreProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,45 +1 @@
|
||||
'use client'
|
||||
|
||||
import { type ReactNode, createContext, useContext, useRef } from 'react'
|
||||
import { useStore } from 'zustand'
|
||||
import { type SnStore, StoreZus } from './store'
|
||||
|
||||
|
||||
export type StoreApi = ReturnType<typeof StoreZus>
|
||||
|
||||
export const StoreContext = createContext<StoreApi | undefined>(
|
||||
undefined,
|
||||
)
|
||||
|
||||
export interface StoreProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const StoreProvider = ({
|
||||
children,
|
||||
}: StoreProviderProps) => {
|
||||
const storeRef = useRef<StoreApi | null>(null)
|
||||
|
||||
if (!storeRef.current) {
|
||||
storeRef.current = StoreZus()
|
||||
}
|
||||
|
||||
return (
|
||||
<StoreContext.Provider value={storeRef.current}>
|
||||
{children}
|
||||
</StoreContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useSnStore = <T,>(
|
||||
//selector: (store: ReturnType<typeof StoreZus>) => T,
|
||||
selector: (store: SnStore) => T,
|
||||
): T => {
|
||||
const storeContext = useContext(StoreContext)
|
||||
|
||||
if (!storeContext) {
|
||||
throw new Error(`useStore must be used within StoreProvider`)
|
||||
}
|
||||
|
||||
return useStore(storeContext, selector)
|
||||
}
|
||||
export { useUserStore, type IUserInfo } from "./user-store";
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
export type TableUpperProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
upName?: string;
|
||||
upSequnce?: number;
|
||||
}
|
||||
type PublicReuseFacility = {
|
||||
sido: string,
|
||||
sigungo: string,
|
||||
facilityName: string,
|
||||
location: string,
|
||||
locationgubun: string,
|
||||
facilityCapacity: string,
|
||||
yn?: string,
|
||||
authoDay?: string,
|
||||
area?: string,
|
||||
reuse?: string
|
||||
}
|
||||
const upHeadList:TableUpperProps[] = [
|
||||
{ id: "sido", title: "자산ID"},
|
||||
{ id: "sigungo", title: "설비시설"},
|
||||
{ id: "facilityName", title: "주소"},
|
||||
{ id: "location", title: "자산코드"},
|
||||
{ id: "locationgubun", title: "자산명"},
|
||||
{ id: "facilityCapacity", title: "용도"},
|
||||
{ id: "yn", title: '신설/교체'},
|
||||
{ id: "authoDay", title: '처리구역'},
|
||||
{ id: "area", title: '내용연수' },
|
||||
{ id: "reuse", title: '취득금액' },
|
||||
];
|
||||
|
||||
const publicReuseFacilityUper:PublicReuseFacility[] = [
|
||||
{
|
||||
sido: "1048967",
|
||||
sigungo: "관로",
|
||||
facilityName: "양평군 양평읍 백안리 25-5전",
|
||||
location: "S-T-S-2018000675",
|
||||
locationgubun: "2018000675",
|
||||
facilityCapacity: "오수관",
|
||||
yn: "신설",
|
||||
authoDay: "양평하수처리구역",
|
||||
area: "30",
|
||||
reuse: "28,313,220"
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
|
||||
export type tableState = {
|
||||
isInit: boolean;
|
||||
upHeadList: TableUpperProps[];
|
||||
publicReuseFacilityUper: PublicReuseFacility[];
|
||||
}
|
||||
type tableActions = {
|
||||
decrementList: () => void
|
||||
}
|
||||
|
||||
export type tableType = tableState & tableActions;
|
||||
|
||||
export const tableStateExport:tableState= {
|
||||
isInit:false,
|
||||
upHeadList: upHeadList,
|
||||
publicReuseFacilityUper: publicReuseFacilityUper,
|
||||
}
|
||||
export const tableActionsExport:(set:any)=>tableActions=(set: any) => {
|
||||
return {
|
||||
decrementList:()=>set(
|
||||
(state:tableState) => {
|
||||
return (
|
||||
{
|
||||
upHeadList: state.upHeadList,
|
||||
publicReuseFacilityUper: state.publicReuseFacilityUper.slice(0, state.publicReuseFacilityUper.length - 1)
|
||||
})}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useRef } from "react";
|
||||
import { createUserStore } from "../store/user-store";
|
||||
|
||||
type StoreContextType = {
|
||||
userStore: ReturnType<typeof createUserStore>;
|
||||
};
|
||||
|
||||
export const StoreContext = createContext<StoreContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const StoreProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const storeRef = useRef<StoreContextType | null>(null);
|
||||
if (storeRef.current === null) {
|
||||
storeRef.current = {
|
||||
userStore: createUserStore(),
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<StoreContext.Provider value={storeRef.current}>
|
||||
{children}
|
||||
</StoreContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import { createStore } from 'zustand/vanilla';
|
||||
import { tableActionsExport, tableStateExport, type tableState, type tableType } from './table';
|
||||
|
||||
export type SnStore = tableType;
|
||||
//export type SnStore = ReturnType<typeof StoreZus>["getState"];
|
||||
export const StoreZus = (initState: tableState = tableStateExport) => {
|
||||
return createStore<SnStore>()((set) => {
|
||||
return {
|
||||
...initState,
|
||||
...tableActionsExport(set)
|
||||
}
|
||||
})
|
||||
}
|
||||
//store 등록하는 부분
|
||||
@@ -1,116 +0,0 @@
|
||||
export type TableUpperProps = {
|
||||
id: string,
|
||||
title: string,
|
||||
upSequnce?: number,
|
||||
upName?: string
|
||||
}
|
||||
type PublicReuseFacility = {
|
||||
sido: string,
|
||||
sigungo: string,
|
||||
facilityName: string,
|
||||
location: string,
|
||||
locationgubun: string,
|
||||
facilityCapacity: string,
|
||||
yn?: string,
|
||||
authoDay?: string,
|
||||
area?: string,
|
||||
reuse?: string
|
||||
}
|
||||
const upHeadList:TableUpperProps[] = [
|
||||
{ id: "sido", title: "자산ID"},
|
||||
{ id: "sigungo", title: "설비시설"},
|
||||
{ id: "facilityName", title: "주소"},
|
||||
{ id: "location", title: "자산코드"},
|
||||
{ id: "locationgubun", title: "자산명"},
|
||||
{ id: "facilityCapacity", title: "용도"},
|
||||
{ id: "yn", title: '신설/교체'},
|
||||
{ id: "authoDay", title: '처리구역'},
|
||||
{ id: "area", title: '내용연수' },
|
||||
{ id: "reuse", title: '취득금액' },
|
||||
];
|
||||
|
||||
const publicReuseFacilityUper:PublicReuseFacility[] = [
|
||||
{
|
||||
sido: "1048967",
|
||||
sigungo: "관로",
|
||||
facilityName: "양평군 양평읍 백안리 25-5전",
|
||||
location: "S-T-S-2018000675",
|
||||
locationgubun: "2018000675",
|
||||
facilityCapacity: "오수관",
|
||||
yn: "신설",
|
||||
authoDay: "양평하수처리구역",
|
||||
area: "30",
|
||||
reuse: "28,313,220"
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
export type tableState = {
|
||||
isInit: boolean;
|
||||
upHeadList: TableUpperProps[];
|
||||
publicReuseFacilityUper: PublicReuseFacility[];
|
||||
}
|
||||
type tableActions = {
|
||||
setHeadList: (newHeadList: TableUpperProps[]) => void
|
||||
setTableData: (newTableDataList: PublicReuseFacility[]) => void
|
||||
decrementList: () => void
|
||||
addRow: (newTableData: PublicReuseFacility) => void
|
||||
}
|
||||
|
||||
export type tableType = tableState & tableActions;
|
||||
|
||||
export const tableStateExport:tableState= {
|
||||
isInit:false,
|
||||
upHeadList: [
|
||||
{ id: "sido", title: "자산ID"},
|
||||
{ id: "sigungo", title: "설비시설"},
|
||||
{ id: "facilityName", title: "주소"},
|
||||
{ id: "location", title: "자산코드"},
|
||||
{ id: "locationgubun", title: "자산명"},
|
||||
{ id: "facilityCapacity", title: "용도"},
|
||||
{ id: "yn", title: '신설/교체'},
|
||||
{ id: "authoDay", title: '처리구역'},
|
||||
{ id: "area", title: '내용연수' },
|
||||
{ id: "reuse", title: '취득금액' },
|
||||
],
|
||||
publicReuseFacilityUper: publicReuseFacilityUper,
|
||||
}
|
||||
export const tableActionsExport:(set:any)=>tableActions=(set: any) => {
|
||||
return {
|
||||
// 마지막 데이터 삭제
|
||||
decrementList: ()=>set(
|
||||
(state: tableState) => {
|
||||
return (
|
||||
{
|
||||
...state,
|
||||
upHeadList: state.upHeadList,
|
||||
publicReuseFacilityUper: state.publicReuseFacilityUper.slice(0, state.publicReuseFacilityUper.length - 1)
|
||||
})}
|
||||
),
|
||||
// 데이터 추가
|
||||
addRow: (newTableData: PublicReuseFacility)=>set(
|
||||
(state: tableState) => {
|
||||
console.log('asdfasdfadsf')
|
||||
return (
|
||||
{
|
||||
...state,
|
||||
upHeadList: state.upHeadList,
|
||||
publicReuseFacilityUper: [...state.publicReuseFacilityUper, newTableData]
|
||||
})}
|
||||
),
|
||||
// 테이블 헤더 변경
|
||||
setHeadList: (newHeadList: TableUpperProps[]) => set(
|
||||
(state: tableState) => ({
|
||||
...state,
|
||||
upHeadList: newHeadList
|
||||
})
|
||||
),
|
||||
// 시설 목록 데이터 변경
|
||||
setTableData: (newTableData: PublicReuseFacility[]) => set(
|
||||
(state: tableState) => ({
|
||||
...state,
|
||||
publicReuseFacilityUper: newTableData
|
||||
})
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { createStore } from "zustand/vanilla";
|
||||
|
||||
export interface IUserInfo {
|
||||
regDt: string;
|
||||
mdfcnDt: string;
|
||||
rgtr: string;
|
||||
mdfr: string;
|
||||
adminRoleYn: string;
|
||||
userUnqId: string;
|
||||
userId: string;
|
||||
userName: string;
|
||||
email: string;
|
||||
encryptedPassword: string;
|
||||
refreshToken: string;
|
||||
userStateCode: string;
|
||||
lastLoginDate: string;
|
||||
loginFailCount: number;
|
||||
pswdHnt: string;
|
||||
pswdAns: string;
|
||||
mphn: string;
|
||||
telno: string;
|
||||
zip: string;
|
||||
addr: string;
|
||||
daddr: string;
|
||||
laeSeCd: string;
|
||||
ogdpSeCd: string;
|
||||
aprvYn: string;
|
||||
autzrId: string;
|
||||
expryYmd: string;
|
||||
lastPswdChgDt: string;
|
||||
googleId: string;
|
||||
kakaoId: string;
|
||||
naverId: string;
|
||||
isSocialUser: boolean;
|
||||
hasPassword: boolean;
|
||||
}
|
||||
|
||||
export type UserState = {
|
||||
userInfo: IUserInfo | null;
|
||||
};
|
||||
|
||||
type UserStore = {
|
||||
userInfo: IUserInfo | null;
|
||||
setUserInfo: (userInfo: IUserInfo | null) => void;
|
||||
};
|
||||
|
||||
// ✅ zustand 기본 create + persist 적용
|
||||
export const useUserStore = create<UserStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
userInfo: null,
|
||||
setUserInfo: (userInfo) => set({ userInfo }),
|
||||
}),
|
||||
{
|
||||
name: "user-store", // localStorage 키
|
||||
partialize: (state) => ({ userInfo: state.userInfo }), // 선택적 저장
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export const defaultInitState: UserState = {
|
||||
userInfo: null,
|
||||
};
|
||||
|
||||
export const createUserStore = (initState: UserState = defaultInitState) => {
|
||||
return createStore<UserStore>()((set) => ({
|
||||
...initState,
|
||||
setUserInfo: (userInfo: IUserInfo | null) => set(() => ({ userInfo })),
|
||||
}));
|
||||
};
|
||||
+18
-1
@@ -46,9 +46,26 @@ export function middleware(req: NextRequest) {
|
||||
});
|
||||
}
|
||||
|
||||
if (pathname.startsWith("/user-service")) {
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
const newUrl = `${serverApiUrl}${pathname}`;
|
||||
|
||||
console.log(
|
||||
`[Middleware] Rewriting from '/user-service' path to: ${newUrl}`,
|
||||
);
|
||||
return NextResponse.rewrite(new URL(newUrl), {
|
||||
headers: requestHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/server/:path*","/geoserver/:path*", "/attach-service/:path*"],
|
||||
matcher: [
|
||||
"/server/:path*",
|
||||
"/geoserver/:path*",
|
||||
"/attach-service/:path*",
|
||||
"/user-service/:path*",
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user