- 오늘한 일.
hotel 상세페이지 이동.
react redux에 세션을 통해서 배움.
이력서 세션이 있었음.
오늘의 문제점...
화면 마우스 이벤트로 hover시에 숨겨져야 하는 빨간색 동그라미친 부분이
hide, exhibit 클래스가 제대로 안 먹는 문제가 발생.
컴포넌트 클래스로 죄다 통일하여 정리해야겠다.
수정 예정:
충돌 문제를 해결하기 위해서 스타일 컴포넌트로 리팩토링하여 싹 다 통일 예정.
예상외로 내 속도가 너무 느려서 난항 중.
버튼을 누르면 이동하는 슬릭을 넣을 예정.
아래 코드 블럭은 사진을 표현하는 컴포넌트.
import React, { useState } from "react";
import styled from "styled-components";
import { withRouter } from "react-router-dom";
import { FiCamera } from "react-icons/fi";
import { FaBed, FaRegComments } from "react-icons/fa";
import { GiMountaintop, GiKnifeFork } from "react-icons/gi";
import { MdKeyboardArrowRight, MdKeyboardArrowLeft } from "react-icons/md";
import { BsArrowsAngleExpand } from "react-icons/bs";
function Hotelpicture({idx, hotel}) {
const [ onMouse, setMouse ] = useState(false);
const pictureBundle =() => {
idx.history.push("")
}
const MouseEvent = () => {
setMouse ({
onMouse: true
})
};
const MouseOut = () =>{
setMouse ({
onMouse: false
})
};
return(
<>
{/* 호텔 내용 */}
<HotelPictures>
<HotelPicturePadding>
<LeftPicture>
<BigPicture>
<BigPictureInterior onMouseOver={MouseEvent} onMouseOut={MouseOut} >
<BigPictureImglistWrap>
{/* {hotel.image.map(el => { */}
<BigPictureImglist >
<img src={hotel.image[0]} alt=""></img>
</BigPictureImglist>
{/* })} */}
</BigPictureImglistWrap>
<DisplayButton onMouse={onMouse} onClick={pictureBundle}>
<BsArrowsAngleExpand />
전체보기
</DisplayButton>
<MdKeyboardArrowLeft className="leftBtn"/>
<MdKeyboardArrowRight className="rightBtn" />
<Picturetitle>
<FiCamera className='picture' />
<span className='pictureAll'>모든 사진 보기
<span className='picturecount'>(20)</span>
</span>
</Picturetitle>
</BigPictureInterior>
</BigPicture>
</LeftPicture>
<div className="pictureColumn">
<HighBox bgImg={hotel.image[1]}>
<GiMountaintop className="position"/>
<span className="positionName">주변 환경사진</span>
</HighBox>
<UnderBox bgImg={hotel.image[4]}>
<FaBed className="position"/>
<span className="positionName">객실 및 스위트</span>
</UnderBox>
</div>
<div className="rightColumn">
<RightHighBox bgImg={hotel.image[5]}>
<FaRegComments className="position"/>
<span className="positionName">라운지</span>
</RightHighBox>
{/* 언더박스는 사진모음 */}
<RightUnderBox bgImg={hotel.image[6]}>
<GiKnifeFork className="position"/>
<span className="positionName">식사</span>
</RightUnderBox>
</div>
</HotelPicturePadding>
</HotelPictures>
</>
)
}
// 사진전체 사이즈
const HotelPictures = styled.div`
width: 1280px;
height: 501px;
`;
//사진 전체 패딩
const HotelPicturePadding = styled.div`
width: 100%;
padding: 12px;
display: flex;
flex-direction: wrap;
.pictureColumn{
display: flex;
flex-direction: column;
justify-content: space-between;
cursor:pointer;
}
.rightColumn{
display: flex;
flex-direction: column;
justify-content: space-between;
margin-left: 5px;
cursor:pointer;
}
`;
//우측 작은 사진들
const HighBox = styled.div`
width : 330px;
height: 235px;
background-color: #e0e0e0;
font-size: 16px;
opacity: 0.6;
font-weight: 700;
text-shadow: -1px 0 #000,
0 1px #000,
1px 0 #000,
0 -1px #000;
background-image: url(${props => props.bgImg });
color: white;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-size: cover;
&:hover{
opacity: 0.7;
}
.position {
width: 36px;
height: 36px;
}
.positionName{
height: 32px;
}
`;
const UnderBox =styled.div`
width : 330px;
height: 235px;
background-color: #e0e0e0;
font-size: 16px;
opacity: 0.6;
font-weight: 700;
text-shadow: -1px 0 #000,
0 1px #000,
1px 0 #000,
0 -1px #000;
background-image: url(${props => props.bgImg });
color: white;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-size: cover;
&:hover{
opacity: 0.7;
}
.position {
width: 36px;
height: 36px;
}
.positionName{
height: 32px;
}
`;
const RightHighBox = styled.div`
width : 330px;
height: 235px;
background-color: #e0e0e0;
font-size: 16px;
opacity: 0.6;
font-weight: 700;
text-shadow: -1px 0 #000,
0 1px #000,
1px 0 #000,
0 -1px #000;
background-image: url(${props => props.bgImg });
color: white;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-size: cover;
&:hover{
opacity: 0.7;
}
.position {
width: 36px;
height: 36px;
}
.positionName{
height: 32px;
}
`;
const RightUnderBox = styled.div`
width : 330px;
height: 235px;
background-color: #e0e0e0;
font-size: 16px;
opacity: 0.6;
font-weight: 700;
text-shadow: -1px 0 #000,
0 1px #000,
1px 0 #000,
0 -1px #000;
background-image: url(${props => props.bgImg });
color: white;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-size: cover;
&:hover{
opacity: 0.7;
}
.position {
width: 36px;
height: 36px;
}
.positionName{
height: 32px;
}
`;
//사진 왼쪽 wrap
const LeftPicture = styled.div`
margin-right: 5px;
width: 582px;
height: 475px;
`;
//사진 왼쪽 큰 사진
const BigPicture = styled.div`
width: 582px;
height: 380px;
`;
// 사진위에 있는 전체보기, 옆으로 이동버튼
const BigPictureInterior = styled.div`
position: absolute;
width: 582px;
height: 474px;
.hiddenFoldingPage{
width: 148px;
height: 60px;
font-size: 20px;
top: 237px;
left: 220px;
padding: 0 18px;
position: absolute;
color : white;
display:${({onMouse})=>onMouse===true?"flex":"none"};
align-items: center;
justify-content: center;
background-color: rgba(74,74,74, .6);
&:hover{
background-color: rgba(0,0,0,.5);
}
}
.hide {
width: 148px;
height: 60px;
font-size: 20px;
top: 237px;
left: 220px;
padding: 0 18px;
position: absolute;
color : white;
align-items: center;
justify-content: center;
background-color: rgba(74,74,74, .6);
display : none
}
.exhibit{
display : block
}
.leftBtn {
position: absolute;
color: white;
background-color: rgb(74,74,74, .6);
width: 80px;
height: 100px;
left: 0;
top: 210px;
border-radius: 5px;
&:hover{
background-color: rgba(0,0,0,.5);
}
}
.rightBtn {
position: absolute;
color: white;
background-color: rgba(74,74,74, .6);
width: 80px;
height: 100px;
right: 0;
top: 210px;
border-radius: 5px;
&:hover{
background-color: rgba(0,0,0,.5);
}
}
`;
const ImgClickBtn = styled.div`
`;
const DisplayButton = styled.div`
width: 148px;
height: 60px;
font-size: 20px;
top: 237px;
left: 220px;
padding: 0 18px;
position: absolute;
color : white;
display:${({onMouse})=>(onMouse?"flex":"none")};
align-items: center;
justify-content: center;
background-color: rgba(74,74,74, .6);
&:hover{
background-color: rgba(0,0,0,.5);
}
`;
// 사진 ul
const BigPictureImglistWrap =styled.ul`
background-color: black;
position: relative;
list-style: none;
width: auto;
`;
// 사진 li
const BigPictureImglist = styled.li`
opacity: 1;
position: absolute;
&:hover{
opacity: 0.8;
}
img{
width: 582px;
height: 474px;
}
`;
//사진 위에 있는 타이틀
const Picturetitle = styled.div`
font-size: 16px;
bottom: 0;
width: 100%;
z-index: 5;
padding: 16px 16px 8px 16px;
font-weight: 400;
font-family: 'Arial, Tahoma, "Bitstream Vera Sans", sans-serif';
display: flex;
align-items: center;
text-align: left;
color: white;
position: absolute;
.picture {
width: 32px;
height: 36px;
margin-right: 10px;
}
.pictureAll{
width: auto;
height: auto;
font-weight: 700;
top: -2px;
&:hover {
text-decoration: underline;
}
}
.picturecount{
width: auto;
height: auto;
font-size: 16px;
top: -2px;
}
`;
export default withRouter(Hotelpicture);
또 하나의 수정사항이 있는 부분.
hotel.content.slice('.') 한 부분이 있다.
해당 부분이 내 예상과 다르게 제대로 잘리지 않아서 그냥 내용 그대로 리턴된 상황.
소개글이 잘라서 나와야 더보기, 덜보기 하는 부분을 구현하는데, 그냥 생(生) 리턴되서 매우 이상한 모습으로 출력중.
뛰어쓰기에서 자르자니 물음표 부분에서 어디에서 잘라야 제대로 출력될지 이상해서 고민이 많다.
원문 그대로 .(마침표)에서 자르려고 생각 중.
더 보기, 덜 보기는 내가 처음 쓰는 기능이라서 어떻게 해야할지 고민하고 있다.
중간 부분에는 호텔 시설들을 리턴해야하는데,
조건이 매우 많을 것 같다.
해당 이미지를 일일히 구하고 해당 내용이 있을 경우 그 경우에만 그것을 리턴하는 조건을 짜야해서 과연....
내일부터 밤을 새야하지 않을까..?
백엔드부분에서 이부분을 잘라주고 map함수를 돌리면 되었을 것 같은데, 그러지 않아서 내가 고생(?)하는 것 같음.
아무튼 slice를 통해 배열로 만들어 map함수로 리턴하는 방식으로 해결해야할지 고민이 많다.
제일 우측은 하드코딩의 광고창으로 진행할 예정.
import React, { useState } from "react";
import styled from "styled-components";
import { Link, withRouter } from "react-router-dom";
function HotelIntroduce({idx,hotel}) {
const [ savepoint , setSavepoint ] = useState(hotel)
const [ moretext, setMortext ] = useState(false)
const hiddenText = () => {
setMortext ({
limit: 3,
moretext: false
});
}
const showText =() => {
setMortext ({
limit: hotel.content.length,
moretext: true
});
}
const { kind } = hotel.hotel_rating
const ratingReturn = (kind) => {
if ( kind > 4.5 ) {
return "아주좋음"
}
if ( kind > 3.5 ) {
return "좋음"
}
if ( kind > 2.5) {
return "보통"
}
if (kind >1.5) {
return "별로"
}
if (kind > 0.5) {
return "최악"
}
};
const circleReturn =(kind) => {
if ( kind > 4.5 ) {
return "●●●●●"
}
if ( kind > 3.5 ) {
return "●●●●○"
}
if ( kind > 2.5) {
return "●●●○○"
}
if (kind >1.5) {
return "●●○○○"
}
if (kind > 0.5) {
return "●○○○○"
}
}
// const slicer =
return(
<>
<HotelIntroduceDIV>
<div>
<IntroduceTitle>
<h2 className="Introduce">소개</h2>
</IntroduceTitle>
<HotelContent>
<HotelContentLeft>
<ReviewPoint>
<ReviewNumber>{Number(hotel.hotel_rating).toFixed(1)}</ReviewNumber>
<Link to="null" className="Reviews">
<ReviewGood>{ratingReturn(hotel.hotel_rating)}</ReviewGood>
<div className="flexList">
<ReviewNumberConverter>{circleReturn(hotel.hotel_rating)}</ReviewNumberConverter>
<ReviweAccount>1445건의 리뷰</ReviweAccount>
</div>
</Link>
</ReviewPoint>
<PointContent>
<div className="rate">{circleReturn(hotel.place)}</div>
<div className="title">장소</div>
</PointContent>
<PointContent>
<div className="rate">{circleReturn(hotel.cleanliness)}</div>
<div className="title">청결도</div>
</PointContent>
<PointContent>
<div className="rate">{circleReturn(hotel.service)}</div>
<div className="title">서비스</div>
</PointContent>
<PointPrice>
<div className="rate">{circleReturn(hotel.price)}</div>
<div className="title">가격</div>
</PointPrice>
<FoldingContent>
<ul className="foldingMargin" >
{/* {hotel.content.map() => {}} */}
<li>{hotel.content.slice(`.`)}</li>
</ul>
<div className="foldBtn">
<div className={ moretext === true? "foldSpan hidden" : "foldSpan nonehidden"} onClick={showText}>더보기</div>
<div className={ moretext === false? "foldSpan hidden" : "foldSpan nonehidden"} onClick={hiddenText}>덜 보기</div>
</div>
</FoldingContent>
<MiniPicture>
<div className="smallBigimg"></div>
<div className="verySmallimg"></div>
</MiniPicture>
</HotelContentLeft>
<HotelContentRight></HotelContentRight>
</HotelContent>
</div>
</HotelIntroduceDIV>
</>
)
}
export default withRouter(HotelIntroduce);
const HotelIntroduceDIV = styled.div`
max-width: 1280px;
width: 100%;
height: 1055px;
margin: 24px 0 12px 0;
padding: 24px;
background-color: white;
`;
const IntroduceTitle = styled.div`
width: 100%;
height: 49px;
padding-bottom: 18px;
border-bottom: 1px solid gray;
.Introduce{
width: 100%;
height: 30px;
font-size: 28px;
font-weight: 700;
line-height: 30px;
color: black;
font-family: 'Arial, Tahoma, Bitstream Vera Sans, sans-serif';
}
`;
// 호텔 소개 내용 전체
const HotelContent = styled.div`
width: 100%;
height: 987px;
`;
//소개 왼쪽
const HotelContentLeft = styled.div`
width: 50%;
height: 986px;
padding: 12px;
`;
const ReviewPoint = styled.div`
width: 100%;
margin: 14px 0;
display: flex;
.Reviews{
cursor: pointer;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-items: center;
.flexList{
display: flex;
align-items: center;
}
}
`;
const ReviewNumber = styled.span`
color: black;
font-family: 'Arial, Tahoma, Bitstream Vera Sans, sans-serif';
font-size: 48px;
font-weight: 700;
margin-right: 8px;
width: 90px;
`;
const ReviewGood = styled.div`
color: black;
font-family: 'Arial, Tahoma, Bitstream Vera Sans, sans-serif';
font-size: 16px;
font-weight: 700;
width: 100%;
height: 32px;
line-height: 22px;
display: flex;
align-items: flex-end;
`;
const ReviewNumberConverter = styled.div`
width: 90px;
height: 30px;
font-size: 30px;
color: green;
`;
const ReviweAccount = styled.div`
width: 95px;
height: 30px;
color: black;
font-size: 14px;
font-family: 'Arial, Tahoma, Bitstream Vera Sans, sans-serif';
margin-left: 5px;
display: flex;
align-items: flex-end;
`;
const PointContent = styled.div`
width: 100%;
height: 30px;
margin-bottom: 3px;
display: flex;
.rate {
width: 80px;
height: 30px;
font-size: 30px;
color: green;
}
.title{
color: #2c2c2c;
font-size: 14px;
margin-left: 20px;
font-family: 'Arial, Tahoma, Bitstream Vera Sans, sans-serif';
display: flex;
align-items: flex-end;
font-weight: bold;
}
`;
const PointPrice = styled.div`
width: 100%;
height: 30px;
margin-bottom: 24px;
display: flex;
.rate {
width: 80px;
height: 20px;
font-size: 30px;
color: green;
}
.title {
color: #2c2c2c;
font-size: 14px;
margin-left: 20px;
font-weight: 400;
font-family: 'Arial, Tahoma, Bitstream Vera Sans, sans-serif';
display: flex;
align-items: flex-end;
font-weight: bold;
}
`;
const FoldingContent = styled.div`
width: 100%;
height: 784px;
.foldingMargin{
font-size: 20px;
}
.unfoldBtn{
.foldSpan{
.hidden{
display: none;
}
.nonehidden{
display: block;
}
}}
`;
const MiniPicture = styled.div`
width: 290px;
height: 325px;
border: 1px solid red;
.smallBigimg{
width: 290px;
height: 272px;
}
.verySmallimg{
width: 47px;
height: 50px;
}
`;
//소개 오른쪽
const HotelContentRight = styled.div``;
완성된 제목부분의 코드블럭
아직 리팩토링을 하지 않아 깔끔하지는 않다.
어떻게 더 깔끔하고 최대한 코드를 쓰지 않고 구현할 것인지를 고민해야하지 않는가? 생각중인데
잘 모르겠다.
코딩이 어려워 모르다보니 코드가 길어지고, 점점 더 힘들게 코딩하는 것 같다.
일단 import 부분에서 동일하게 부르는 것부터 합치고 싹다 정리할 예정이긴한데,
머지하느라 또 허겁지겁하면 못 할수도...
import React, { useState } from "react";
import styled from "styled-components";
import { Link, withRouter } from "react-router-dom";
import { IoMdPin } from "react-icons/io";
import { IoMdHeartEmpty } from "react-icons/io";
import { FiShare } from "react-icons/fi";
import { BsFillHeartFill } from "react-icons/bs";
function Hotelpageheader({idx, hotel}) {
const [ savePoint , setSavepoint ] = useState(hotel)
const [ rating , setRating ] = useState(hotel.hotel_rating)
const [ onMouse, setOnMouse ] = useState( false )
const { kind } = hotel.hotel_rating
const circleReturn =(kind) => {
if ( kind > 4.5 ) {
return "●●●●●"
}
if ( kind > 3.5 ) {
return "●●●●○"
}
if ( kind > 2.5) {
return "●●●○○"
}
if (kind >1.5) {
return "●●○○○"
}
if (kind > 0.5) {
return "●○○○○"
}
}
return(
<>
<HotelpageheaderDIV>
{/* 비용절약 광고창탭 */}
<HeaderBanner>
<SavePricetitle>
<Reducead>
<ReduceADimgWrap>
<div className="wrap">
<ReduceADimg src="https://static.tacdn.com/img2/brand_refresh/Tripadvisor_lockup_horizontal.svg" />
</div>
<ReduceADdescription>
<span><b>비용을 절약하세요.</b>트립어드바이저는 호텔 최저가를 찾기 위해 200개 이상의 사이트를 비교합니다.</span>
</ReduceADdescription>
</ReduceADimgWrap>
</Reducead>
</SavePricetitle>
</HeaderBanner>
{/* 여기부터 호텔 이름 */}
<HotelpageName>
<HotelpageSubtitleHigh>
<h1 className="hotelpageSubBigtitle">{hotel.name}
<div className="hotelpageSubEnglishtitle">
{hotel.english_name}
</div>
</h1>
</HotelpageSubtitleHigh>
<ReviwPoint>
<Link to="null">
<span className="rate">
{circleReturn(hotel.hotel_rating)}
</span>
<span className="marginleft">
{hotel.review_count_msg}
</span>
</Link>
</ReviwPoint>
</HotelpageName>
<HotelpageSubtileUnder>
<HotelPageSpace>
<HotelPageSubtitleUnderLeft>
<div>
<Locationbarwrap>
<IoMdPin className="locationImg" />
<span className="locationName">
{hotel.address}
</span>
</Locationbarwrap>
</div>
</HotelPageSubtitleUnderLeft>
<HotelPageSubtitleUnderRight>
<HeartSave onMouseEnter={() => setOnMouse(true)} onMouseLeave={() => setOnMouse(false)}>
{onMouse
? <BsFillHeartFill className="redheart" />
: <IoMdHeartEmpty className="heart" />}
<div className="save">저장</div>
</HeartSave>
<div className="shareBoxMargin">
<div className="sharebox">
<FiShare className="shareImg" />
<span className="share">
공유
</span>
</div>
</div>
</HotelPageSubtitleUnderRight>
</HotelPageSpace>
</HotelpageSubtileUnder>
</HotelpageheaderDIV>
</>
)
}
// 호텔 헤더
const HotelpageheaderDIV = styled.div`
max-width:1280px;
width: 100%;
height: 284px;
padding: 0 24px;
margin: 0 auto;
`;
const HeaderBanner = styled.div`
width: 100%;
height: 100px;
display: flex;
justify-content: center;
`;
const SavePricetitle = styled.div`
width: 100%;
margin: 0;
padding: 5px 0;
`;
const Reducead = styled.div`
width: 970px;
height: 66px;
padding: 16px 0;
margin: 12px auto 0;
align-items: center;
background-color: #faf1ed;
`;
const ReduceADimgWrap = styled.div`
display: flex;
justify-content: center;
align-items:center;
height: 100%;
.wrap {
width: 232px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
}
`;
const ReduceADimg = styled.img`
width: 164px;
padding: 0 12px 0 0;
top: 3px;
`;
const ReduceADdescription = styled.div`
width: 76%;
height: 49px;
font-size: 18px;
padding: 0 15px;
border-left: 1px solid white;
text-align: left;
`;
// 호텔헤더 이름
const HotelpageName = styled.div`
width: 100%;
height: 107px;
padding: 12px 12px 0;
flex:none;
`;
const HotelpageSubtitleHigh = styled.div`
width: 100%;
height: 71px;
.hotelpageSubBigtitle {
width: 800px;
height: 71px;
font-size: 32px;
line-height: 36px;
font-weight: 700;
color: black;
font-family: 'Arial,Tahoma,Bitstream Vera Sans,sans-serif';
margin: 0;
}
.hotelpageSubEnglishtitle {
width: 100%;
height: 35px;
font-size: 32px;
line-height: 36px;
font-weight: 700;
color: black;
font-family: 'Arial,Tahoma,Bitstream Vera Sans,sans-serif';
}
`;
const ReviwPoint = styled.div`
width: 100%;
height: 30px;
margin: 4px 0 0;
display: flex;
.rate{
width: 80px;
height: 20px;
font-size: 30px;
color: green;
}
.marginleft{
width: 95px;
height: 30px;
font-size: 14px;
color: #474747;
font-family: "Arial,Tahoma,Bitstream Vera Sans,sans-serif";
margin : 0 0 0 5px;
}
`;
const HotelpageSubtileUnder = styled.div`
width: 100%;
height: 38px;
padding: 0 12px 12px;
`;
// 저장,공유, 호텔방문 flex먹인 곳.
const HotelPageSpace = styled.div`
width: 100%;
height: 32px;
display: flex;
justify-content: space-between;
align-items: center;
`;
const HotelPageSubtitleUnderRight = styled.div`
width: 128px;
height: 22px;
color: black;
display: flex;
flex-wrap: wrap;
/* 공유박스 */
.shareBoxMargin{
width: 64px;
height: 22px;
margin-left: 8px;
padding-left: 8px;
border-left: 1px solid #e0e0e0;
.sharebox {
width: 56px;
height: 22px;
display: flex;
align-items: center;
.shareImg{
width: 20px;
height: 20px;
}
.share{
width: 36px;
height: 20px;
padding-left: 4px;
font-size: 16px;
font-family:Arial, Tahoma, "Bitstream Vera Sans", sans-serif;
}
}
}
`;
// 하트,저장 부분
const HeartSave = styled.div`
width: 55px;
height: 20px;
cursor: pointer;
display: flex;
justify-items: space-between;
.heart {
width: 20px;
height: 20px;
}
.redheart {
width: 20px;
height: 20px;
color: red;
}
.save{
width: 34px;
font-size: 16px;
font-family:Arial, Tahoma, "Bitstream Vera Sans", sans-serif;
height: 20px;
margin-left: 4px;
&:hover {
text-decoration:underline;
}
}
`;
const HotelPageSubtitleUnderLeft = styled.div`
display: flex;
`;
const Locationbarwrap = styled.div`
width: auto;
height: 25px;
display: flex;
align-items: center;
.locationImg {
width: 18px;
height: 18px;
}
.locationName{
width: auto;
height: 17px;
font-size: 14px;
font-weight: 400;
color: #474747;
border-bottom: 1px dotted #e0e0e0;
text-decoration: none;
&:hover{
text-decoration: underline;
}
}
`;
export default withRouter(Hotelpageheader);
호텔페이지를 구성하는 최상단 컴포넌트
import React, { useEffect, useState } from "react";
import Hotelpageheader from "./Hotelpageheader";
import Hotelpicture from "./Hotelpicture";
import HotelIntroduce from "./HotelIntroduce";
import { Apiresource } from "../../Config";
import Nav from "../Nav.js";
import styled from "styled-components";
function Hotelpage() {
const [ hotels, setHotel ] = useState([]);
useEffect((props) => {
fetch(`${Apiresource}/data/hotel1pMoc.json`)
.then(res => res.json())
.then(res => {
setHotel(res.data )
})
}, []);
return(
<div>
{hotels.map((hotel, index) => (
<section key={index}>
<Nav />
<Hotelpageheader idx={index} hotel={hotel}/>
<HotelBox>
<Hotelpicture idx={index} hotel={hotel} />
<HotelIntroduce idx={index} hotel={hotel} />
</HotelBox>
</section>
))};
</div>
)
}
export default Hotelpage;
const HotelBox = styled.div`
width: 100%;
height: 100%;
margin-bottom: 12px;
padding: 0 24px;
display: flex;
align-items: center;
flex-direction: column;
background-color:#f2f2f2;
`;
그렇게 정리할 내용이 보이진 않는 것 같다.
원래라면 해당 컴포넌트에는 스타일 컴포넌트를 구현할 필요는 없겠지만, 배경색이 회색인지라 별 수 없이 집어넣었다.
좀 아쉬운 부분.
지옥에서 돌아온 로그인 컴포넌트들.
무엇이 문제인가? modal을 먹이는데,
이게 내가 편하게 쓰던 props.history.push가 아니라서 해당 창에서 그냥 떠 올라나오는 것이라
컴포넌트를 호출하는 것을 오늘 알았다.
해당 컴포넌트를 import하고 모달이 뜨게 했지만, 로그인 종류가 3가지나 되어 이것이 교체되면서 뜨는 것이 문제.
로그인 로직은
버튼 => 소셜로그인 => 이메일 로그인 => 회원가입 => 1)소셜로그인 2) 이메일 로그인으로 갈 수 있다보니 매우 헷깔린다.
import React, { useState } from "react";
import styled from "styled-components";
import KakaoLogin from "react-kakao-login";
import { GoogleLogin } from "react-google-login";
import { withRouter } from "react-router-dom";
import { Apiresource } from "../../Config";
import { ClientId } from "../../Config";
import { Jskey } from "../../Config";
import EmailLogin from "./EmailLogin";
function SocialLogin(props, visible) {
const [ id, setId ] = useState('');
const [ name, setName ] = useState('');
const [ provider, setProvider ] = useState('');
const [ isEmailModal, setIsEmailModal ] = useState(false);
// 구글 로그인
const clickGoogleBtn = (res) => {
fetch(`${Apiresource}/sign-in/google`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
email: res.profileObj.email,
name: res.profileObj.name,
})
})
.then(res => res.json())
.then(res => {
if (res) {
localStorage.setItem('kakao-token', res.token)
alert('로그인을 환영합니다')
props.history.push('/main')
} else {
alert('아이디와 비밀번호를 확인해주세요.')
}
})
}
// 카카오 로그인
const clickKakaoBtn = (res) => {
fetch(`${Apiresource}/account/sign-in/kakao`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
access_token : res.response.access_token
})
})
.then(res => res.json())
.then(res => {
if (res.access_token) {
localStorage.setItem('kakao-token', res.access_token)
alert('로그인을 환영합니다')
props.history.push('/main')
} else {
alert('아이디와 비밀번호를 확인해주세요.')
}
})
}
const responseFail = (err) => {
console.error(err)
};
// 모달창 끄기
const openEmailLogin = () => {
setIsEmailModal(true)
}
const closeEmailLogin =() => {
setIsEmailModal(false)
}
return(
<>
<ModalOverlay visible={visible} />
<ModalWrapper tabIndex="-1" visible={visible}>
<ModalIner tabIndex="0">
<div>
<LoginImageContainer>
<LoginImgtag src="https://static.tacdn.com/img2/brand_refresh/Tripadvisor_lockup_horizontal_registered.svg" />
</LoginImageContainer>
<LogInnput>
<LoginInputMargin>
<div className="centerMargin">
<GoogleBtn
clientId={`${ClientId}`}
onSuccess={clickGoogleBtn}
onFailure={responseFail}
>
<Socialspan>Google로 계속하기</Socialspan>
</GoogleBtn>
<KakaoBtn
jsKey={`${Jskey}`}
onSuccess={clickKakaoBtn}
onFailure={responseFail}
getProfile="true"
>
<SocialImg src="https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcTI2AI6Bz3gkrH3VCq4azeIg-CXoxxvsar2Og&usqp=CAU" />
<Socialspan>카카오톡 계정으로 계속하기</Socialspan>
</KakaoBtn>
<OR>
<BorderLine>
<Topspan>또는</Topspan>
</BorderLine>
</OR>
<SocialBtn onClick={()=>props.setMode("email")}>
<SocialImg src="https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcQvsq86NX1qN68M4GUeh5k8h6KM4pADF86mEw&usqp=CAU" />
<Socialspan>
이메일로 계속하기
</Socialspan>
</SocialBtn>
</div>
<ContiNue>계속 진행할 경우, 트립어드바이저의
<span className="textDeco">개인정보 취급방침</span>
및
<span className="textDeco">쿠키 정책</span>
에 동의한 것으로 간주됩니다.
</ContiNue>
</LoginInputMargin>
</LogInnput>
</div>
<button onClick={props.closeModal} className="returnmain" tabIndex="0">
X
</button>
</ModalIner>
</ModalWrapper>
</>
);
}
const ModalOverlay = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 999;
`;
const ModalWrapper = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
overflow: auto;
outline: 0;
display: flex;
justify-content: center;
align-items: center;
`;
const ModalIner = styled.div`
width: 420px;
height: 666px;
padding: 40px 45px 30px;
box-sizing: border-box;
position: relative;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5);
background-color: white;
.returnmain {
position: absolute;
z-index: 1;
top: 0;
right: 0;
text-align: center;
width: 48px;
height: 48px;
font-size: 25px;
cursor: pointer;
}
`;
const LoginImageContainer = styled.div`
width:328px;
`;
const LoginImgtag = styled.img`
width: 210px;
height: 32px;
margin-right: 20px;
`;
const LogInnput = styled.div`
width: 329px;
height: 467px;
`;
const LoginInputMargin = styled.div`
width:328px;
height:48px;
.centerMargin{
margin: 50px 0;
position: relative;
}
`;
const GoogleBtn = styled(GoogleLogin)`
width: 328px !important;
height: 48px !important;
margin-bottom: 16px !important;
border-radius: 12px !important;
box-shadow: none !important;
border : 2px solid rgba(0,0,0,.2) !important;
color: black !important;
background-color: 'white' !important;
display:flex !important;
align-items: center !important;
div{
width: 20px;
height: 20px;
margin: 0 0 0 24px !important;
padding: 0 !important;
}
span {
padding : 0 !important;
}
`;
const KakaoBtn = styled(KakaoLogin)`
width: 328px;
height: 48px;
margin-bottom: 16px;
border-radius: 12px;
box-shadow: none;
border : 2px solid rgba(0,0,0,.2);
color: black;
background-color: 'white';
display:flex;
align-items: center;
`;
const SocialBtn = styled.div`
width: 328px;
height: 48px;
margin-bottom: 16px;
border-radius: 12px;
box-shadow: none;
border : 2px solid rgba(0,0,0,.2);
color: black;
background-color: 'white';
display:flex;
align-items: center;
`;
const SocialImg = styled.img`
width: 20px;
height: 20px;
margin-left: 24px;
`;
const Socialspan = styled.span`
color : black;
width: auto;
font-size: 14px;
font-family: '굴림, gulim, sans-serif';
margin: 0 12px 0 50px;
`;
const OR = styled.div`
padding : 8px 0;
text-align: center;
`;
const BorderLine = styled.div`
border-top : 1px solid #e0e0e0;
content: none;
display: block;
`;
const Topspan = styled.span`
position: relative;
top: -8px;
font-weight:600;
font-size: 10px;
line-height: 14px;
color: #8c8c8c;
padding: 0 6px;
background-color: white;
`;
const ContiNue = styled.div`
width: 328px;
height: 32px;
font-size: 12px;
font-family: '굴림, gulim, sans-serif';
.textDeco {
text-decoration: underline;
color: black;
text-decoration-style: solid;
font-weight: 600;
line-height: 16px;
}
`;
export default withRouter(SocialLogin);
지금 email로그인에서 회원가입으로 넘어가나 이것을 어떻게 받고 어디서 무엇을 넘겨줘야하는지 갑자기 까먹었다.
물론 여기서도 소셜로그인으로 넘겨줘야한다.
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { withRouter } from "react-router-dom";
import { Apiresource } from "../../Config.js";
import Signup from "./Signup";
function EmailLogin(props, visible) {
const [ userInfo, setName ] = useState({email:"", password:""});
const [ isSignup, setIsSignup ] = useState(false);
const inputValuedetector = (e) => {
const { name, value} = e.target
setName({...userInfo, [ name ] : value })
};
const submit = () => {
const { email, password } = userInfo
fetch(`${Apiresource}/account/sign-in`, {
method: 'POST',
body: JSON.stringify({ email, password })
})
.then(res => res.json())
.then(res => {
if (res.token) {
localStorage.setItem("tripadvisor-token", res.token)
alert("로그인을 환영합니다")
props.history.push('/main')
} else {
alert('이메일과 비밀번호를 확인해주십시오')
};
})
}
const returnSocialLogin = (e) => {
props.history.push('/SocialLogin')
}
const openSignup =() => {
setIsSignup(true)
}
const closeSignup =() => {
setIsSignup(false)
}
const isValidEmail = !userInfo.email.length || (userInfo.email.length && userInfo.email.includes("@"));
return(
<>
<ModalOverlay visible={visible} />
<ModalWrapper tabIndex="-1" visible={visible}>
<ModalIner tabIndex="0">
<div className="modalOutline">
<LoginImageContainer>
<LoginImgtag src="https://static.tacdn.com/img2/brand_refresh/Tripadvisor_lockup_horizontal_registered.svg" />
</LoginImageContainer>
<SignInput>
<div className="signupContainer">
<div className="welcome">환영합니다 - 로그인하세요!</div>
<label className="welcome label">이메일 주소</label>
<input onChange={inputValuedetector}
className="inputValue"
type='text'
name="email"
placeholder="이메일" />
<label className="welcome label">패스워드</label>
<input onChange={inputValuedetector}
className="inputValue"
type='password'
name="password"
placeholder="비밀번호" />
<div onClick={submit} className="inputValue logIntitle" type="buttton">
로그인
</div>
<div className="passWordFindertitle">
<span className="passwordfinder">
패스워드 찾기
</span>
</div>
<div className="passWordFindertitle">
계정이 없으신가요?
<span onClick={openSignup} className="passwordfinder">
가입하기
</span>
</div>
<div className="passWordFindertitle">
대신 카카오톡이나 Google을 사용하고 싶으세요?
<span onClick={returnSocialLogin} className="passwordfinder">
돌아가기
</span>
</div>
</div>
<ContiNue>
계속 진행할 경우, 트립어드바이저의
<span className="textDeco">
개인정보 취급방침
</span>
및
<span className="textDeco Cookie">
쿠키 정책
</span>
에 동의한 것으로 간주됩니다.
</ContiNue>
</SignInput>
<div className={ isValidEmail
? 'noneinputError'
: 'inputError'
} >
이메일 주소로써 유효하지 않거나 저희쪽에서 메일을 발송할 수 없는 문자열을 포함하고있습니다.
</div>
</div>
<button onClick={props.closeModal} className="returnmain" tabIndex="0">
X
</button>
</ModalIner>
{isSignup?<Signup closeSignup={closeSignup} />:null }
</ModalWrapper>
</>
)
}
const ModalOverlay = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 999;
`;
const ModalWrapper = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
overflow: auto;
outline: 0;
display: flex;
justify-content: center;
align-items: center;
`;
const ModalIner = styled.div`
width: 420px;
height: 666px;
padding: 40px 45px 30px;
box-sizing: border-box;
position: relative;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5);
background-color: white;
.modalOutline {
width: 100%;
.inputError {
position: absolute;
display: block;
color: red;
background-color: white;
font-family:굴림, gulim, sans-serif;
font-size: 12px;
border: 1px solid #e0e0e0;
box-shadow: 0 2px 12px rgba(0,0,0,.35);
padding: 18px;
line-height: 16px;
width: 316px;
height: 68px;
z-index: 5;
left: 51px;
right: auto;
top: 113px;
bottom: auto;;
}
.noneinputError {
display: none;
color: white;
}
}
.returnmain {
position: absolute;
z-index: 1;
top: 0;
right: 0;
text-align: center;
width: 48px;
height: 48px;
font-size: 25px;
cursor: pointer;
}
`;
const LoginImageContainer = styled.div`
width:328px;
`;
const LoginImgtag = styled.img`
width: 210px;
height: 32px;
margin-right: 20px;
`;
const SignInput = styled.div`
.signupContainer {
width: 328px;
height: 440px;
display: flex;
flex-direction: column;
.welcome {
color : black;
font-size: 16px;
font-family: '굴림, gulim, sans-serif';
margin: 36px 0 25px;
}
.label {
font-size: 14px;
font-weight: 600;
line-height: 18px;
text-align: left;
margin: 0;
}
.inputValue {
background-color: white;
height: 48px;
margin: 8px 0 16px;
padding : 4px 4px 4px 8px;
border: 2px solid #e0e0e0;
border-radius: 12px;
}
.logIntitle {
color : white;
background: black;
width: 100%;
font-size: 14px;
font-weight: 700;
margin: 20px 0;
padding: 14px 16px 8px;
border: 1px solid transparent;
line-height: 18px;
text-align : center;
&:hover {
background: #525252;
}
}
.passWordFindertitle {
margin-bottom: 12px;
.passwordfinder {
font-size: 12px;
font-family: '굴림, gulim, sans-serif';
font-weight: 600;
color: black;
line-height: 16px;
margin-left: 5px;
&:hover{
text-decoration: underline;
}
}
}
}
`;
const ContiNue = styled.div`
width: 328px;
height: 32px;
font-size: 12px;
font-family: '굴림, gulim, sans-serif';
.textDeco {
text-decoration: underline;
color: black;
text-decoration-style: solid;
font-weight: 600;
line-height: 16px;
margin: 0 5px;
}
.Cookie {
margin: 0 0 0 5px;
}
`;
export default withRouter(EmailLogin);
마지막으로 지금 헤매고 있는 회원가입
보내줘여할 곳이 소셜로그인과 이메일 로그인으로 보내줘야하는데, 이것저것 섞이니 지금 헷깔려서;;;;
import React, { useState } from "react";
import styled from "styled-components";
import { withRouter } from "react-router-dom";
import { Apiresource } from "../../Config";
import SocialLogin from "./SocialLogin";
import EmailLogin from "./EmailLogin";
function Signup(props, visible) {
const [ userInfo, setName ] = useState({email:"",password:"",name:""});
const [isLoginModalOn ,setIsLoginModalOn] = useState(false);
const inputValuedetector = (e) => {
const { name, value } = e.target
setName({...userInfo, [ name ] : value});
};
const submit = () => {
const {email, password, name } = userInfo
fetch(`${Apiresource}/account/sign-up`, {
method: 'POST',
body: JSON.stringify({ email, password, name })
})
.then(res => res.json)
.then(res => {
if (res) {
alert("회원가입을 환영합니다")
props.history.push('/main')
} else {
alert('이메일과 비밀번호를 확인해주십시오')
};
})
}
const returnLogin = (e) => {
props.history.push('/EmailLogin')
}
const returnSocialLogin = (e) => {
props.history.push('/SocialLogin')
}
const openEmailLogin = () => {
setIsLoginModalOn(true)
}
const closeEmailLogin = () => {
setIsLoginModalOn(false)
}
const isValidEmail = !userInfo.email.length || (userInfo.email.length && userInfo.email.includes("@"));
return(
<>
<ModalOverlay visible={visible} />
<ModalWrapper tabIndex="-1" visible={visible}>
<ModalIner tabIndex="0">
<div className="modalOutline">
<LoginImageContainer>
<LoginImgtag src="https://static.tacdn.com/img2/brand_refresh/Tripadvisor_lockup_horizontal_registered.svg" />
</LoginImageContainer>
<SignInput>
<div className="signupContainer">
<div className="signupBody">
<div className="welcome">
바로 회원가입하기 - 무료입니다!
</div>
<label className="welcome label">
이메일 주소
</label>
<input onChange={inputValuedetector}
className="email_input"
type='text'
name="email"
placeholder="이메일" />
<label className="welcome label">
패스워드 생성하기
</label>
<input onChange={inputValuedetector}
className="email_input"
type='password'
name="password"
placeholder="비밀번호" />
<label className="welcome label">
닉네임 생성하기
</label>
<input onChange={inputValuedetector}
className="email_input"
type='text'
name="name"
placeholder="닉네임" />
<div onClick={submit}
className="logIntitle"
type="button">
가입하기
</div>
</div>
<div className="passWordFindertitle">
이미 계정이 있으신가요?
<span onClick={returnLogin}
className="passwordfinder">
로그인
</span>
</div>
<div className="passWordFindertitle">
대신 카카오톡이나 Google을 사용하고 싶으세요?
<span onClick={returnSocialLogin}
className="passwordfinder">
돌아가기
</span>
</div>
</div>
<div className="inputboxTitle">
<input className="inputbot" type="checkbox"></input>
<label>
트립어드바이저의 여행 특가, 팁, 새로운 기능에 대한 정보 메일을 수신하겠습니다. 수신을 원치 않을 때는 해지할 수 있습니다.
</label>
</div>
</SignInput>
<div className={ isValidEmail
? 'noneinputError'
: 'inputError'
} >
이메일 주소로써 유효하지 않거나 저희쪽에서 메일을 발송할 수 없는 문자열을 포함하고있습니다.
</div>
</div>
<button onClick={props.closeModal}
className="returnmain"
tabIndex="0">
X
</button>
</ModalIner>
</ModalWrapper>
</>
)
}
const ModalOverlay = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 999;
`;
const ModalWrapper = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
overflow: auto;
outline: 0;
display: flex;
justify-content: center;
align-items: center;
`;
const ModalIner = styled.div`
width: 420px;
height: 666px;
padding: 40px 45px 30px;
box-sizing: border-box;
position: relative;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5);
background-color: white;
.modalOutline {
width: 100%;
.inputError {
position: absolute;
display: block;
color: red;
background-color: white;
font-family:굴림, gulim, sans-serif;
font-size: 12px;
border: 1px solid #e0e0e0;
box-shadow: 0 2px 12px rgba(0,0,0,.35);
padding: 18px;
line-height: 16px;
width: 316px;
height: 68px;
z-index: 5;
left: 51px;
right: auto;
top: 113px;
bottom: auto;;
}
.noneinputError {
display: none;
color: white;
}
}
.returnmain {
position: absolute;
z-index: 1;
top: 0;
right: 0;
text-align: center;
width: 48px;
height: 48px;
font-size: 25px;
cursor: pointer;
}
`;
const LoginImageContainer = styled.div`
width:328px;
`;
const LoginImgtag = styled.img`
width: 210px;
height: 32px;
margin-right: 20px;
`;
const SignInput = styled.div`
.signupContainer {
width: 100%;
height: 440px;
.signupBody {
display: flex;
flex-direction: column;
width: 100%;
height: 362px;
.welcome {
color : black;
font-size: 16px;
font-family: '굴림, gulim, sans-serif';
margin: 36px 0 25px;
}
.label {
font-size: 14px;
font-weight: 600;
line-height: 18px;
text-align: left;
margin: 0;
}
.email_input {
background-color: white;
height: 48px;
margin: 8px 0 16px;
padding : 4px 4px 4px 8px;
border: 2px solid #e0e0e0;
border-radius: 12px;
}
.logIntitle {
color : white;
background: black;
width: 100%;
height: 48px;
font-size: 14px;
font-weight: 700;
margin: 20px 0;
padding: 14px 16px 8px;
border-radius: 12px;
border: 1px solid transparent;
line-height: 18px;
text-align : center;
&:hover {
background: #525252;
}
}
}
.passWordFindertitle {
margin-bottom: 12px;
.passwordfinder {
font-size: 12px;
font-family: '굴림, gulim, sans-serif';
font-weight: 600;
color: black;
line-height: 16px;
margin-left: 5px;
&:hover{
text-decoration: underline;
}
}
}
}
`;
export default withRouter(Signup);
마지막으로 날 지금 갑자기 헤매게 만든....코드..
mode를 멘토님이 만들어주셨는데, 이부분이 몹시 헷깔린다.
mode를 인자로 받고 이것이 맞을때 이것을 넣어준다는 내용은 아무리 봐도 redux인데,
리덕스랑 비슷하다고만 말씀하시던;;;
배움에는 끝이없다는 내용이 생각난다.
다시한번 설명듣고 다시 천천히 정리해서 끝내야겠다.
import { withRouter } from "react-router-dom";
import SocialLogin from "../Components/LoginComponent/SocialLogin";
import EmailLogin from "../Components/LoginComponent/EmailLogin"
import SignUp from "../Components/LoginComponent/Signup"
const [isLoginModalOn ,setIsLoginModalOn] = useState(false);
const [loginMode ,setLoginMode] =useState("social")
const openLoginHadler =() => {
setIsLoginModalOn(true)
};
const closeModal =()=>{
setIsLoginModalOn(false)
}
const modalHandler =(mode)=>{
switch(mode){
case "social":
return <SocialLogin setMode={setLoginMode} closeModal={closeModal}/>
case "email":
return <EmailLogin closeModal={closeModal}/>
case "signup":
return <SignUp closeModal={closeModal}/>
default:
return <SocialLogin setMode={setLoginMode} closeModal={closeModal}/>
}
}
//--------------해당 코드가 작동되는 코드-------------------
<LoginBtn onClick={openLoginHadler}>로그인</LoginBtn>
</BtnWrap>
</NavWrap>
{isLoginModalOn? modalHandler(loginMode):null}
'코딩 > 위코드 코딩학습' 카테고리의 다른 글
[위코드] TIL(Today I am learned) -27(2차프로젝트 트립어드바이저)(후기) (0) | 2020.08.17 |
---|---|
[위코드] TIL(Today I am learned) -26 (0) | 2020.08.12 |
[위코드] TIL(Today I am learned) -24(위코드의 마지막 주말) (0) | 2020.08.09 |
[위코드] TIL(Today I am learned) -23 (0) | 2020.08.07 |
[위코드] TIL(Today I am learned) -22(2차프로젝트 시작/트립어드바이저) (0) | 2020.08.06 |