Skip to content

Commit

Permalink
Feat: 로그인 모달 병합 #13
Browse files Browse the repository at this point in the history
develop 브랜치에서 로그인 모달 내용 병합함
  • Loading branch information
devhaeun committed Jan 27, 2025
2 parents 7f03bda + 0772ab1 commit d378c35
Show file tree
Hide file tree
Showing 18 changed files with 978 additions and 3 deletions.
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
Expand All @@ -8,6 +8,7 @@
</head>
<body>
<div id="root"></div>
<div id="modal-root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@iconify/react": "^5.2.0",
"@mui/icons-material": "^6.4.1",
"@mui/material": "^6.4.1",
"axios": "^1.7.9",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
6 changes: 5 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// import './App.css';
import { ModalContextProvider } from './components/Modal/context/ModalContext';
import Router from './routes/router';
// import SearchBox from './components/SearchBox';

function App() {
return (
<>
<Router />
<ModalContextProvider>
<Router />
</ModalContextProvider>
</>
);
}
Expand Down
Binary file added src/assets/Vector.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/ticketLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { PropsWithChildren } from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components';
import { Icon } from '@iconify/react';
import { useModalContext } from './context/ModalContext';

export default function Modal({
children,
onClose,
}: PropsWithChildren<{ onClose: () => void }>) {
const { clearModals } = useModalContext();

return ReactDOM.createPortal(
<ModalOverlay onClick={clearModals}>
<ModalContent
onClick={(e: React.MouseEvent<HTMLDivElement>) => e.stopPropagation}
>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Icon
icon={'ei:close-o'}
style={{
width: '30px',
height: '30px',
color: '#7D7D7D',
transform: 'translateX(-14px)',
}}
onClick={onClose}
/>
</div>
<div onClick={(e) => e.stopPropagation()}>{children}</div>
</ModalContent>
</ModalOverlay>,
document.getElementById('modal-root') as HTMLElement,
);
}

const ModalOverlay = styled.div`
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
`;

const ModalContent = styled.div`
display: flex;
position: relative;
width: 425px;
height: auto;
flex-direction: column;
background-color: white;
border-radius: 6px;
padding-top: 14px;
padding-bottom: 58px;
`;
60 changes: 60 additions & 0 deletions src/components/Modal/context/ModalContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
createContext,
FunctionComponent,
ReactNode,
useCallback,
useContext,
useState,
} from 'react';

interface IModalState {
modals: FunctionComponent<{ onClose: () => void }>[];
}

type TModalAction = {
openModal: (modal: FunctionComponent<{ onClose: () => void }>) => void;
closeModal: (index: number) => void;
clearModals: () => void;
};

type ModalContextType = IModalState & TModalAction;

const ModalContext = createContext<ModalContextType | undefined>(undefined);

export const ModalContextProvider: FunctionComponent<{
children: ReactNode;
}> = ({ children }) => {
const [modals, setModals] = useState<IModalState['modals']>([]);

const openModal = (modal: FunctionComponent<{ onClose: () => void }>) => {
setModals((prevModals) => [...prevModals, modal]);
};

const closeModal = (index: number) => {
setModals((prevModals) => prevModals.filter((_, i) => i !== index));
};

const clearModals = useCallback(() => {
setModals([]);
}, []);

return (
<ModalContext.Provider
value={{ modals, openModal, closeModal, clearModals }}
>
{children}
</ModalContext.Provider>
);
};

export const useModalContext = () => {
const context = useContext(ModalContext);

if (!context) {
throw new Error(
'useModalContext must be used within a ModalContextProvider',
);
}

return context;
};
21 changes: 21 additions & 0 deletions src/components/Modal/context/ModalProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { useEffect } from 'react';
import { useModalContext } from './ModalContext';
import { useLocation } from 'react-router-dom';

