Skip to content

Commit

Permalink
Merge pull request #263 from Nexters/feat/home-popup
Browse files Browse the repository at this point in the history
홈 팝업 Dialog 작업
  • Loading branch information
hexdrinker authored Jan 15, 2025
2 parents 1de1802 + 70c356a commit baee743
Show file tree
Hide file tree
Showing 13 changed files with 383 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import styled from '@emotion/styled';
import { mq_lg } from '@boolti/ui';

interface PopupImageProps {
hasDetail: boolean;
}

const HomePopupContent = styled.div`
margin: 0 -24px;
${mq_lg} {
margin: 0;
width: 500px;
}
`;

const PopupImage = styled.img<PopupImageProps>`
border-top-left-radius: 8px;
border-top-right-radius: 8px;
cursor: ${({ hasDetail }) => (hasDetail ? 'pointer' : 'default')};
${mq_lg} {
width: 500px;
height: 578px;
}
`;

const PopupFooter = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`;

const CheckLabel = styled.label`
display: flex;
align-items: center;
gap: 8px;
padding: 16px 20px;
color: ${({ theme }) => theme.palette.grey.g60};
cursor: pointer;
`;

const CloseButton = styled.button`
padding: 16px 20px;
color: ${({ theme }) => theme.palette.grey.g100};
cursor: pointer;
`;

export default {
HomePopupContent,
CheckLabel,
PopupImage,
PopupFooter,
CloseButton,
};
55 changes: 55 additions & 0 deletions apps/admin/src/components/EventPopupContent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Checkbox } from '@boolti/ui';
import Styled from './EventPopupContent.styles';
import { useState } from 'react';
import { useBodyScrollLock } from '~/hooks/useBodyScrollLock';
import { useNavigate } from 'react-router-dom';
import useCookie from '~/hooks/useCookie';

interface HomePopupContentProps {
id: number;
imagePath: string;
detailPath: string | null;
onClose: () => void;
}
const EventPopupContent = ({ id, imagePath, detailPath, onClose }: HomePopupContentProps) => {
const [checked, setChecked] = useState(false);
const navigate = useNavigate();
useBodyScrollLock();
const { setCookie } = useCookie();

const onChange = () => {
setChecked((checked) => !checked);
};

const closeDialog = () => {
if (checked) {
const midnight = new Date();
midnight.setHours(24, 0, 0, 0);
setCookie('popup', `${id}`, { expires: midnight.toUTCString() });
}
onClose();
};

const onClickImage = () => {
if (!detailPath) {
return;
}
navigate(detailPath);
onClose();
};

return (
<Styled.HomePopupContent>
<Styled.PopupImage src={imagePath} onClick={onClickImage} hasDetail={!!detailPath} />
<Styled.PopupFooter>
<Styled.CheckLabel>
<Checkbox variant="main" checked={checked} onChange={onChange} />
오늘 하루 그만보기
</Styled.CheckLabel>
<Styled.CloseButton onClick={closeDialog}>닫기</Styled.CloseButton>
</Styled.PopupFooter>
</Styled.HomePopupContent>
);
};

export default EventPopupContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import styled from '@emotion/styled';
import { Button, mq_lg } from '@boolti/ui';

const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
${mq_lg} {
width: 410px;
padding: 0 20px 20px 20px;
}
`;

const Header = styled.div`
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 12px 0;
`;

const CloseButton = styled.button`
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: ${({ theme }) => theme.palette.grey.g70};
svg {
width: 24px;
height: 24px;
}
`;

const Title = styled.h1`
font-size: 18px;
color: ${({ theme }) => theme.palette.grey.g70};
margin-bottom: 24px;
`;

const Emphasized = styled.div`
width: 100%;
border-radius: 4px;
padding: 16px;
background-color: ${({ theme }) => theme.palette.grey.g00};
color: ${({ theme }) => theme.palette.red.main};
font-size: 15px;
text-align: center;
margin-bottom: 16px;
`;

const Description = styled.p`
color: ${({ theme }) => theme.palette.grey.g60};
font-size: 14px;
margin-bottom: 28px;
`;

const ConfirmButton = styled(Button)`
width: 100%;
`;

export default {
Container,
Header,
CloseButton,
Title,
Emphasized,
Description,
ConfirmButton,
};
50 changes: 50 additions & 0 deletions apps/admin/src/components/NoticePopupContent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { CloseIcon } from '@boolti/icon';
import Styled from './NoticePopupContent.styles';
import { useMemo } from 'react';

interface NoticePopupContentProps {
title: string;
description: string;
onClose: () => void;
}

const NoticePopupContent = ({ title, description, onClose }: NoticePopupContentProps) => {
const dividedDescription = useMemo(() => {
const regex = /`([^`]*)`|([^`]+)/g;
let match;
const result: { emphasized: string[]; normal: string[] } = {
emphasized: [],
normal: [],
};

while ((match = regex.exec(description)) !== null) {
if (match[1] !== undefined) {
result.emphasized.push(match[1]);
} else if (match[2] !== undefined) {
result.normal.push(match[2].trim());
}
}

