diff --git a/apps/cs/src/app/providers/app-provider.tsx b/apps/cs/src/app/providers/app-provider.tsx index b8fabff3d..1a2189b03 100644 --- a/apps/cs/src/app/providers/app-provider.tsx +++ b/apps/cs/src/app/providers/app-provider.tsx @@ -5,7 +5,6 @@ import { MultiBackend, getBackendOptions } from "@minoru/react-dnd-treeview"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ConfirmProvider } from "@frontend-opensource/use-react-hooks"; -import { ClientIpGuard } from "@/entities/ip"; import { AuthWrapper } from "@/entities/auth"; import { MenuWrapper } from "@/entities/menu"; import { PromptProvider, StoreProvider } from "@/shared/providers"; @@ -17,30 +16,28 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { const queryClient = new QueryClient(); return ( - - - - - - - - {children} - - - - - - - - - -
- -
-
+ + + + + + + {children} + + + + + + + + + +
+ +
); }; diff --git a/apps/cs/src/pages/site/ui/modify-page.tsx b/apps/cs/src/pages/site/ui/modify-page.tsx index 0f36bc58d..fdc40c477 100644 --- a/apps/cs/src/pages/site/ui/modify-page.tsx +++ b/apps/cs/src/pages/site/ui/modify-page.tsx @@ -18,12 +18,8 @@ function ModifyPage({ siteId }: { siteId: string }) { queryKey: [ENDPOINT.CMS_SERVICE.SITES, siteId], queryFn: () => SiteApi.siteDetail(siteId), select: (data) => { - const keys = Object.keys(siteFormSchema.shape); - const filteredData = Object.fromEntries( - Object.entries(data).filter(([key]) => keys.includes(key)), - ); return { - ...filteredData, + ...data, faxNo: data.faxNumber, bscSiteYn: data.basicSiteYn, }; @@ -50,7 +46,14 @@ function ModifyPage({ siteId }: { siteId: string }) { const form = useForm({ resolver: zodResolver(siteFormSchema), - values: siteDetail as SiteFormData, + values: { + ...siteDetail, + siteId: siteDetail?.siteId ?? "", + siteNm: siteDetail?.siteNm ?? "", + siteKndCd: siteDetail?.siteKndCd ?? "", + bscSiteYn: siteDetail?.bscSiteYn ?? "", + useYn: siteDetail?.useYn ?? "", + }, }); return ( diff --git a/apps/cs/src/pages/site/ui/site-form.tsx b/apps/cs/src/pages/site/ui/site-form.tsx index c76f23ae5..ffd03e7f4 100644 --- a/apps/cs/src/pages/site/ui/site-form.tsx +++ b/apps/cs/src/pages/site/ui/site-form.tsx @@ -82,6 +82,7 @@ function SiteForm({ form, siteId, ...props }: SiteFormProps) { diff --git a/packages/common/assets/src/images/index.ts b/packages/common/assets/src/images/index.ts index 7abbeb673..e9626cef8 100644 --- a/packages/common/assets/src/images/index.ts +++ b/packages/common/assets/src/images/index.ts @@ -1,5 +1,6 @@ export const images = { logo: require("./logo.png"), + logoWhite: require("./logo_white.png"), menuBtn: require("./mob_menuBtn.svg"), menuBtnClose: require("./mob_menuBtn_close.svg"), iconFolderOff: require("./icon_folder_off.png"), diff --git a/packages/common/assets/src/images/logo_white.png b/packages/common/assets/src/images/logo_white.png new file mode 100644 index 000000000..e76dd65d5 Binary files /dev/null and b/packages/common/assets/src/images/logo_white.png differ diff --git a/packages/common/components/src/api/ip.ts b/packages/common/components/src/api/ip.ts new file mode 100644 index 000000000..398f5ac1d --- /dev/null +++ b/packages/common/components/src/api/ip.ts @@ -0,0 +1,62 @@ +import { getApiClient } from "./client"; +import { ENDPOINT } from "../config"; + +interface PageableParams { + page?: number; + size?: number; + sort?: string[]; +} + +export interface IpParams extends PageableParams { + siteId: string; + ipAddr?: string; + prmYn?: string; +} + +export interface IP { + mngNo: number; + siteId: string; + ipAddr: string; + prmYn: string; + mode: string; +} + +interface SortConfig { + empty: boolean; + sorted: boolean; + unsorted: boolean; +} + +interface PageableConfig { + sort: SortConfig; + offset: number; + pageNumber: number; + pageSize: number; + paged: boolean; + unpaged: boolean; +} + +interface PageableList { + content: T[]; + pageable: PageableConfig; + sort: SortConfig; + totalPages: number; + totalElements: number; + number: number; + numberOfElements: number; + size: number; + first: boolean; + last: boolean; + empty: boolean; +} + +export type IpList = PageableList; + +const client = getApiClient(); + +export const getIpList = async (params: IpParams) => { + const response = await client.get(`${ENDPOINT.CMS_SERVICE.IPS}`, { + params, + }); + return response.data; +}; diff --git a/packages/common/components/src/config/api.ts b/packages/common/components/src/config/api.ts index 2b2612ffb..12efdd8bd 100644 --- a/packages/common/components/src/config/api.ts +++ b/packages/common/components/src/config/api.ts @@ -3,6 +3,7 @@ export const ENDPOINT = { MENU_ROLES: "/cms-service/api/v1/menu-roles", SITES: "/cms-service/api/v1/sites", POPUPS: "/cms-service/api/v1/popups", + IPS: "/cms-service/api/v1/site-ips", }, USER_SERVICE: { USERS: "/user-service/api/v1/users", diff --git a/packages/common/components/src/lib/client-ip-guard.tsx b/packages/common/components/src/lib/client-ip-guard.tsx new file mode 100644 index 000000000..e6b30fe81 --- /dev/null +++ b/packages/common/components/src/lib/client-ip-guard.tsx @@ -0,0 +1,59 @@ +import { useEffect, useState } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { getConfig } from "@common/config"; +import { getSiteInfo } from "../api/site"; +import { getIpList } from "../api/ip"; +import { ENDPOINT } from "../config"; + +const { SITE_ID } = getConfig(); + +const ClientIpGuard = ({ + currentPath, + children, +}: { + currentPath?: string; + children: React.ReactNode; +}) => { + const queryClient = useQueryClient(); + const [clientIp, setClientIp] = useState(""); + const [isAllowed, setIsAllowed] = useState(true); + + useEffect(() => { + (async () => { + const response = await fetch("https://api.ipify.org?format=json"); + const data = await response.json(); + if (!data?.ip) return; + setClientIp(data.ip); + })(); + }, []); + + useEffect(() => { + (async () => { + if (!SITE_ID || !clientIp) return; + + const siteInfo = await queryClient.ensureQueryData({ + queryKey: [ENDPOINT.CMS_SERVICE.SITES, SITE_ID], + queryFn: () => getSiteInfo(SITE_ID), + }); + + if (siteInfo.siteKndCd === "O") return; + + const { content: ipList } = await getIpList({ + siteId: SITE_ID, + prmYn: "Y", + }); + + const allowedClientIp = ipList.some((ip) => ip.ipAddr === clientIp); + setIsAllowed(allowedClientIp); + })(); + }, [queryClient, clientIp, currentPath]); + + if (!isAllowed) { + alert("허용되지 않은 IP로 접근하였습니다."); + return <>; + } + + return <>{children}; +}; + +export { ClientIpGuard }; diff --git a/packages/common/components/src/lib/use-pathname.ts b/packages/common/components/src/lib/use-pathname.ts new file mode 100644 index 000000000..9e14c0d89 --- /dev/null +++ b/packages/common/components/src/lib/use-pathname.ts @@ -0,0 +1,43 @@ +import { useEffect, useState } from "react"; + +const initHistoryPatch = (() => { + let initialized = false; + return () => { + if (initialized) return; + initialized = true; + + const patch = (method: "pushState" | "replaceState") => { + const original = history[method]; + history[method] = function (...args) { + const result = original.apply(this, args); + window.dispatchEvent(new Event("locationchange")); + return result; + }; + }; + + patch("pushState"); + patch("replaceState"); + }; +})(); + +export const usePathname = () => { + const [pathname, setPathname] = useState(""); + + useEffect(() => { + initHistoryPatch(); + + const update = () => { + queueMicrotask(() => setPathname(window.location.pathname)); + }; + + window.addEventListener("popstate", update); + window.addEventListener("locationchange", update); + + return () => { + window.removeEventListener("popstate", update); + window.removeEventListener("locationchange", update); + }; + }, []); + + return pathname; +}; diff --git a/packages/common/components/src/ui/header/index.tsx b/packages/common/components/src/ui/header/index.tsx index 5733c4168..8852d29f0 100644 --- a/packages/common/components/src/ui/header/index.tsx +++ b/packages/common/components/src/ui/header/index.tsx @@ -78,6 +78,13 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) { } }, []); + const getLogoImage = () => { + const siteThemeKey = getCookie("site-theme-key"); + if (siteThemeKey === "blue" || siteThemeKey === "green") + return images.logoWhite; + return images.logo; + }; + useEffect(() => { if (!userPopupList?.length) return; @@ -98,7 +105,7 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {

- logo + logo {siteInfo?.siteNm} diff --git a/packages/common/components/src/ui/layout/base.tsx b/packages/common/components/src/ui/layout/base.tsx index f5db1c50a..6eb1322b0 100644 --- a/packages/common/components/src/ui/layout/base.tsx +++ b/packages/common/components/src/ui/layout/base.tsx @@ -1,6 +1,7 @@ import type { QueryClient } from "@tanstack/react-query"; import { QueryProvider } from "../../provider/query-provider"; import { PopupProvider } from "../../provider/popup-provider"; +import { ClientIpGuard } from "../../lib/client-ip-guard"; import type { MenuItem } from "../../api"; import { SidebarProvider } from "../sidebar"; import { LNB } from "../lnb"; @@ -21,25 +22,27 @@ export function BaseLayout({ }) { return ( - - -
-
-
- -
-
- {children} + + + +
+
+
+ +
+
+ {children} +
+
-
-
- - + + + ); } diff --git a/packages/common/components/src/ui/sidebar/sidebar.css b/packages/common/components/src/ui/sidebar/sidebar.css index b41ea1ace..b7ed79d73 100644 --- a/packages/common/components/src/ui/sidebar/sidebar.css +++ b/packages/common/components/src/ui/sidebar/sidebar.css @@ -36,6 +36,14 @@ [data-sidebar="trigger"] > svg { color: #fff !important; } + [data-radix-popper-content-wrapper] > * { + color: #0a0a0a !important; + background: #fff !important; + } + [data-radix-popper-content-wrapper] > *:hover { + color: #0a0a0a !important; + background: #fff !important; + } } [data-theme="red"] { @@ -78,5 +86,13 @@ [data-sidebar="trigger"] > svg { color: #fff !important; } + [data-radix-popper-content-wrapper] > * { + color: #0a0a0a !important; + background: #fff !important; + } + [data-radix-popper-content-wrapper] > *:hover { + color: #0a0a0a !important; + background: #fff !important; + } } }