diff --git a/src/components/commons/bottomSheet/BottomSheet.styled.ts b/src/components/commons/bottomSheet/BottomSheet.styled.ts index cc1e8e5c..27f8925f 100644 --- a/src/components/commons/bottomSheet/BottomSheet.styled.ts +++ b/src/components/commons/bottomSheet/BottomSheet.styled.ts @@ -1,10 +1,25 @@ -import styled from "styled-components"; +import styled, { keyframes } from "styled-components"; -export const BottomSheetWrapper = styled.section` - position: fixed; +const bottomSheetUp = keyframes` + 0% { transform: translateY(100%); } + 100% { transform: translateY(0); } +`; + +const bottomSheetDown = keyframes` + 0% { transform: translateY(0); } + 100% { transform: translateY(100%); } +`; + +export const BottomSheetWrapper = styled.section<{ $isOpen: boolean }>` + position: absolute; bottom: 0; - z-index: 1; + left: 0; + z-index: 10; display: flex; + justify-content: center; + width: 100%; + + animation: ${({ $isOpen }) => ($isOpen ? bottomSheetUp : bottomSheetDown)} 250ms ease-in-out; `; export const BottomSheetLayout = styled.section` diff --git a/src/components/commons/bottomSheet/BottomSheet.tsx b/src/components/commons/bottomSheet/BottomSheet.tsx index a7c35d1a..e60969f9 100644 --- a/src/components/commons/bottomSheet/BottomSheet.tsx +++ b/src/components/commons/bottomSheet/BottomSheet.tsx @@ -2,13 +2,14 @@ import { ReactNode } from "react"; import * as S from "./BottomSheet.styled"; export interface BottomSheetPropType { + isOpen: boolean; children?: ReactNode; title?: string; } -const BottomSheet = ({ title, children }: BottomSheetPropType) => { +const BottomSheet = ({ isOpen, title, children }: BottomSheetPropType) => { return ( - e.stopPropagation()}> + {title} {children} diff --git a/src/components/commons/bottomSheet/actionsBottomSheet/ActionBottomSheet.styled.ts b/src/components/commons/bottomSheet/actionsBottomSheet/ActionBottomSheet.styled.ts index 13efa8ef..e9f1e8ee 100644 --- a/src/components/commons/bottomSheet/actionsBottomSheet/ActionBottomSheet.styled.ts +++ b/src/components/commons/bottomSheet/actionsBottomSheet/ActionBottomSheet.styled.ts @@ -2,14 +2,22 @@ import styled from "styled-components"; import { IcomCopy } from "@assets/svgs"; -export const ActionBottomSheetWrapper = styled.section` +export const ActionBottomSheetWrapper = styled.section<{ $isOpen: boolean }>` position: fixed; - top: 0; bottom: 0; - left: auto; + z-index: 30; display: flex; + justify-content: center; width: 100%; height: 100%; + + background-color: rgb(0 0 0 / 50%); + visibility: ${({ $isOpen }) => ($isOpen ? "visible" : "hidden")}; + opacity: ${({ $isOpen }) => ($isOpen ? 1 : 0)}; + + transition: + opacity 250ms ease-in-out, + visibility 250ms ease-in-out; `; export const SubTitle = styled.h2` diff --git a/src/components/commons/bottomSheet/actionsBottomSheet/ActionBottomSheet.tsx b/src/components/commons/bottomSheet/actionsBottomSheet/ActionBottomSheet.tsx index a5710161..6ca94911 100644 --- a/src/components/commons/bottomSheet/actionsBottomSheet/ActionBottomSheet.tsx +++ b/src/components/commons/bottomSheet/actionsBottomSheet/ActionBottomSheet.tsx @@ -5,11 +5,12 @@ import OuterLayout from "@components/commons/bottomSheet/OuterLayout"; import ContextBox from "@components/commons/contextBox/ContextBox"; import { ContextBoxStyle } from "@typings/contextBoxProps"; -import React, { ReactNode, Children, isValidElement } from "react"; +import React, { Children, isValidElement, ReactNode, useEffect } from "react"; interface ActionBottomSheetProps extends React.ButtonHTMLAttributes, ContextBoxStyle { + isOpen: boolean; title?: string; subTitle?: string; children?: ReactNode; @@ -17,6 +18,7 @@ interface ActionBottomSheetProps } const ActionBottomSheet = ({ + isOpen, title, subTitle, onClickOutside, @@ -37,9 +39,20 @@ const ActionBottomSheet = ({ onClickOutside(); }; + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "auto"; + } + return () => { + document.body.style.overflow = "auto"; + }; + }, [isOpen]); + return ( - - + + {subTitle} {innerChildren} diff --git a/src/components/commons/bottomSheet/viewBottomSheet/ViewBottomSheet.styled.ts b/src/components/commons/bottomSheet/viewBottomSheet/ViewBottomSheet.styled.ts index 76f94b06..eab61d16 100644 --- a/src/components/commons/bottomSheet/viewBottomSheet/ViewBottomSheet.styled.ts +++ b/src/components/commons/bottomSheet/viewBottomSheet/ViewBottomSheet.styled.ts @@ -1,15 +1,23 @@ import styled from "styled-components"; -import { BoxTitleStyle, BoxDividerStyle } from "@typings/contextBoxProps"; +import { BoxDividerStyle, BoxTitleStyle } from "@typings/contextBoxProps"; -export const ViewBottomSheetWrapper = styled.section` +export const ViewBottomSheetWrapper = styled.section<{ $isOpen: boolean }>` position: fixed; - top: 0; bottom: 0; - left: auto; + z-index: 30; display: flex; + justify-content: center; width: 100%; height: 100%; + + background-color: rgb(0 0 0 / 50%); + visibility: ${({ $isOpen }) => ($isOpen ? "visible" : "hidden")}; + opacity: ${({ $isOpen }) => ($isOpen ? 1 : 0)}; + + transition: + opacity 250ms ease-in-out, + visibility 250ms ease-in-out; `; export const BoxTitle = styled.h1` diff --git a/src/components/commons/bottomSheet/viewBottomSheet/ViewBottomSheet.tsx b/src/components/commons/bottomSheet/viewBottomSheet/ViewBottomSheet.tsx index 3fbc253c..8d08b216 100644 --- a/src/components/commons/bottomSheet/viewBottomSheet/ViewBottomSheet.tsx +++ b/src/components/commons/bottomSheet/viewBottomSheet/ViewBottomSheet.tsx @@ -1,11 +1,12 @@ -import * as S from "./ViewBottomSheet.styled"; import BottomSheet from "@components/commons/bottomSheet/BottomSheet"; import OuterLayout from "@components/commons/bottomSheet/OuterLayout"; import ContextBox from "@components/commons/contextBox/ContextBox"; +import * as S from "./ViewBottomSheet.styled"; -import { ReactNode, Children, isValidElement } from "react"; +import { Children, isValidElement, ReactNode, useEffect } from "react"; interface ViewBottomSheetProps { + isOpen: boolean; title?: string; boxTitle?: string; boxTitleColor?: string; @@ -14,6 +15,7 @@ interface ViewBottomSheetProps { } const ViewBottomSheet = ({ + isOpen, title, boxTitle, boxTitleColor, @@ -35,9 +37,20 @@ const ViewBottomSheet = ({ onClickOutside(); }; + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "auto"; + } + return () => { + document.body.style.overflow = "auto"; + }; + }, [isOpen]); + return ( - - + + {boxTitle} diff --git a/src/components/commons/input/textField/TextField.tsx b/src/components/commons/input/textField/TextField.tsx index a56446ab..5f9336e0 100644 --- a/src/components/commons/input/textField/TextField.tsx +++ b/src/components/commons/input/textField/TextField.tsx @@ -71,6 +71,7 @@ const TextField = ({ }, } as ChangeEvent; + inputRef.current.focus(); onChange(newEvent); } }; @@ -85,8 +86,8 @@ const TextField = ({ { const [isOpen, setIsOpen] = useState(false); @@ -19,22 +19,22 @@ const ActionBottomSheetTest = () => { return ( - {isOpen && ( - - - - - - - )} + + + + + + + ); }; @@ -42,8 +42,10 @@ const ActionBottomSheetTest = () => { export default ActionBottomSheetTest; const Test = styled.div` - width: 37.5rem; - height: 66.7rem; + display: flex; + flex-direction: column; + align-items: center; + padding: 0 2.4rem; background-color: white; `; diff --git a/src/pages/ViewBottomSheetTest.tsx b/src/pages/ViewBottomSheetTest.tsx index 97f91d7c..8010ecb4 100644 --- a/src/pages/ViewBottomSheetTest.tsx +++ b/src/pages/ViewBottomSheetTest.tsx @@ -1,9 +1,9 @@ -import styled from "styled-components"; +import OuterLayout from "@components/commons/bottomSheet/OuterLayout"; import ViewBottomSheet from "@components/commons/bottomSheet/viewBottomSheet/ViewBottomSheet"; -import Context from "@components/commons/contextBox/Context"; import Button from "@components/commons/button/Button"; -import OuterLayout from "@components/commons/bottomSheet/OuterLayout"; +import Context from "@components/commons/contextBox/Context"; import { useState } from "react"; +import styled from "styled-components"; const ViewBottomSheetTest = () => { const [isOpen, setIsOpen] = useState(false); @@ -19,26 +19,26 @@ const ViewBottomSheetTest = () => { return ( - {isOpen && ( - - - - - - - - - - )} + + + + + + + + + + ); }; @@ -46,8 +46,10 @@ const ViewBottomSheetTest = () => { export default ViewBottomSheetTest; const Test = styled.div` - width: 37.5rem; - height: 660.7rem; + display: flex; + flex-direction: column; + align-items: center; + padding: 0 2.4rem; background-color: white; `; diff --git a/src/pages/book/Book.styled.ts b/src/pages/book/Book.styled.ts new file mode 100644 index 00000000..6e89ba13 --- /dev/null +++ b/src/pages/book/Book.styled.ts @@ -0,0 +1,26 @@ +import styled from "styled-components"; + +export const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 0 2.4rem; +`; + +export const Divider = styled.div` + width: 375px; + height: 8px; + margin-top: 1.6rem; + + background: ${({ theme }) => theme.colors.gray_800}; + opacity: 0.6; + border: 1px s; +`; + +export const FooterContainer = styled.div` + position: sticky; + bottom: 0; + padding: 2.4rem; + + background-color: ${({ theme }) => theme.colors.gray_900}; +`; diff --git a/src/pages/book/Book.tsx b/src/pages/book/Book.tsx new file mode 100644 index 00000000..970e0d2c --- /dev/null +++ b/src/pages/book/Book.tsx @@ -0,0 +1,219 @@ +import OuterLayout from "@components/commons/bottomSheet/OuterLayout"; +import ViewBottomSheet from "@components/commons/bottomSheet/viewBottomSheet/ViewBottomSheet"; +import Button from "@components/commons/button/Button"; +import Context from "@components/commons/contextBox/Context"; +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; + +import * as S from "./Book.styled"; +import BookerInfo from "./components/bookerInfo/BookerInfo"; +import Count from "./components/count/Count"; +import EasyPassEntry from "./components/easyPassEntry/EasyPassEntry"; +import Info from "./components/info/Info"; +import Select from "./components/select/Select"; +import TermCheck from "./components/termCheck/TermCheck"; +import { BOOK_DETAIL_INFO } from "./constants/dummy"; +import { FormData } from "./typings/formData"; + +const Book = () => { + const { performanceId } = useParams<{ performanceId: string }>(); + + // TODO: 회원/비회원 여부 + // navigate 할 때 state로 넘기기 ? + const isNonMember = true; + + const [detail, setDetail] = useState(BOOK_DETAIL_INFO); + const [selectedValue, setSelectedValue] = useState(); + const [round, setRound] = useState(1); + const [bookerInfo, setBookerInfo] = useState({ + bookerName: "", + bookerPhoneNumber: "", + birthDate: "", + }); + const [easyPassword, setEasyPassword] = useState({ + password: "", + passwordCheck: "", + }); + const [isTermChecked, setIsTermChecked] = useState({ + term1: false, + term2: false, + }); + const [isOpen, setIsOpen] = useState(false); + const [activeButton, setActiveButton] = useState(false); + + const handleRadioChange = (value: number) => { + setSelectedValue(value); + }; + + const onChangeBookerInfo = (e: React.ChangeEvent) => { + const { name, value } = e.target; + + setBookerInfo((prev) => ({ ...prev, [name]: value })); + }; + + const onChangeEasyPassword = (e: React.ChangeEvent) => { + const { name, value } = e.target; + + setEasyPassword((prev) => ({ ...prev, [name]: value })); + }; + + const onMinusClick = () => { + setRound((prev) => prev - 1); + }; + + const onPlusClick = () => { + setRound((prev) => prev + 1); + }; + + const onChangeTermCheck = (e: React.ChangeEvent) => { + const { name, checked } = e.target; + + setIsTermChecked((prev) => ({ ...prev, [name]: checked })); + }; + + const handleSheetOpen = () => { + setIsOpen(true); + }; + + const handleSheetClose = () => { + setIsOpen(false); + }; + + const handleClickBook = () => { + // TODO: 티켓 매수 요청 GET API 후, true 인 상태일 때 바텀 시트 열기 + + handleSheetOpen(); + }; + + const handleClickBookRequst = () => { + // TODO: 티켓 매수 요청 get 요청 후, true 인 상태이면, 바텀 시트 열기 + + let formData = { + scheduleId: performanceId, + selectedValue, + purchaseTicketCount: round, + totalPaymentAmount: detail.ticketPrice * round, + } as FormData; + + // TODO: 회원, 비회원 여부에 따라서 예매하기 post 요청 + if (isNonMember) { + // 비회원 예매하기 post 요청 + formData = { ...formData, ...bookerInfo, password: easyPassword.password } as FormData; + + console.log(formData); + } else { + // 회원 예매하기 post 요청 + formData = { + ...formData, + bookerName: bookerInfo.bookerName, + bookerPhoneNumber: bookerInfo.bookerPhoneNumber, + } as FormData; + + console.log(formData); + } + + // TODO: 완료 페이지로 navigate + }; + + useEffect(() => { + if ( + selectedValue && + bookerInfo.bookerName && + bookerInfo.bookerPhoneNumber && + isTermChecked.term2 + ) { + if ( + isNonMember && + isTermChecked.term1 && + easyPassword.password === easyPassword.passwordCheck + ) { + setActiveButton(true); + } else { + setActiveButton(false); + } + } else { + setActiveButton(false); + } + }, [isNonMember, selectedValue, bookerInfo, easyPassword, isTermChecked]); + + return ( + + + +