-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
371 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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%; | ||
} | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
), | ||
}, | ||
], | ||
}, | ||
}; |
Oops, something went wrong.