export default function ModalProvider() {
const { modals, closeModal, clearModals } = useModalContext();

const location = useLocation();

useEffect(() => {
clearModals();
}, [location, clearModals]);

return (
<div>
{modals.map((Modal, index) => (
<Modal key={index} onClose={() => closeModal(index)} />
))}
</div>
);
}
169 changes: 169 additions & 0 deletions src/pages/login/components/AgeModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import Checkbox from '@mui/material/Checkbox';
import CircleChecked from '@mui/icons-material/CheckCircleOutline';
import CircleUnchecked from '@mui/icons-material/RadioButtonUnchecked';
import SignupModal from './SignupModal';
import Modal from '../../../components/Modal/Modal';
import { useModalContext } from '../../../components/Modal/context/ModalContext';
import media from '../../../styles/media';

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

const AgeModal: React.FC<ModalProps> = ({ onClose }) => {
const [firstChecked, setFirstChecked] = useState(false);
const [secondChecked, setSecondChecked] = useState(false);
const { openModal } = useModalContext();
const [isLargeScreen, setIsLargeScreen] = useState<boolean>(() =>
typeof window !== 'undefined' ? window.innerWidth >= 745 : false,
);

useEffect(() => {
const handleResize = () => {
setIsLargeScreen(window.innerWidth >= 745);
};

window.addEventListener('resize', handleResize);

return () => {
window.removeEventListener('resize', handleResize);
};
}, []);

const handleChange1 = (event: React.ChangeEvent<HTMLInputElement>) => {
setFirstChecked(event.target.checked);
};

const handleChange2 = (event: React.ChangeEvent<HTMLInputElement>) => {
setSecondChecked(event.target.checked);
};

const handleOpenNextModal = () => {
openModal(({ onClose }) => <SignupModal onClose={onClose} />);
};

const Content = (
<Contents>
{' '}
<Container>
<NewOption>
<Circle />
<Title>최소 연령 확인</Title>
</NewOption>
<Line />
<Option style={{ marginBottom: '26px' }}>
<Checkbox
checked={firstChecked}
onChange={handleChange1}
icon={<CircleUnchecked />}
checkedIcon={<CircleChecked />}
sx={{
'& .MuiSvgIcon-root': { fontSize: 14 },
'&.Mui-checked': {
color: '#C908FF',
},
}}
/>
<Short>만 14세 이상입니다.</Short>
</Option>
<Option>
<Checkbox
checked={secondChecked}
onChange={handleChange2}
icon={<CircleUnchecked />}
checkedIcon={<CircleChecked />}
sx={{
'& .MuiSvgIcon-root': {
fontSize: 14,
},
'&.Mui-checked': {
color: '#C908FF',
},
}}
/>
<Short>만 14세 미만입니다.</Short>
</Option>
<Button onClick={handleOpenNextModal}>계속하기</Button>
</Container>
</Contents>
);
return isLargeScreen ? <Modal onClose={onClose}>{Content}</Modal> : Content;
};
const Contents = styled.div`
${media.notLarge`
background-color: white;
width: 100vw;
height: 100vh;
z-index: 1000;
position: fixed;
top: 0;
left: 0;
`}
`;

const Container = styled.div`
padding-left: 61px;
`;

const Circle = styled.div`
width: 14px;
height: 14px;
background-color: #c908ff;
border: 0;
border-radius: 100%;
`;

const Button = styled.button`
margin-top: 77px;
width: 302px;
height: 39px;
border-radius: 7px;
background-color: #c908ff;
border: 0;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 14px;
font-style: normal;
font-weight: 700;
`;

const Line = styled.div`
width: 302px;
height: 1px;
background: #8f8e94;
margin-top: 35px;
margin-bottom: 34px;
`;

const Short = styled.div`
color: #000000;
font-family: Pretendard;
font-size: 16px;
font-style: normal;
font-weight: 400;
`;

const NewOption = styled.div`
display: flex;
column-gap: 39px;
align-items: center;
margin-top: 127px;
`;

const Option = styled.div`
display: flex;
column-gap: 35px;
align-items: center;
`;

const Title = styled.div`
font-size: 16px;
font-style: normal;
font-weight: 600;
`;

export default AgeModal;
Loading

0 comments on commit d378c35

Please sign in to comment.