Skip to content
3 changes: 3 additions & 0 deletions public/assets/dashboard/mail/Arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions public/assets/dashboard/mail/Notice.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/assets/dashboard/menu/SentMail(black).svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/assets/dashboard/menu/SentMail(pink).svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion src/app/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import EventTagPage from '../../pages/dashboard/ui/EventTagPage';
import TicketListPage from '../../pages/dashboard/ui/ticket/TicketListPage';
import TicketCreatePage from '../../pages/dashboard/ui/ticket/TicketCreatePage';
import HostInfoPage from '../../pages/menu/ui/HostInfoPage';
import EmailPage from '../../pages/dashboard/ui/EmailPage';
import EmailPage from '../../pages/dashboard/ui/mail/EmailPage';
import EventDetailsPage from '../../pages/event/ui/EventDetailsPage';
import ParticipantsManagementPage from '../../pages/dashboard/ui/ParticipantsMangementPage';
import MailBoxPage from '../../pages/dashboard/ui/mail/MailBoxPage';
import EmailEditPage from '../../pages/dashboard/ui/mail/EmailEditPage';

const routesConfig = [
{
Expand Down Expand Up @@ -131,6 +133,16 @@ const routesConfig = [
element: <EmailPage />,
requiresAuth: false,
},
{
path: DASHBOARD_ROUTES.mailBox,
element: <MailBoxPage />,
requiresAuth: false,
},
{
path: DASHBOARD_ROUTES.emailEdit,
element: <EmailEditPage />,
requiresAuth: false,
},
{
path: DASHBOARD_ROUTES.participantsMangement,
element: <ParticipantsManagementPage />,
Expand Down
2 changes: 2 additions & 0 deletions src/app/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,7 @@ export const DASHBOARD_ROUTES = {
ticket: `${MAIN_ROUTES.dashbord}/ticket`,
ticketCreate: `${MAIN_ROUTES.dashbord}/ticket/create`,
email: `${MAIN_ROUTES.dashbord}/email`,
mailBox: `${MAIN_ROUTES.dashbord}/mailBox`,
emailEdit: `${MAIN_ROUTES.dashbord}/edit-email`,
participantsMangement: `${MAIN_ROUTES.dashbord}/participants-mangement`,
};
4 changes: 2 additions & 2 deletions src/features/dashboard/ui/EmailInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import MultilineTextField from '../../../../design-system/ui/textFields/Multilin
import EmailInputBase from '../../../shared/ui/EmailInputBase';

interface EmailInputProps {
type?: '이메일 예약 발송' | '선택 이메일 보내기';
type?: '이메일 예약 발송' | '선택 이메일 보내기' | '이메일 내용 수정';
openSelectTicket: () => void;
allParticipantEmails: string[];
}
Expand All @@ -21,7 +21,7 @@ const EmailInput = ({ type = '이메일 예약 발송', openSelectTicket, allPar
setAllEmails([]);
};
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-4 mb-2">
<div className="w-full text-center font-bold text-xl">{type}</div>

{/* 수신자와 제목 입력하는 부분 */}
Expand Down
29 changes: 29 additions & 0 deletions src/pages/dashboard/ui/mail/EmailEditPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import DashboardLayout from '../../../../shared/ui/backgrounds/DashboardLayout';
import EmailInput from '../../../../features/dashboard/ui/EmailInput';
import { participantsInfo } from '../../../../shared/types/participantInfoType';
import TimePicker from '../../../../features/event-manage/ui/TimePicker';
import Button from '../../../../../design-system/ui/Button';
import SelectTicketModal from '../../../../widgets/dashboard/ui/SelectTicketModal';

const EmailEditPage = () => {
const navigate = useNavigate();
const [ticketModalOpen, setTicketModalOpen] = useState(false);
return (
<DashboardLayout centerContent="WOOACON 2024">
<div className="flex flex-col gap-5 mt-8 px-7">
<EmailInput
type="이메일 내용 수정"
openSelectTicket={() => setTicketModalOpen(true)}
allParticipantEmails={participantsInfo.map(p => p.email)}
/>
{/*시간 선택 컴포넌트*/}
<TimePicker />
<Button label="보내기" onClick={() => navigate('/dashboard/mailBox')} className="w-full h-12 rounded-full" />
</div>
{ticketModalOpen && <SelectTicketModal onClose={() => setTicketModalOpen(false)} />}
</DashboardLayout>
);
};
export default EmailEditPage;
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import DashboardLayout from '../../../shared/ui/backgrounds/DashboardLayout';
import Button from '../../../../design-system/ui/Button';
import TimePicker from '../../../features/event-manage/ui/TimePicker';
import EmailInput from '../../../features/dashboard/ui/EmailInput';
import { participantsInfo } from '../../../shared/types/participantInfoType';
import DashboardLayout from '../../../../shared/ui/backgrounds/DashboardLayout';
import Button from '../../../../../design-system/ui/Button';
import TimePicker from '../../../../features/event-manage/ui/TimePicker';
import EmailInput from '../../../../features/dashboard/ui/EmailInput';
import { participantsInfo } from '../../../../shared/types/participantInfoType';
import { useState } from 'react';
import SelectTicketModal from '../../../widgets/dashboard/ui/SelectTicketModal';
import SelectTicketModal from '../../../../widgets/dashboard/ui/SelectTicketModal';
import { useNavigate } from 'react-router-dom';

const EmailPage = () => {
const navigate = useNavigate();
const [ticketModalOpen, setTicketModalOpen] = useState(false);
return (
<DashboardLayout centerContent="WOOACON 2024">
Expand All @@ -18,7 +20,7 @@ const EmailPage = () => {
{/*시간 선택 컴포넌트*/}
<TimePicker />
<div className="flex-grow"></div>
<Button label="보내기" onClick={() => console.log('post 요청')} className="w-full h-12 rounded-full" />
<Button label="보내기" onClick={() => navigate('/dashboard/mailBox')} className="w-full h-12 rounded-full" />
</div>
{ticketModalOpen && <SelectTicketModal onClose={() => setTicketModalOpen(false)} />}
</DashboardLayout>
Expand Down
48 changes: 48 additions & 0 deletions src/pages/dashboard/ui/mail/MailBoxPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState } from 'react';
import TextButton from '../../../../../design-system/ui/buttons/TextButton';
import DashboardLayout from '../../../../shared/ui/backgrounds/DashboardLayout';
import SearchBar from '../../../../shared/ui/SearchBar';
import SentMailCard from '../../../../widgets/dashboard/ui/SentMailCard';
import { mailInfo } from '../../../../shared/types/mailInfoType';
import EmailDeleteMoal from '../../../../widgets/dashboard/ui/EmailDeleteModal';

const MailBoxPage = () => {
const [listType, setListType] = useState<'all' | 'pending'>('all');
const [isModalOpen, setIsModalOpen] = useState(false);
const currentDate = new Date();

const pendingMails = mailInfo.filter(mail => new Date(mail.date) > currentDate);

return (
<DashboardLayout centerContent="WOOACON 2024">
<div className={`flex flex-col gap-2 mt-8 px-7 ${isModalOpen ? 'blur-sm' : ''}`}>
<h1 className="w-full text-center font-bold text-xl">보낸 메일함</h1>
<div className="flex justify-end">
<SearchBar placeholder="제목 검색" className="w-[35%] my-2" />
</div>
<div className="flex gap-3 font-semibold text-15">
<TextButton
label="모든 메일"
onClick={() => setListType('all')}
className={listType === 'all' ? 'text-main' : ''}
/>
<TextButton
label="미발송 예약 메일"
onClick={() => setListType('pending')}
className={listType === 'pending' ? 'text-main' : ''}
/>
</div>
{(listType === 'all' ? mailInfo : pendingMails).map(mail => (
<SentMailCard
key={mail.id}
mail={mail}
isPending={listType === 'pending' ? true : false}
setIsModalOpen={setIsModalOpen}
/>
))}
</div>
{isModalOpen && <EmailDeleteMoal onClose={() => setIsModalOpen(false)} />}
</DashboardLayout>
);
};
export default MailBoxPage;
10 changes: 10 additions & 0 deletions src/shared/lib/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const formatDate = (isoDate: string) => {
const dateObj = new Date(isoDate);

const year = dateObj.getFullYear();
const month = dateObj.getMonth() + 1; // getMonth()는 0부터 시작하므로 +1
const day = dateObj.getDate();
const hours = dateObj.getHours();

return `${year}년 ${month}월 ${day}일, ${hours}시`;
};
3 changes: 3 additions & 0 deletions src/shared/types/dashboardType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import ticket from '../../../public/assets/dashboard/menu/Ticket(black).svg';
import clickedTicket from '../../../public/assets/dashboard/menu/Ticket(pink).svg';
import email from '../../../public/assets/dashboard/menu/Email(black).svg';
import clickedEmail from '../../../public/assets/dashboard/menu/Email(pink).svg';
import sentEmail from '../../../public/assets/dashboard/menu/SentMail(black).svg';
import clickedSentEmail from '../../../public/assets/dashboard/menu/SentMail(pink).svg';
import participants from '../../../public/assets/dashboard/menu/Participants(black).svg';
import clickedParticipants from '../../../public/assets/dashboard/menu/Participants(pink).svg';

Expand All @@ -27,6 +29,7 @@ export const menuLists = [
{ text: '이벤트 태그 정보', icon: tag, clickedIcon: clickedTag, path: '/dashboard/eventTag' },
{ text: '티켓 생성하기', icon: ticket, clickedIcon: clickedTicket, path: '/dashboard/ticket' },
{ text: '이메일 예약 발송', icon: email, clickedIcon: clickedEmail, path: '/dashboard/email' },
{ text: '보낸 이메일', icon: sentEmail, clickedIcon: clickedSentEmail, path: '/dashboard/mailBox' },
{
text: '구매/참가자 관리',
icon: participants,
Expand Down
58 changes: 58 additions & 0 deletions src/shared/types/mailInfoType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export interface mailInfoData {
id: number;
receiver: string;
title: string;
date: string;
content: string;
}

export const mailInfo: mailInfoData[] = [
{
id: 1,
receiver: '전체',
title: '행사 당일 체크인 관련 공지',
date: '2025-03-24T15:00',
content:
'행사 당일 선착순 입장입니다.\n구매하신 티켓을 내 티켓 페이지에 해당 이벤트 정보를 누르면 QR코드를 확인할 수 있습니다.\n입장은 정시에 시작되며 재입장은 불가합니다.\n\n행사 당일 행사장 내에 주차는 불가하며 인근 유로 주차장이나 대중교통을 이용하시길 바랍니다.',
},
{
id: 2,
receiver: '일반',
title: '행사 당일 체크인 관련 공지',
date: '2025-05-24T12:15',
content:
'행사 당일 선착순 입장입니다.\n구매하신 티켓을 내 티켓 페이지에 해당 이벤트 정보를 누르면 QR코드를 확인할 수 있습니다.\n입장은 정시에 시작되며 재입장은 불가합니다.\n\n행사 당일 행사장 내에 주차는 불가하며 인근 유로 주차장이나 대중교통을 이용하시길 바랍니다.',
},
{
id: 3,
receiver: '무료',
title: '행사 당일 체크인 관련 공지',
date: '2025-02-11T10:30',
content:
'행사 당일 선착순 입장입니다.\n구매하신 티켓을 내 티켓 페이지에 해당 이벤트 정보를 누르면 QR코드를 확인할 수 있습니다.\n입장은 정시에 시작되며 재입장은 불가합니다.\n\n행사 당일 행사장 내에 주차는 불가하며 인근 유로 주차장이나 대중교통을 이용하시길 바랍니다.',
},
{
id: 4,
receiver: '일반',
title: '행사 당일 체크인 관련 공지',
date: '2025-01-24T15:45',
content:
'행사 당일 선착순 입장입니다.\n구매하신 티켓을 내 티켓 페이지에 해당 이벤트 정보를 누르면 QR코드를 확인할 수 있습니다.\n입장은 정시에 시작되며 재입장은 불가합니다.\n\n행사 당일 행사장 내에 주차는 불가하며 인근 유로 주차장이나 대중교통을 이용하시길 바랍니다.',
},
{
id: 5,
receiver: '무료',
title: '행사 당일 체크인 관련 공지',
date: '2025-07-27T09:30',
content:
'행사 당일 선착순 입장입니다.\n구매하신 티켓을 내 티켓 페이지에 해당 이벤트 정보를 누르면 QR코드를 확인할 수 있습니다.\n입장은 정시에 시작되며 재입장은 불가합니다.\n\n행사 당일 행사장 내에 주차는 불가하며 인근 유로 주차장이나 대중교통을 이용하시길 바랍니다.',
},
{
id: 6,
receiver: '전체',
title: '행사 당일 체크인 관련 공지',
date: '2025-01-14T11:10',
content:
'행사 당일 선착순 입장입니다.\n구매하신 티켓을 내 티켓 페이지에 해당 이벤트 정보를 누르면 QR코드를 확인할 수 있습니다.\n입장은 정시에 시작되며 재입장은 불가합니다.\n\n행사 당일 행사장 내에 주차는 불가하며 인근 유로 주차장이나 대중교통을 이용하시길 바랍니다.',
},
];
26 changes: 26 additions & 0 deletions src/widgets/dashboard/ui/EmailDeleteModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import TertiaryButton from '../../../../design-system/ui/buttons/TertiaryButton';
import notice from '../../../../public/assets/dashboard/mail/Notice.svg';

interface EmailDeleteMoalProps {
onClose: () => void;
}

const EmailDeleteMoal = ({ onClose }: EmailDeleteMoalProps) => {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center w-full max-w-lg mx-auto backdrop-blur-sm">
<div className="flex flex-col items-center justify-center w-[90%] gap-5 py-6 bg-white border border-placeholderText rounded-lg p-6">
<img src={notice} alt="경고 아이콘" className="w-20" />
<span className="text-14 font-semibold text-center">
이메일을 삭제하면 예약이 자동으로 취소됩니다.
<br />
그래도 삭제하시겠습니까?
</span>
<div className="flex justify-end gap-4">
<TertiaryButton label="취소" type="button" color="black" size="medium" onClick={onClose} />
<TertiaryButton label="삭제" type="button" color="pink" size="medium" onClick={onClose} />
</div>
</div>
</div>
);
};
export default EmailDeleteMoal;
60 changes: 60 additions & 0 deletions src/widgets/dashboard/ui/SentMailCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { mailInfoData } from '../../../shared/types/mailInfoType';
import arrow from '../../../../public/assets/dashboard/mail/Arrow.svg';
import { useState } from 'react';
import IconButton from '../../../../design-system/ui/buttons/IconButton';
import { formatDate } from '../../../shared/lib/date';
import TertiaryButton from '../../../../design-system/ui/buttons/TertiaryButton';
import { useNavigate } from 'react-router-dom';

interface SentMailCardProps {
mail: mailInfoData;
isPending: boolean;
setIsModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
}

const SentMailCard = ({ mail, isPending = false, setIsModalOpen }: SentMailCardProps) => {
const navigate = useNavigate();
const [isOpen, setIsOpen] = useState(false);

return (
<div className="p-4 bg-white rounded-[5px] border-[0.5px] border-gray4 transition-all">
<div className="flex justify-between transition-transform">
<div className="flex flex-col gap-1 text-md">
<div className="flex items-center gap-2">
<p className="text-main font-semibold">[{mail.receiver}]</p>
<p>{mail.title}</p>
</div>
<p className="text-sm text-placeholderText">{formatDate(mail.date)}</p>
</div>
<IconButton
iconPath={<img src={arrow} alt="화살표" className={`${isOpen ? 'rotate-180' : ''}`} />}
onClick={() => setIsOpen(!isOpen)}
/>
</div>
{isOpen && (
<div className="flex flex-col">
<p className="mt-4 text-14 whitespace-pre-line">{mail.content}</p>
{isPending && (
<div className="flex justify-end gap-3">
<TertiaryButton
label="삭제하기"
type="button"
color="black"
size="medium"
onClick={() => setIsModalOpen(true)}
/>
<TertiaryButton
label="수정하기"
type="button"
color="pink"
size="medium"
onClick={() => navigate('/dashboard/edit-email')}
/>
</div>
)}
</div>
)}
</div>
);
};
export default SentMailCard;
Loading