FOOTER 배너 표시

This commit is contained in:
2025-08-27 21:07:20 +09:00
parent 2368202fa7
commit 5ddf9fe6e0
5 changed files with 137 additions and 23 deletions
@@ -0,0 +1,14 @@
import { getConfig } from "@common/config";
import { ENDPOINT } from "../config";
const { PROXY_HOST } = getConfig();
const BASE_URL = `${PROXY_HOST}/server`;
export const getImageUrlByAtflCd = async (atflCd: string): Promise<string> => {
if (!atflCd) return "";
const response = await fetch(
`${BASE_URL}/${ENDPOINT.ATTACH_SERVICE.IMAGES}/atflCd/${atflCd}`,
);
const blob = await response.blob();
return URL.createObjectURL(blob);
};
@@ -0,0 +1,35 @@
import { getApiClient } from "./client";
import { getConfig } from "@common/config";
import { ENDPOINT } from "../config";
export interface Banner {
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 client = getApiClient();
export const getBannerList = async (siteId: string) => {
const response = await client.get<Banner[]>(
`${BASE_URL}/${ENDPOINT.CMS_SERVICE.BANNERS}/${siteId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
return response.data;
};
@@ -1,3 +1,5 @@
export * from "./attachment";
export * from "./banner";
export * from "./client";
export * from "./connection-log";
export * from "./menu";
@@ -9,8 +9,12 @@ export const ENDPOINT = {
CONNECTION_LOG: "/cms-service/api/v1/cntnlog",
NOTICE: "/cms-service/api/v1/noti",
NOTICE_USER: "/cms-service/api/v1/noti/user",
BANNERS: "/cms-service/api/v1/bnrs/user",
},
USER_SERVICE: {
USERS: "/user-service/api/v1/users",
},
ATTACH_SERVICE: {
IMAGES: "/attach-service/api/v1/images",
},
};
@@ -1,8 +1,9 @@
import { useRef } from "react";
import Image from "next/image";
import Autoplay from "embla-carousel-autoplay";
import { useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "../../config";
import { getSiteInfo } from "../../api";
import { getSiteInfo, getBannerList, getImageUrlByAtflCd } from "../../api";
import { getCookie } from "../../lib";
import {
Carousel,
@@ -14,17 +15,67 @@ import {
const siteId = getCookie("site-id-key") ?? "";
type BannerImageProps = {
atflCd: string;
bnrSn: number;
lnkgUrlAddr: string;
};
const BannerImage = ({ atflCd, bnrSn, lnkgUrlAddr }: BannerImageProps) => {
const { data: imageUrl, isLoading } = useQuery({
queryKey: [ENDPOINT.ATTACH_SERVICE.IMAGES, atflCd],
queryFn: () => getImageUrlByAtflCd(atflCd),
enabled: !!atflCd,
});
const handleClick = () => {
if (lnkgUrlAddr) {
window.open(lnkgUrlAddr, '_blank');
}
};
if (isLoading) {
return (
<div className="relative flex h-24 items-center justify-center bg-accent mx-auto">
<div className="text-sm text-muted-foreground">로딩 중...</div>
</div>
);
}
if (!imageUrl) {
return (
<div className="relative flex h-24 items-center justify-center bg-accent mx-auto">
<div className="text-sm text-muted-foreground">이미지 없음</div>
</div>
);
}
return (
<div
className="relative flex h-24 items-center justify-center cursor-pointer hover:opacity-80 transition-opacity mx-auto"
onClick={handleClick}
>
<Image src={imageUrl} className="object-contain" fill alt="배너 이미지" />
</div>
);
};
export function Footer() {
const { data: siteInfo } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SITES, siteId],
queryFn: () => getSiteInfo(siteId),
});
const { data: bannerList } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.BANNERS, siteId],
queryFn: () => getBannerList(siteId),
});
const plugin = useRef(Autoplay({ delay: 2000, stopOnInteraction: true }));
return (
<footer className="flex w-full shrink-0 items-start border-t border-solid border-border bg-background px-6 py-8 text-foreground">
<div className="w-1/2">
<footer className="flex w-full shrink-0 items-start border-t border-solid border-border bg-background px-6 pt-4 pb-1 text-foreground">
<div className="w-[55%]">
<h6 className="mb-4 text-2xl font-bold">{siteInfo?.siteNm ?? ""}</h6>
<p className="my-1 text-xl opacity-60">{siteInfo?.siteAddr ?? ""}</p>
<p className="my-1 text-xl opacity-60">
@@ -32,7 +83,7 @@ export function Footer() {
</p>
<p className="my-1 text-xl opacity-60">{siteInfo?.lwndCn ?? ""}</p>
</div>
<div className="flex w-1/2 flex-col">
<div className="flex w-[45%] flex-col">
<div className="flex justify-end gap-4">
<span className="text-xl">개인정보처리방침</span>
<span className="text-xl">이용약관</span>
@@ -42,21 +93,29 @@ export function Footer() {
<Carousel
plugins={[plugin.current]}
opts={{ loop: true }}
className="relative my-8 h-32"
className="relative my-8 h-24"
>
<CarouselContent>
{Array.from({ length: 10 }).map((_, i) => (
<CarouselItem key={`banner-${i}`} className="basis-1/3">
<div className="relative flex h-32 items-center justify-center">
<div className="flex h-full w-full items-center justify-center bg-accent">
{`${i + 1}번째 배너`}
</div>
{bannerList && bannerList.length > 0 ? (
bannerList.map((banner) => (
<CarouselItem key={`banner-${banner.bnrSn}`} className="basis-1/3">
<BannerImage
atflCd={banner.atflCd}
bnrSn={banner.bnrSn}
lnkgUrlAddr={banner.lnkgUrlAddr}
/>
</CarouselItem>
))
) : (
<CarouselItem className="basis-1/3">
<div className="relative flex h-24 w-full items-center justify-center bg-accent">
<div className="text-sm text-muted-foreground">배너가 없습니다</div>
</div>
</CarouselItem>
))}
)}
</CarouselContent>
<CarouselPrevious className="absolute left-2 top-1/2 !bg-[rgba(255,255,255,0.8)] text-accent-foreground" />
<CarouselNext className="absolute right-2 top-1/2 !bg-[rgba(255,255,255,0.8)] text-accent-foreground" />
<CarouselPrevious className="absolute left-2 top-1/2 !bg-[rgba(255,255,255,0.8)] text-accent-foreground z-10" />
<CarouselNext className="absolute right-2 top-1/2 !bg-[rgba(255,255,255,0.8)] text-accent-foreground z-10" />
</Carousel>
</div>
</footer>