Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

9주차미션-제이 #19

Merged
merged 13 commits into from
Dec 2, 2024
15 changes: 15 additions & 0 deletions 제이-김규리/9주차/mission 1/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="/src/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<div id="portal"></div>

<script type="module" src="/src/main.jsx"></script>
</body>
</html>
167 changes: 167 additions & 0 deletions 제이-김규리/9주차/mission 1/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React, {useEffect} from 'react';
import { useSelector, useDispatch } from 'react-redux'
import styled from 'styled-components'
import Item from './components/Item';
import {CartIcon} from './constants/icons';
import ModalPortal from './components/modal/ModalPortal';
import Modal from './components/modal/Modal';
import { openModal } from './redux/modalSlice';

const Header = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
background-color: blue;
margin-bottom: 60px
`;

const CountBox = styled.div`
position: absolute;
background-color: #118ab2;
width: 20px;
height: 20px;
display: flex;
border-radius: 50%;
justify-content: center;
align-items: center;
right: -15px;
top: -5px;
`;

const PriceBox = styled.div`
border-top: 2px solid lightgrey;
display: flex;
justify-content: space-between;
width: 645px;
margin-top: 30px;
margin-bottom: 20px;
`;

const ClearButton = styled.button`
padding: 10px;
padding-top: 6px;
padding-bottom: 6px;
margin-bottom: 20px;
background-color: white;
color: red;
border-color: red;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s ease; /* 부드러운 애니메이션 효과 */

&:hover {
background-color: rgba(255, 0, 0, 0.2); /* 살짝 빨간색 배경 */
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2); /* 회색 그림자 */
}
`;

const Footer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`;

const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
position: relative;
`;

const Overlay = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* 반투명 검정 배경으로 어두워짐 */
z-index: 5; /* 모달 뒤에 렌더링 */
opacity: ${({ isOpen }) => (isOpen ? 1 : 0)};
pointer-events: ${({ isOpen }) => (isOpen ? 'auto' : 'none')};
transition: opacity 0.3s ease; /* 부드러운 전환 효과 */
`;

function App() {
const data = useSelector(state => state.cart)
const {isOpen} = useSelector((store) => store.modal)
const dispatch = useDispatch();

//console.log(data)
const list = data.items;
const totalCount = data.totalCount;
const totalPrice = data.totalPrice;


const preventScroll = () => {
const currentScrollY = window.scrollY;
document.body.style.position = "fixed";
document.body.style.width = "100%";
document.body.style.top = `-${currentScrollY}px`;
document.body.style.overflowY = 'scroll'; // 스크롤바 보존
//document.body.style.filter = "blur(5px)";
return currentScrollY;
};

const allowScroll = (prevScrollY) => {
document.body.style.position = "";
document.body.style.width = "";
document.body.style.top = "";
//document.body.style.filter = "";
window.scrollTo(0, prevScrollY);
};
// 모달이 열렸을 때 스크롤을 못하게 함
useEffect(() => {
if (isOpen) {
const prevScrollY = preventScroll();
return () => {
allowScroll(prevScrollY);
};
}
}, [isOpen]);
return (
<>
{isOpen && <Overlay isOpen={isOpen} />} {/* 어두운 흐림 배경 */}
<Container isOpen={isOpen}>
<Header>
<p style={{color: 'white', fontWeight: 'bold', fontSize: '20px'}}>UMC PlayList</p>
<div style={{position: 'relative'}}>
<div style={{width: '20px'}}>
<CartIcon />
</div>
<CountBox>
<p style={{color: 'white', margin: '0', fontSize: '15px'}}>{totalCount}</p>
</CountBox>
</div>
</Header>
<h1>당신이 선택한 음반</h1>

<main>
<Item/>
{isOpen &&
<ModalPortal>
<Modal>
<h4>담아두신 모든 음반을 삭제하시겠습니까?</h4>
</Modal>
</ModalPortal>
}
</main>

{totalCount > 0 ? (
<Footer>
<PriceBox>
<p style={{fontWeight: 'bold'}}>총 가격</p>
<p style={{fontWeight: 'bold'}}>₩ {totalPrice}</p>
</PriceBox>
<ClearButton onClick={() => dispatch(openModal())}>장바구니 초기화</ClearButton>
</Footer>
) : (
<h4>고객님이 좋아하는 음반을 담아보세요~!</h4>
)}

</Container>
</>
)
}

