feat: 미리보기 팝업 header 컴포넌트에 적용

This commit is contained in:
2025-05-24 00:47:27 +09:00
parent a27e9f1cdd
commit 6a7218b8ce
12 changed files with 204 additions and 18 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "features",
"version": "0.0.0",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev -p 13000",
@@ -9,13 +9,9 @@ import { StoreProvider, PromptProvider } from "@/shared/providers";
import { AlertDialog } from "@/shared/ui/alert-dialog";
import { PromptDialog } from "@/shared/ui/prompt-dialog";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import { initializeNextClientConfig } from "@common/config";
const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
const queryClient = new QueryClient();
initializeNextClientConfig();
return (
<QueryClientProvider client={queryClient}>
<StoreProvider>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "cs",
"version": "0.0.0",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -76,13 +76,6 @@ const PreviewButton = ({ popupSn }: { popupSn: number }) => {
e.stopPropagation();
try {
const cookieName = `cs-popup-hide-for-day-${popupSn}`;
const cookies = document.cookie.split(";");
const popupCookie = cookies.find((cookie) =>
cookie.trim().startsWith(`${cookieName}=`),
);
if (popupCookie) return;
const response = await PopupApi.getPopupInfo(popupSn);
if (!response) return;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@common/assets",
"version": "0.0.0",
"version": "1.0.0",
"private": true,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@common/components",
"version": "0.0.0",
"version": "1.0.0",
"private": true,
"sideEffects": false,
"type": "module",
@@ -1,4 +1,5 @@
export * from "./client";
export * from "./menu";
export * from "./popup";
export * from "./site";
export * from "./user";
@@ -0,0 +1,69 @@
import { getApiClient } from "./client";
import { ENDPOINT } from "../config";
export interface PopupInfo {
popupSn: number;
popupTypeCd: string;
popupTypeNm: string;
contsTypeCd: string;
contsTypeNm: string;
popupTtl: string;
popupCn: string;
popupWdth: number;
popupHgt: number;
popupVtclPstn: number;
popupHrzntltyPstn: number;
popupEndUseYn: string;
popupLnkgUrl: string;
atflCd: string;
useYn: string;
regDt: string;
mdfcnDt: string;
rgTrNm: string;
mdfrNm: 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<T> {
content: T[];
pageable: PageableConfig;
sort: SortConfig;
totalPages: number;
totalElements: number;
number: number;
numberOfElements: number;
size: number;
first: boolean;
last: boolean;
empty: boolean;
}
const client = getApiClient();
export const getPopupList = async () => {
const response = await client.get<PageableList<PopupInfo>>(
`${ENDPOINT.CMS_SERVICE.POPUPS}`,
);
return response.data;
};
export const getPopupInfo = async (popupSn: number) => {
const response = await client.get<PopupInfo>(
`${ENDPOINT.CMS_SERVICE.POPUPS}/${popupSn}`,
);
return response.data;
};
@@ -2,6 +2,7 @@ export const ENDPOINT = {
CMS_SERVICE: {
MENU_ROLES: "/cms-service/api/v1/menu-roles",
SITES: "/cms-service/api/v1/sites",
POPUPS: "/cms-service/api/v1/popups",
},
USER_SERVICE: {
USERS: "/user-service/api/v1/users",
@@ -1,9 +1,15 @@
import { useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
import { images } from "@common/assets";
import { getConfig } from "@common/config";
import { ENDPOINT } from "../../config";
import { setCookie } from "../../lib";
import { getSiteInfo, getUserInfo } from "../../api";
import {
getPopupInfo,
getPopupList,
getSiteInfo,
getUserInfo,
} from "../../api";
import {
DropdownMenu,
DropdownMenuTrigger,
@@ -35,6 +41,53 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
enabled: !viewOnly,
});
const { data: popupList } = useQuery({
queryKey: ["ENDPOINT.CMS_SERVICE.POPUPS"],
queryFn: () => getPopupList(),
enabled: false,
});
useEffect(() => {
if (!popupList?.content.length) return;
const openPopups = async () => {
for (const popup of popupList.content) {
try {
const cookieName = `cs-popup-hide-for-day-${popup.popupSn}`;
const cookies = document.cookie.split(";");
const popupCookie = cookies.find((cookie) =>
cookie.trim().startsWith(`${cookieName}=`),
);
if (popupCookie) continue;
const response = await getPopupInfo(popup.popupSn);
if (!response) continue;
const width = response.popupWdth;
const height = response.popupHgt;
const left = response.popupHrzntltyPstn + window.screenX;
const top = response.popupVtclPstn + window.screenY;
const popupWindow = window.open(
"",
`popup_preview_${popup.popupSn}`,
`width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,status=no,location=no,menubar=no`,
);
if (!popupWindow) continue;
popupWindow.document.write(
previewTemplate(popup.popupSn, response.popupTtl, response.popupCn),
);
popupWindow.document.close();
} catch (error) {
console.error("Failed to open popup:", error);
continue;
}
}
};
openPopups();
}, [popupList]);
return (
<header className="sticky top-0 z-30 box-border flex h-[5rem] w-full items-center justify-between border-b border-solid border-border bg-background">
<div className="flex items-center">
@@ -72,3 +125,76 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
</header>
);
}
const previewTemplate = (
popupSn: number,
popupTitle: string,
popupCn: string,
) => {
return `<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>팝업 미리보기</title>
<style>
*,*::before,*::after{box-sizing:border-box}
*{margin:0}
@media(prefers-reduced-motion:no-preference){html{interpolate-size:allow-keywords}}
body{line-height:1.5;-webkit-font-smoothing:antialiased}
img,picture,video,canvas,svg{display:block;max-width:100%}
input,button,textarea,select{font:inherit}
p,h1,h2,h3,h4,h5,h6{overflow-wrap:break-word}
p{text-wrap:pretty}
h1,h2,h3,h4,h5,h6{text-wrap:balance}
#root,#__next{isolation:isolate}
body{margin:0;padding:0;font-family:sans-serif;padding:1rem;background:#f1f6f9}
hr{margin:0.5rem 0 1rem;border:1px solid #eee}
pre{font-size:1rem;line-height:1}
.popup-container{width:100%;height:100%;overflow:auto;background:#fff;border-radius:0.5rem;padding:1rem;position:relative}
.popup-header h1{font-size:1.2rem;font-weight:600}
.popup-footer{display:flex;justify-content:space-between;margin-top:1rem;padding-top:1rem;border-top:1px solid #eee}
.popup-footer .left{display:flex;align-items:center}
.popup-footer .right{display:flex}
.popup-footer button{padding:0.5rem 1rem;border-radius:0.25rem;border:none;cursor:pointer;font-size:0.875rem}
.popup-footer button.close{background:#f1f6f9;color:#333}
.popup-footer button.close:hover{background:#e3eef8}
.popup-footer label{display:flex;align-items:center;cursor:pointer;font-size:0.875rem}
.popup-footer input[type="checkbox"]{margin-right:0.5rem}
</style>
</head>
<body>
<div class="popup-container">
<div class="popup-header">
<h1>${popupTitle || "팝업 미리보기"}</h1>
</div>
<hr />
<pre>${popupCn || ""}</pre>
<div class="popup-footer">
<div class="left">
<label>
<input type="checkbox" id="doNotShowToday" />
오늘 하루 보지 않기
</label>
</div>
<div class="right">
<button class="close" onclick="closePopup()">닫기</button>
</div>
</div>
</div>
<script>
function closePopup() {
if (document.getElementById('doNotShowToday').checked) {
const popupId = '${popupSn}';
const expireDate = new Date();
expireDate.setDate(expireDate.getDate() + 1);
document.cookie = \`cs-popup-hide-for-day-\${popupId}=true; expires=\${expireDate.toUTCString()}; path=/\`;
}
window.close();
}
</script>
</body>
</html>
`;
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@common/config",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "./dist/index.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@common/network",
"version": "0.0.0",
"version": "1.0.0",
"private": true,
"main": "./index.ts",
"types": "./index.ts",