Skip to content

Commit

Permalink
Merge pull request #19 from UMC-MJU/제이9주차
Browse files Browse the repository at this point in the history
9주차미션-제이
  • Loading branch information
OrangeKim04 authored Dec 2, 2024
2 parents 449894e + d69809b commit ff10f6a
Show file tree
Hide file tree
Showing 53 changed files with 8,656 additions and 0 deletions.
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

0 comments on commit ff10f6a

Please sign in to comment.