-
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.
Merge pull request #19 from UMC-MJU/제이9주차
9주차미션-제이
- Loading branch information
Showing
53 changed files
with
8,656 additions
and
0 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
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> |
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,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 |
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,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> | ||
|
||
) | ||
} |
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,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; |
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,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; |
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,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; |
Oops, something went wrong.