export default App
83 changes: 83 additions & 0 deletions 제이-김규리/9주차/mission 1/src/components/Item.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, {useEffect} from 'react'
import { useSelector, useDispatch } from 'react-redux'
import styled from 'styled-components'
import {increase, decrease, removeItem, clearCart, calculateTotals} from '../redux/cartSlice'
import {ChevronUp, ChevronDown} from '../constants/icons'

const Container = styled.div`
display: flex;
margin-bottom: 25px;
align-items: center;
justify-content: center;
`;
const Img = styled.img`
width: 100px;
height: 100px;
margin-right: 15px;
`;

const Price = styled.p`
color: grey;
font-weight: 500;
margin-top: 0;
`;
const Txt = styled.p`
margin-bottom: 5px;
`;

const IconButton = styled.button`
background: none;
border: none;
cursor: pointer;

`;

export default function Item() {
const data = useSelector(state => state.cart)
const dispatch = useDispatch()

const list = data.items;

/* console.log("data: " ,data);
console.log("list: " ,list); */

// list가 변경될 때 마다 총 수량과 총 가격 다시 계산
useEffect(() => {
dispatch(calculateTotals(data));
console.log("data: ", data);
}, [dispatch, list]);

const listView = list.map((item) => (
<Container key={item.id}>
<Img src={item.img} />
<div style={{width: '500px', marginRight: '10px'}}>
<Txt>{item.title} | {item.singer}</Txt>
<Price>₩ {item.price}</Price>
</div>
<div style={{width: '20px', display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
<IconButton onClick={() => dispatch(increase(item.id))}>
{<ChevronUp/>}
</IconButton>
<p style={{marginTop: '5px', marginBottom: '5px', color: 'blue'}}>{item.amount}</p>
<IconButton
onClick={() => {
console.log(item.amount)
item.amount === 1
? dispatch(removeItem(item.id))
: dispatch(decrease(item.id))
}}
>
{<ChevronDown/>}
</IconButton>
</div>
</Container>
))


return (
<div style={{marginTop: '50px'}}>
{listView}
</div>

)
}
32 changes: 32 additions & 0 deletions 제이-김규리/9주차/mission 1/src/components/modal/Modal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import ModalButton from "./ModalButton";
import styled from "styled-components";

const ModalContainer = styled.aside`
display: flex;
justify-content: center;
align-items: center;
position: relative;
bottom: 450px;
z-index: 10;
`;

const ModalBox = styled.div`
width: 400px;
background-color: white;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 10px;
`;
const Modal = ({children}) => {
return (
<ModalContainer className="modal-container" onClick={(e) => {}} >
<ModalBox className="modal">
{children}
<ModalButton/>
</ModalBox>
</ModalContainer>
)
}

export default Modal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useDispatch } from "react-redux";
import { clearCart } from "../../redux/cartSlice";
import { closeModal } from "../../redux/modalSlice";
import styled from "styled-components";

const Button = styled.button`
background-color: white;
padding: 15px;
padding-top: 3px;
padding-bottom: 3px;
border-radius: 5px;
cursor: pointer;
color: ${(props) => props.color || "blue"};
border-color: ${(props) => props.color || "blue"};
transition: all 0.2s ease; /* 부드러운 애니메이션 효과 */

&:hover {
background-color: ${(props) => props.hoverColor || "rgba(0, 0, 255, 0.2)"};
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2); /* 회색 그림자 */
}
`;

const ButtonBox = styled.div`
display: flex;
justify-content: space-between;
padding: 80px;
padding-top: 0;
padding-bottom: 30px;
width: 240px;
`;
const ModalButton = () => {
const dispatch = useDispatch();
return(
<ButtonBox className="btn-container">
<Button
type="button"
className="btn confirm-btn"
onClick={() => {
dispatch(clearCart());
dispatch(closeModal());
}}
>
</Button>
<Button
type="button"
className="btn clear-btn"
onClick={() => {
dispatch(closeModal());
}}
color="red"
hoverColor="rgba(255, 0, 0, 0.2)"
>
아니요
</Button>
</ButtonBox>
);
}

export default ModalButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import reactDom from 'react-dom';

const ModalPortal = ({children}) => {
if(typeof window === "undefined"){
return null;
}

const node = document.getElementById("portal");

return reactDom.createPortal(children, node);
};

export default ModalPortal;
Loading