Skip to content

Commit

Permalink
Add dialog, add ticket page
Browse files Browse the repository at this point in the history
  • Loading branch information
emscb committed Apr 14, 2024
1 parent 07ca0f8 commit 1448ef2
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 2 deletions.
9 changes: 9 additions & 0 deletions src/components/Nav/menus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ const Menus = {
},
],
},
ticket: {
name: "티켓",
sub: [
{
name: "구매하기",
path: "buy",
},
],
},
contribution: {
name: "기여하기",
sub: [
Expand Down
94 changes: 94 additions & 0 deletions src/components/common/Dialog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import { isEscKeyPressed } from "utils";
import "./style.scss";

type Props = React.HTMLAttributes<HTMLDivElement> & {
isOpened: boolean;
header: string;
confirmLabel?: string;
cancelLabel?: string;
isLoading?: boolean;

onConfirmed: () => void;
onCanceled: () => void;
};

type State = {
willNotShow: boolean;
ref: ReturnType<typeof React.createRef<HTMLDivElement>>;
};

const Dialog = ({
isOpened,
onConfirmed,
onCanceled,
confirmLabel,
cancelLabel,
header,
isLoading,
className,
onKeyDown,
children,
...rest
}: Props) => {
const [willNotShow, setWillNotShow] = useState<State["willNotShow"]>(false);
const ref = useRef<HTMLDivElement>(null);
const className_ =
`cm-dialog ${className ?? ""} ${isOpened ? "opened" : ""} ${willNotShow ? "closing" : ""}`
.replace(/\s\s+/g, " ")
.trim();

const close = useCallback(async () => {
setWillNotShow(true);
await new Promise((r) => setTimeout(r, 0.3 * 1000)); // animation
onCanceled();
}, [onCanceled]);

useEffect(() => {
if (isOpened) {
document.body.style.overflowY = "hidden";
setWillNotShow(false);
ref.current?.focus();
}
return () => {
document.body.style.overflowY = "auto";
};
}, [isOpened, ref]);

return (
<div
className={className_}
ref={ref}
tabIndex={0}
onKeyDown={(e) => {
if (isEscKeyPressed(e)) {
close();
return;
}
onKeyDown && onKeyDown(e);
}}
{...rest}
>
{isOpened && (
<div
onClick={(e) => {
e.stopPropagation();
}}
>
<div className="header">{header}</div>
<div className="content">{children}</div>
<div className="footer">
<button className="confirmBtn" onClick={onConfirmed}>
{confirmLabel ?? "확인"}
</button>
<button className="closeBtn" onClick={onCanceled}>
{cancelLabel ?? "취소"}
</button>
</div>
</div>
)}
</div>
);
};

export default Dialog;
119 changes: 119 additions & 0 deletions src/components/common/Dialog/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
div.cm-dialog {
display: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 99;
background-color: rgba(0, 0, 0, 0.6);

button {
outline: none;
cursor: pointer;
border: 0;
}

& > div {
width: 90%;
max-width: 512px;
margin: 0 auto;
border-radius: 0.3rem;
background-color: #fff;
animation: modal-show 0.3s;
overflow: hidden;

& > div.header {
position: relative;
padding: 16px 64px 16px 16px;
background-color: #f1f1f1;
font-weight: 700;
}

& > div.content {
max-height: 50vh;
padding: 1rem;
overflow-y: auto;
word-wrap: break-word;
border-top: 1px solid #dee2e6;
border-bottom: 1px solid #dee2e6;
}

& > div.footer {
padding: 12px 16px;
text-align: right;

button {
padding: 6px 12px;
color: #fff;
background-color: #6c757d;
border-radius: 5px;
font-size: 13px;
}
button+button {
margin-left: 8px;
}
}

&.closing {
animation: modal-hide 0.3s;
}
}

&.opened {
display: flex;
align-items: center;
animation: modal-bg-show 0.3s;

&.closing {
animation: modal-bg-hide 0.3s;
}
}
}

@keyframes modal-show {
from {
opacity: 0;
margin-top: -50px;
}
to {
opacity: 1;
margin-top: 0;
}
}

@keyframes modal-hide {
0% {
opacity: 1;
margin-top: 0;
}
80% {
opacity: 0;
margin-top: -50px;
}
100% {
opacity: 0;
margin-top: -50px;
}
}

@keyframes modal-bg-show {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

@keyframes modal-bg-hide {
0% {
opacity: 1;
}
80% {
opacity: 0;
}
100% {
opacity: 0;
}
}
3 changes: 2 additions & 1 deletion src/components/common/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { isEscKeyPressed } from "utils";

type Props = React.HTMLAttributes<HTMLDivElement> & {
isOpened: boolean;
onClose: () => void;
header: string;

onClose: () => void;
};

type State = {
Expand Down
80 changes: 80 additions & 0 deletions src/pages/Ticket/buy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import Page from "components/common/Page";
import React, { useState } from "react";
import styled from "styled-components";
import { Helmet } from "react-helmet";
import { tickets } from "./tickets";
import Dialog from "components/common/Dialog";

const BuyTicket = () => {
const [isDialogOpened, setIsDialogOpened] = useState<boolean>(false);
const [dialogHeader, setDialogHeader] = useState<string>("");
const [dialogContent, setDialogContent] = useState<any>();
const openDialog = (header: string, content: any) => {
setDialogHeader(header);
setDialogContent(content);
setIsDialogOpened(true);
};

return (
<Page>
<Helmet title="티켓 구매" />
<Dialog
header={dialogHeader}
isOpened={isDialogOpened}
confirmLabel="구매하기"
onConfirmed={() => {}}
onCanceled={() => {
setIsDialogOpened(false);
}}
>
{dialogContent}
</Dialog>
<h1>티켓 구매하기</h1>
<Container>
{Object.entries(tickets).map(([k, v]) => (
<div key={k}>
<h2>{v.name}</h2>
<table>
<tbody>
{v.tickets.map((t) => (
<tr key={t.name}>
<td>{t.name}</td>
<td>{t.price.toLocaleString()}</td>
<td>
<button
onClick={() => {
openDialog(`${v.name} 구매`, t.description);
}}
>
구매하기
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
))}
</Container>
</Page>
);
};

export default BuyTicket;

const Container = styled.div`
table {
width: 25vw;
td:first-of-type {
width: 25%;
font-weight: bold;
}
td:nth-of-type(2) {
width: 10%;
}
td:nth-of-type(3) {
width: 10%;
}
}
`;
64 changes: 64 additions & 0 deletions src/pages/Ticket/tickets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from "react";

export const tickets: {
[ticketType: string]: {
name: string;
tickets: {
name: string;
price: number;
description: string | React.ReactElement;
}[];
};
} = {
conference: {
name: "컨퍼런스 티켓",
tickets: [
{
name: "컨퍼런스 티켓",
price: 70000,
description: (
<p>
주말에 열리는 컨퍼런스 티켓입니다.
<br />
<br />
컨퍼런스 15일 전(2024.10.11.)까지 100% 환불이 가능합니다.
</p>
),
},
],
},
tutorial: {
name: "튜토리얼 티켓",
tickets: [
{
name: "Django로 웹사이트 만들기",
price: 20000,
description: (
<p>
금요일에 진행되는 튜토리얼 (Django로 웹사이트 만들기) 티켓입니다.
<br />
<br />
2024.09.30.까지 환불 가능합니다.
</p>
),
},
],
},
sprint: {
name: "스프린트 티켓",
tickets: [
{
name: "django",
price: 20000,
description: (
<p>
금요일에 진행되는 스프린트 (django) 티켓입니다.
<br />
<br />
2024.09.30.까지 환불 가능합니다.
</p>
),
},
],
},
};
Loading

0 comments on commit 1448ef2

Please sign in to comment.