ABOUT ME

웹개발과 일상에 대해 포스팅하는 블로그입니다.

Today
Yesterday
Total
  • 영화목록 (Netflix) 앱 11 - 모달창 사용하기
    프로젝트/영화 목록 (Netflix) 앱 2021. 11. 25. 21:52
    728x90

     

    * 모달창 컴포넌트 추가하기

    components 폴더에 아래 파일을 추가한다.

    import React from 'react'
    import './Modal.css'
    
    function Modal({ open, children }){
        return <div className={`Modal-container ${open? "open": "close"}`}>
                    <div className={`Modal`}>{children}</div>
                </div> 
    }
    
    export default Modal;
    
    Modal.defaultProps = {
        open: false
    }

    Modal.js 파일을 위와 같이 작성하자!

    .Modal-container{
        width: 100%;
        height: 100vh;
        background: black;
        /* opacity: 0.98; */
    
        position: absolute;
        display: none;
        top: 0;
        left: 0;
    }
    .Modal {
        width: 500px;
        box-shadow: rgba(255, 255, 255, 0.35) 0px 3px 15px;
        background: url('../assets/images/background.jpg');
        color: white;
        font-weight: bold;
        
        position: fixed;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        padding: 10px;
        text-align: center;
        z-index: 1;
    }
    .open{
        display: block;
    }
    .close{
        display: none;
    }

    Modal.css 파일을 위와 같이 작성하자!

    export { default as Input } from './Input'
    export { default as Movie } from './Movie'
    export { default as Loading } from './Loading'
    export { default as Button } from './Button'
    export { default as Menu } from './Menu'
    export { default as Modal } from './Modal'

    index.js 파일에 Modal 컴포넌트를 내보낸다.

     

    * 사용자 등록 화면에 모달창 적용하기

    import React, { useState } from 'react'
    import { useNavigate } from 'react-router-dom'
    import { Input, Button, Modal } from 'components'
    import './Register.css'
    
    const Register = () => {
        const [id, setId] = useState('')
        const [password, setPassword] = useState('')
        const [open, setOpen] = useState(false)
        const navigate = useNavigate()
    
        const handleChange = (e) => {
            const { name, value } = e.target
            name === 'id'? setId(value) : setPassword(value)
            console.log(name, value)
        }
        const handleRegister = () => {
            // 사용자 정보가 있으면 로그인 페이지로 이동하기
            if(JSON.parse(sessionStorage.getItem('user'))){
                navigate('/login')
            // 사용자 정보가 없으면 사용자를 생성하고 홈페이지로 이동하기
            }else {
                // 사용자 정보를 입력하지도 않고 그냥 버튼 누르면 곧바로 데이터 저장해버림
                if(id !== '' && password !== ''){
                    sessionStorage.setItem('user', JSON.stringify({ id, password }))  
                    navigate('/home')
                }else{
                    // alert('You need to give right user info.')
                    openModal()
                }
                
            }
        }
        const openModal = () => {
            setOpen(true)
        }
        const closeModal = () => {
            setOpen(false)
        }
        return (
            <div className='register-container'>
                <Input name='id' type='text' placeholder='Type ID ...' value={id} onChange={handleChange}/><br/>
                <Input name='password' type='password' placeholder='Type PASSWORD ...' value={password} onChange={handleChange}/>
                <Button handleClick={handleRegister}>Register</Button>
    
                {/* 모달창 */}
                <Modal open={open}>
                    <div className="header">-- Warning message --</div>
                    <div className="body">
                        You need to give right user information !
                    </div>
                    <div className="footer">
                        <Button size="small" handleClick={closeModal}>Close</Button>
                    </div>
                </Modal>
            </div>
        )
    }
    export default Register

    Register.js 파일을 위와 같이 작성하자!

    import { Input, Button, Modal } from 'components'

    Modal 컴포넌트를 추가로 임포트한다.

    const [open, setOpen] = useState(false)

    모달창을 열고 닫기 위하여 open 상태를 추가한다. 

    // 사용자 정보를 입력하지도 않고 그냥 버튼 누르면 곧바로 데이터 저장해버림
    if(id !== '' && password !== ''){
        sessionStorage.setItem('user', JSON.stringify({ id, password }))  
        navigate('/home')
    }else{
        // alert('You need to give right user info.')
        openModal()
    }

    경고창 대신 모달창을 오픈한다. 

    const openModal = () => {
        setOpen(true)
    }
    const closeModal = () => {
        setOpen(false)
    }

    모달창을 열고 닫는 이벤트핸들러 함수이다. open 상태를 true, false 로 변경한다. 

    <div className='register-container'>
        <Input name='id' type='text' placeholder='Type ID ...' value={id} onChange={handleChange}/><br/>
        <Input name='password' type='password' placeholder='Type PASSWORD ...' value={password} onChange={handleChange}/>
        <Button handleClick={handleRegister}>Register</Button>
    
        {/* 모달창 */}
        <Modal open={open}>
            <div className="header">-- Warning message --</div>
            <div className="body">
                You need to give right user information !
            </div>
            <div className="footer">
                <Button size="small" handleClick={closeModal}>Close</Button>
            </div>
        </Modal>
    </div>

    Modal 컴포넌트를 추가한다. 모달창의 header, body, footer 에는 원하는 문구나 입력창을 넣어주면 된다. 

    .register-container{
        width: 100%;
        height: 100vh;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        background: url('../assets/images/background.jpg');
    }
    
    
    /* 모달창 스타일링 */
    .header, .body, .footer{
        padding: 5px;
    }
    .header{
        font-size: 1.2rem;
        font-weight: bold;
    }
    .body{
        font-size: 1rem;
        margin: 20px;
    }
    .body input{
        width: 60%;
        height: 30px;
        border: 1px solid lightgray;
        border-radius: 5px;
        margin-bottom: 10px;
    }
    .footer{
        display: flex;
        justify-content: flex-end;
        align-items: center;
    }

    Register.css 파일을 위와 같이 수정하자! 모달창의 header, body, footer 관련 스타일이 추가되었다.

     

    * 로그인 화면에 모달창 적용하기

    import React, { useReducer, useState } from 'react'
    import { useNavigate } from "react-router-dom"
    
    import { Input, Button, Modal } from 'components'
    import './Login.css'
    
    const Login = () => {
        const [id, setId] = useState('')
        const [password, setPassword] = useState('')
        const [open, setOpen] = useState(false)
        const navigate = useNavigate()
    
        const handleChange = (e) => {
            const { name, value } = e.target
            name === 'id' ? setId(value) : setPassword(value)
            console.log(name, value)
        }
        const isNotValid = (user) => {
            console.log(user)
            return user.id === '' || user.password === ''
        }
        const handleLogin = () => {
            // 사용자 정보가 있으니까 불러오기
            const user = JSON.parse(sessionStorage.getItem('user'))
    
            if(!isNotValid(user) && (id === user.id && password === user.password)){
                navigate('/home')
            }else{
                // alert('You gaved wrong id or password !')
                openModal()
            } 
        }
        const openModal = () => {
            setOpen(true)
        }
        const closeModal = () => {
            setOpen(false)
        }
        return (
            <div className='login-container'>
                <Input name='id' type='text' placeholder='Type ID ...' value={id} onChange={handleChange}/><br/>
                <Input name='password' type='password' placeholder='Type PASSWORD ...' value={password} onChange={handleChange}/><br/>
                <Button handleClick={handleLogin}>Login</Button>
    
                {/* 모달창 */}
                <Modal open={open}>
                    <div className="header">-- Warning message --</div>
                    <div className="body">
                        You gaved wrong id or password !
                    </div>
                    <div className="footer">
                        <Button size="small" handleClick={closeModal}>Close</Button>
                    </div>
                </Modal>
            </div>
        )
    }
    
    export default Login

    Login.js 파일을 위와 같이 수정하자!

    import { Input, Button, Modal } from 'components'

    Modal 컴포넌트를 추가로 임포트한다.

    const [open, setOpen] = useState(false)

    모달창을 열고 닫기 위하여 open 상태를 추가한다. 

    const handleLogin = () => {
        // 사용자 정보가 있으니까 불러오기
        const user = JSON.parse(sessionStorage.getItem('user'))
    
        if(!isNotValid(user) && (id === user.id && password === user.password)){
            navigate('/home')
        }else{
            // alert('You gaved wrong id or password !')
            openModal()
        } 
    }

    경고창 대신 모달창을 오픈한다.

    const openModal = () => {
        setOpen(true)
    }
    const closeModal = () => {
        setOpen(false)
    }

    모달창을 열고 닫는 이벤트핸들러 함수이다. open 상태를 true, false 로 변경한다. 

    <div className='login-container'>
        <Input name='id' type='text' placeholder='Type ID ...' value={id} onChange={handleChange}/><br/>
        <Input name='password' type='password' placeholder='Type PASSWORD ...' value={password} onChange={handleChange}/><br/>
        <Button handleClick={handleLogin}>Login</Button>
    
        {/* 모달창 */}
        <Modal open={open}>
            <div className="header">-- Warning message --</div>
            <div className="body">
                You gaved wrong id or password !
            </div>
            <div className="footer">
                <Button size="small" handleClick={closeModal}>Close</Button>
            </div>
        </Modal>
    </div>

    Modal 컴포넌트를 추가한다. 모달창의 header, body, footer 에는 원하는 문구나 입력창을 넣어주면 된다.

    .login-container{
        width: 100%;
        height: 100vh;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        background: url('../assets/images/background.jpg');
    }
    
    /* 모달창 스타일링 */
    .header, .body, .footer{
        padding: 5px;
    }
    .header{
        font-size: 1.2rem;
        font-weight: bold;
    }
    .body{
        font-size: 1rem;
        margin: 20px;
    }
    .body input{
        width: 60%;
        height: 30px;
        border: 1px solid lightgray;
        border-radius: 5px;
        margin-bottom: 10px;
    }
    .footer{
        display: flex;
        justify-content: flex-end;
        align-items: center;
    }

    login.css 파일을 위와 같이 수정하자! 모달창의 header, body, footer 관련 스타일이 추가되었다.

     

    * 영화추천 페이지에 모달창 적용하기

    import React, { useState, useEffect } from 'react'
    import { useLocation, Link, useNavigate } from 'react-router-dom'
    
    import { Movie, Menu, Button, Modal } from 'components'
    import './Recommendation.css'
    
    
    
    const Recommendation = () => {
        const [open, setOpen] = useState(false)
    
        // 사용자 정보 유무에 따른 페이지 접근 제한하기
        const navigateToRegister = useNavigate()
        const user = JSON.parse(sessionStorage.getItem('user'))
    
        const openModal = () => {
            setOpen(true)
        }
        const closeModal = () => {
            setOpen(false)
    
            // alert("Sorry ! You need to register first !")
            navigateToRegister('/')
        }
    
        if(!user){
            useEffect ( () => {
                openModal()
            })
            
            return <>
                        {/* 모달창 */}
                        <Modal open={open}>
                            <div className="header">-- Warning message --</div>
                            <div className="body">
                                "Sorry ! You need to register first !"
                            </div>
                            <div className="footer">
                                <Button size="small" handleClick={closeModal}>Close</Button>
                            </div>
                        </Modal>
                    </>
        }
    
    
        const location = useLocation()
        const { movies } = location.state
        console.log(movies)
        const navigate = useNavigate()
    
        const likes = JSON.parse(sessionStorage.getItem('likes')) || {}
        console.log(likes)
    
        const toHomePage = () => {
            navigate('/home')
        }
    
        const bestMovies = movies
                                .sort( (a, b) => {
                                    return (b.rating - a.rating);
                                })
                                .slice(0, 3)
                                .map(movie =>
                                    <Link key={movie.id}  
                                        to='/detail'
                                        state={{ movie }} 
                                        style={{ textDecoration: 'none', color: 'white'}}
                                    >
                                        <Movie 
                                                title={movie.title} 
                                                genres={movie.genres} 
                                                cover={movie.medium_cover_image} 
                                                summary={movie.summary}
                                                year={movie.year}
                                                rating={movie.rating}
                                                likes={likes[movie.id]}
                                            />
                                    </Link> 
                                            )
        
        // likes 객체를 배열 객체로 변환하기
        const likesArray = []
        for(let like in likes){
            likesArray.push({ id: like, favorite: likes[like]})
        }
    
        // favorite (좋아요 숫자) 기준으로 정렬하기
        const bestMoviesByLikes = likesArray
        .sort( (a, b) => {
            return (b.favorite - a.favorite);
        })
        .slice(0, 3)
        .map(likeInfo => {
            const movieId = parseInt(likeInfo.id) 
            const movie = movies.filter(movie => movie.id === movieId)[0]
            console.log('movie by likes',parseInt(likeInfo.id))
            console.log('movie: ', movie)
    
            return (
                <Link key={movie.id}  
                to='/detail'
                state={{ movie }} 
                style={{ textDecoration: 'none', color: 'white'}}
            >
                <Movie 
                        title={movie.title} 
                        genres={movie.genres} 
                        cover={movie.medium_cover_image} 
                        summary={movie.summary}
                        year={movie.year}
                        rating={movie.rating}
                        likes={likes[movie.id]}
                    />
            </Link> 
            )     
         } )
                    
    
    
        return (
            <div className='Recommendation-container'>
                <Menu>
                    <Button handleClick={toHomePage}>Home</Button>
                </Menu>
                <div className='Recommendation-text first-text'>Best Movies by rating</div>
                <div className='Recommendation-bestmovies'>{bestMovies}</div>
                <div className='Recommendation-text second-text'>Best Movies by likes</div>
                <div className='Recommendation-bestmovies'>{bestMoviesByLikes}</div>
            </div>
        )
    }
    export default Recommendation

    Recommendation.js 파일을 위와 같이 수정하자! 

    import React, { useState, useEffect } from 'react'

    useState 함수를 추가로 임포트한다. 

    import { Movie, Menu, Button, Modal } from 'components'

    Modal 컴포넌트를 추가로 임포트한다. 

    const [open, setOpen] = useState(false)

    모달창을 열고 닫기 위하여 open 상태를 추가한다. 

    const openModal = () => {
        setOpen(true)
    }
    const closeModal = () => {
        setOpen(false)
    
        // alert("Sorry ! You need to register first !")
        navigateToRegister('/')
    }

    모달창을 열고 닫는 이벤트핸들러 함수이다. 모달창을 닫을때 사용자 등록화면으로 이동한다. 

    useEffect ( () => {
        openModal()
    })

     

    컴포넌트 마운트가 완료되었을때 모달창을 오픈한다. 

    <>
        {/* 모달창 */}
        <Modal open={open}>
            <div className="header">-- Warning message --</div>
            <div className="body">
                "Sorry ! You need to register first !"
            </div>
            <div className="footer">
                <Button size="small" handleClick={closeModal}>Close</Button>
            </div>
        </Modal>
    </>

    모달창을 추가하고 원하는 문구를 header, body, footer 에 넣어준다. 

     

    * 홈화면에 모달창 적용하기

    import React, { useState, useEffect } from 'react'
    import { Link, useNavigate } from 'react-router-dom'
    
    import { Movie, Loading, Input, Button, Menu, Modal } from 'components'
    import './Home.css'
    
    const Home = () => {
    
        const [open, setOpen] = useState(false)
    
        // 사용자 정보 유무에 따른 페이지 접근 제한하기
        const navigateToRegister = useNavigate()
        const user = JSON.parse(sessionStorage.getItem('user'))
    
        const openModal = () => {
            setOpen(true)
        }
        const closeModal = () => {
            setOpen(false)
    
            // alert("Sorry ! You need to register first !")
            navigateToRegister('/')
        }
    
        if(!user){
            useEffect ( () => {
                openModal()
            })
            
            return <>
                        {/* 모달창 */}
                        <Modal open={open}>
                            <div className="header">-- Warning message --</div>
                            <div className="body">
                                "Sorry ! You need to register first !"
                            </div>
                            <div className="footer">
                                <Button size="small" handleClick={closeModal}>Close</Button>
                            </div>
                        </Modal>
                    </>
        }
    
    
    
        const [loading, setLoading] = useState(true)
        const [movies, setMovies] = useState([])
        const [query, setQuery] = useState('')
        const [isSorted, setIsSorted] = useState(-1)
        const [limit, setLimit] = useState(6)
        const navigate = useNavigate()
    
        const likes = JSON.parse(sessionStorage.getItem('likes')) || {}
        console.log(likes)
        
    
        useEffect( () => {
            fetch('https://yts.mx/api/v2/list_movies.json?limit=20')
            .then( res => res.json())
            .then( result => {
                const {data: {movies}} = result
                console.log(movies)
                setLoading(false)
                setMovies(movies)
            })
        }, [])
    
        const handleChange = (e) => {
            const { value } = e.target
            setQuery(value)
        }
    
        const sortByYear = (e) => {
            setIsSorted(isSorted * -1)
        }
    
        const updateLikes = (id) => {
            const likes = JSON.parse(sessionStorage.getItem('likes')) || {}
           
            if(likes[id] === null || likes[id] === undefined){
                likes[id] = 0
            }
            likes[id] += 1
            sessionStorage.setItem('likes', JSON.stringify(likes))
        }
    
        const handleRemove = (id) => {
            const moviesFiltered = movies.filter(movie => movie.id !== id)
            setMovies(moviesFiltered)
    
            // likes 리스트에서도 해당 영화에 대한 좋아요 정보 제거
            const likes = JSON.parse(sessionStorage.getItem('likes')) || {}
            delete likes[id]
            sessionStorage.setItem('likes', JSON.stringify(likes))
    
        }
        const displayEntireMovies = () => {
            console.log('display all movies !')
            setLimit(movies.length)
        }
    
        const homeUI = movies
                            .filter(movie => {
                                const title = movie.title.toLowerCase()
                                const genres = movie.genres.join(' ').toLowerCase()
                                const q = query.toLowerCase()
                            
                                return title.includes(q) || genres.includes(q)
                            })
                            .sort( (a, b) => {
                                return (b.year - a.year) * isSorted;
                            })
                            .slice(0, limit)
                            .map(movie =>
                                <div className='movie-item' key={movie.id}  >
                                    <div className='movie-delete' onClick={(e) => handleRemove(movie.id)}>X</div>
                                    <Link 
                                        to='/detail'
                                        state={{ movie }} 
                                        style={{ textDecoration: 'none', color: 'white'}}
                                        onClick={() => updateLikes(movie.id)}
                                    >
                                        
                                        <Movie 
                                                title={movie.title} 
                                                genres={movie.genres} 
                                                cover={movie.medium_cover_image} 
                                                summary={movie.summary}
                                                year={movie.year}
                                                rating={movie.rating}
                                                likes={likes[movie.id]}
                                            />
                                    </Link>
                                </div> 
                                        )
    
        const toRankPage = () => {
            navigate('/recommend', { state: { movies }})
        }
        return (
            <>
                {loading? <Loading/>: <div className='Home-container'>
                                        <Menu>
                                            <Button handleClick={toRankPage}>Rank</Button>
                                        </Menu>
                                        <div className='Home-entire'>
                                            <Button handleClick={displayEntireMovies}>See Entire Movies</Button>
                                        </div>
                                        
                                        <div className='Home-contents'>
                                            <Input name='search' type='text' placeholder='Search movies ...' value={query} onChange={handleChange}/>
                                            <Button handleClick={sortByYear}>정렬</Button>
                                            <div className='Home-movies'>{homeUI}</div>
                                        </div>
                                      </div>}
            </>
        )
    }
    
    export default Home

    Home.js 파일을 위와 같이 수정하자!

    import { Movie, Loading, Input, Button, Menu, Modal } from 'components'

    Modal 컴포넌트를 추가로 임포트한다.

    const [open, setOpen] = useState(false)
    
        // 사용자 정보 유무에 따른 페이지 접근 제한하기
        const navigateToRegister = useNavigate()
        const user = JSON.parse(sessionStorage.getItem('user'))
    
        const openModal = () => {
            setOpen(true)
        }
        const closeModal = () => {
            setOpen(false)
    
            // alert("Sorry ! You need to register first !")
            navigateToRegister('/')
        }
    
        if(!user){
            useEffect ( () => {
                openModal()
            })
            
            return <>
                        {/* 모달창 */}
                        <Modal open={open}>
                            <div className="header">-- Warning message --</div>
                            <div className="body">
                                "Sorry ! You need to register first !"
                            </div>
                            <div className="footer">
                                <Button size="small" handleClick={closeModal}>Close</Button>
                            </div>
                        </Modal>
                    </>
        }

    영화추천 페이지와 동일한 코드가 추가되었다. 

     

    * 상세페이지에 모달창 적용하기

    import React, { useEffect, useState } from 'react'
    import { Movie, Button, Menu, Modal } from 'components'
    import { useLocation, useNavigate } from 'react-router-dom'
    
    import './Detail.css'
    
    const Detail = () => {
        const [open, setOpen] = useState(false)
    
        // 사용자 정보 유무에 따른 페이지 접근 제한하기
        const navigateToRegister = useNavigate()
        const user = JSON.parse(sessionStorage.getItem('user'))
    
        const openModal = () => {
            setOpen(true)
        }
        const closeModal = () => {
            setOpen(false)
    
            // alert("Sorry ! You need to register first !")
            navigateToRegister('/')
        }
    
        if(!user){
            useEffect ( () => {
                openModal()
            })
            
            return <>
                        {/* 모달창 */}
                        <Modal open={open}>
                            <div className="header">-- Warning message --</div>
                            <div className="body">
                                "Sorry ! You need to register first !"
                            </div>
                            <div className="footer">
                                <Button size="small" handleClick={closeModal}>Close</Button>
                            </div>
                        </Modal>
                    </>
        }
    
        
        const location = useLocation()
        const { movie } = location.state
        const { yt_trailer_code } = movie
        console.log(movie)
        const navigate = useNavigate()
    
        const likes = JSON.parse(sessionStorage.getItem('likes')) || {}
        console.log(likes)
    
        const watchMovieTrailer = () => {
            window.location.href = yt_trailer_code? `https://www.youtube.com/watch?v=${yt_trailer_code}`: ""
        }
        const toHomePage = () => {
            navigate('/home')
        }
        return (
            <div className='Detail-container'>
                <Menu>
                    <Button handleClick={toHomePage}>Home</Button>
                </Menu>
                <div className='Detail-contents'>
                    <Movie title={movie.title} 
                            genres={movie.genres} 
                            cover={movie.medium_cover_image} 
                            summary={movie.summary}
                            year={movie.year}
                            rating={movie.rating}
                            likes={likes[movie.id]}>    
                    </Movie>
    
                    <div className='Movie-info'>
                        <p className='Movie-runtime'>Runtime {movie.runtime} min.</p>
                        <p className='Movie-summary'>{movie.summary}</p>
                        <a href={movie.torrents.length !== 0 ? movie.torrents[0].url : ''} download>Download Torrent</a><br/>
                        <Button handleClick={watchMovieTrailer}>Watch Youtube trailer</Button>
                    </div>
                </div>
            </div>
        )
    }
    export default Detail

    Detail.js 파일을 위와 같이 작성하자!

    import React, { useEffect, useState } from 'react'

    useState 함수를 추가로 임포트한다. 

    import { Movie, Button, Menu, Modal } from 'components'

    Modal 컴포넌트를 추가로 임포트한다. 

     const [open, setOpen] = useState(false)
    
        // 사용자 정보 유무에 따른 페이지 접근 제한하기
        const navigateToRegister = useNavigate()
        const user = JSON.parse(sessionStorage.getItem('user'))
    
        const openModal = () => {
            setOpen(true)
        }
        const closeModal = () => {
            setOpen(false)
    
            // alert("Sorry ! You need to register first !")
            navigateToRegister('/')
        }
    
        if(!user){
            useEffect ( () => {
                openModal()
            })
            
            return <>
                        {/* 모달창 */}
                        <Modal open={open}>
                            <div className="header">-- Warning message --</div>
                            <div className="body">
                                "Sorry ! You need to register first !"
                            </div>
                            <div className="footer">
                                <Button size="small" handleClick={closeModal}>Close</Button>
                            </div>
                        </Modal>
                    </>
        }

    영화추천 페이지와 동일한 코드가 추가되었다. 

    728x90
Designed by Tistory.