return result;
}, [description]);

return (
<Styled.Container>
<Styled.Header>
<Styled.CloseButton onClick={onClose}>
<CloseIcon />
</Styled.CloseButton>
</Styled.Header>
<Styled.Title>{title}</Styled.Title>
{dividedDescription.emphasized.length ? (
<Styled.Emphasized>{dividedDescription.emphasized}</Styled.Emphasized>
) : null}
<Styled.Description>{dividedDescription.normal}</Styled.Description>
<Styled.ConfirmButton colorTheme="primary" size="medium" onClick={onClose}>
확인
</Styled.ConfirmButton>
</Styled.Container>
);
};

export default NoticePopupContent;
54 changes: 54 additions & 0 deletions apps/admin/src/hooks/useCookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
interface CookieOptions {
path?: string;
'max-age'?: number;
domain?: string;
secure?: boolean;
[key: string]: unknown;
}

const uscCookie = () => {
const setCookie = (name: string, value: string, options: CookieOptions = {}) => {
options.path = options.path || '/';

let updatedCookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;

for (const [key, optionValue] of Object.entries(options)) {
updatedCookie += `; ${key}`;
if (optionValue !== true) {
updatedCookie += `=${optionValue}`;
}
}

console.log(`updatedCookie: ${updatedCookie}`);

document.cookie = updatedCookie;
};

const getCookie = (name: string) => {
const cookieString = document.cookie;
const cookies = cookieString.split('; ');

for (const cookie of cookies) {
const [key, value] = cookie.split('=');
if (decodeURIComponent(key) === name) {
return decodeURIComponent(value);
}
}

return null;
};

const deleteCookie = (name: string) => {
setCookie(name, '', {
'max-age': -1,
});
};

return {
setCookie,
getCookie,
deleteCookie,
};
};

export default uscCookie;
62 changes: 62 additions & 0 deletions apps/admin/src/hooks/usePopupDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useEffect } from 'react';
import useCookie from './useCookie';
import { useDialog } from '@boolti/ui';
import { Popup } from '@boolti/api';
import NoticePopupContent from '~/components/NoticePopupContent';
import EventPopupContent from '~/components/EventPopupContent';

const usePopupDialog = (popupData?: Popup) => {
const eventPopupDialog = useDialog();
const noticePopupDialog = useDialog();
const { getCookie } = useCookie();

useEffect(() => {
if (!popupData) {
return;
}
const today = new Date();
const startDate = new Date(popupData.startDate);
const endDate = new Date(popupData.endDate);
if (!(startDate <= today && today <= endDate)) {
return;
}
const hasCookie = !!getCookie('popup');

switch (popupData.type) {
case 'EVENT':
if (hasCookie) {
return;
}
eventPopupDialog.open({
content: (
<EventPopupContent
id={popupData.id}
imagePath={popupData.description}
detailPath={popupData.eventUrl}
onClose={eventPopupDialog.close}
/>
),
mobileType: 'centerPopup',
isAuto: true,
contentPadding: '0',
});
return;
case 'NOTICE':
noticePopupDialog.open({
content: (
<NoticePopupContent
title={popupData.noticeTitle as string}
description={popupData.description}
onClose={noticePopupDialog.close}
/>
),
mobileType: 'centerPopup',
isAuto: true,
contentPadding: '0',
});
return;
}
}, [popupData]);
};

export default usePopupDialog;
4 changes: 4 additions & 0 deletions apps/admin/src/pages/HomePage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
queryKeys,
useLogout,
usePopup,
useQueryClient,
useSettlementBanners,
useShowList,
Expand All @@ -21,6 +22,7 @@ import { useAuthAtom } from '~/atoms/useAuthAtom';
import SettingDialogContent from '~/components/SettingDialogContent';
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';
import usePopupDialog from '~/hooks/usePopupDialog';

const bannerDescription = {
REQUIRED: '공연의 정산 내역서가 도착했어요. 내역을 확인한 후 정산을 요청해 주세요.',
Expand All @@ -42,6 +44,8 @@ const HomePage = () => {
const { data: userProfileData, isLoading: isUserProfileLoading } = useUserProfile();
const { data: showList = [], isLoading: isShowListLoading } = useShowList();
const { data: settlementBanners } = useSettlementBanners();
const { data: popupData } = usePopup();
usePopupDialog(popupData);

const { imgPath, nickname = '', userCode } = userProfileData ?? {};

Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import useCastTeamList from './useCastTeamList';
import useAdminTicketList from './useAdminTicketList';
import useAdminSalesTicketList from './useAdminSalesTicketList';
import useAdminReservationSummaryV2 from './useAdminReservationSummaryV2';
import usePopup from './usePopup';
import useSuperAdminShowSettlementStatement from './useSuperAdminShowSettlementStatement';

export {
Expand Down Expand Up @@ -85,5 +86,6 @@ export {
useSuperAdminSalesTicketList,
useSuperAdminInvitationTicketList,
useSuperAdminInvitationCodeList,
usePopup,
useSuperAdminShowSettlementStatement,
};
Loading

0 comments on commit baee743

Please sign in to comment.