LoginSignup
0
0

More than 1 year has passed since last update.

React(TailwindCSS)の構成でアプリを作成しました【Movie App】

Last updated at Posted at 2022-06-27

環境の準備

①ターミナルでreactアプリケーションを作成する。

$ npx create-react-app <プロジェクト名>
$ cd <プロジェクト名>
$ npm start

② 使用しないファイルを削除する。

App.test.css
logo.svg  
reportWebVitals.js  
setupTest.js

③ 必要なパッケージをインストールする。

公式サイト:Tailwind CSS

$ npm install -D tailwindcss postcss autoprefixer
$ npm i tailwind-scrollbar-hide
$ npx tailwindcss init -p
$ yarn add axios react-router-dom
$ yarn add tailwindcss-scrollbar-hide

コンポーネント・ファイル構成

 src
  ├─ assets
       ├── loading_spinner.gif
       └── NavBar.jsx
  ├─ components
       ├── MovieCard.jsx
       └── NavBar.jsx
  ├─ pages
       ├── HomePage.jsx
       ├── MoviePage.jsx
       └── PageNotFound.jsx
  ├── App.jsx
  ├── index.css
  └── index.jsx
 ├── .env
 ├── craco.config.js
 ├── tailwind.config.js
src/components/MovieCard.jsx
import { Link } from 'react-router-dom';

const MovieCard = ({ movie }) => {
  return (
    <Link to={`/movie/${movie.id}`}>
      <div
        className='w-[21rem] max-w-[100%] bg-black rounded-xl p-3 text-white m-5
      flex flex-col cursol-poinster text-xl hover:scale-110'
      >
        <img
          className='w-full self-center rounded-lg h-[476px]'
          src={'https://image.tmdb.org/t/p/original/' + movie.poster_path}
          alt='poster'
        />
        <h3 className='my-1'>{movie.title}</h3>
        <h3 className='my-1'>{movie.vote_average}</h3>
      </div>
    </Link>
  );
};

export default MovieCard;
src/components/Nabvar.jsx
import { Link } from 'react-router-dom';

const NavBar = () => {
  return (
    <div className='w-full'>
      <Link to='/'>
        <div className='text-white text-2xl font-bold md:text-4xl lg:text-5xl ml-4 pt-2'>
          Front-end Moivies App
        </div>
      </Link>
    </div>
  );
};

export default NavBar;
src/pages/HomePage.jsx
import NavBar from '../components/NavBar';
import loading_spinner from './../assets/loading_spinner.gif';
import { useState, useEffect } from 'react';
import axios from 'axios';
import MovieCard from '../components/MovieCard';

async function getMovies(pageNo) {
  const res = await axios.get(
    `https://api.themoviedb.org/3/trending/movie/day?api_key=${process.env.REACT_APP_API_KEY}&page=${pageNo}`
  );
  console.log(res.data.results);
  return res.data.results;
}

const HomePage = () => {
  const [movies, setMovies] = useState('Loading');
  const [pageNo, setPageNo] = useState(1);

  useEffect(() => {
    getMovies(pageNo)
      .then((res) => {
        setMovies(res);
      })
      .catch((err) => {
        alert(err);
      });
  }, [pageNo]);
  if (movies === 'Loading' || !movies || movies.length === 0)
    return (
      <div className='flex items-center justify-center h-screen bg-gray-200'>
        <img src={loading_spinner} alt='loading' height='200px' width='200px' />
      </div>
    );
  else
    return (
      <div className='bg-gray-200 min-h-screen flex flex-col items-center h-full'>
        <NavBar />
        <div className='flex flex-warp justify-evenly'>
          {movies.map((movie) => (
            <MovieCard movie={movie} />
          ))}
        </div>
        <div className='w-[250] mt-5 pb-10 font-bold'>
          <button
            className='bg-white rounded-full px-4 mr-2 hover:border-black border-2 hover:font-bold'
            onClick={() => {
              if (pageNo > 1) setMovies('Loading');
              setPageNo(pageNo - 1);
            }}
          >
            Previous
          </button>
          {pageNo}
          <button
            className='bg-white rounded-full px-4 ml-2 hover:border-black border-2 hover:font-bold'
            onClick={() => {
              if (pageNo > 1000) setMovies('Loading');
              setPageNo(pageNo + 1);
            }}
          >
            Next
          </button>
        </div>
      </div>
    );
};

export default HomePage;
src/pages/MoviePage.jsx
import { useNavigate, useParams } from 'react-router';
import { useState, useEffect } from 'react';
import loading_spinner from './../assets/loading_spinner.gif';
import axios from 'axios';
import NavBar from './../components/NavBar';
import play_icon from './../assets/play_icon.png';
async function getMovie(movieId) {
  const res =
    await axios.get(`https://api.themoviedb.org/3/movie/${movieId}?api_key=${process.env.REACT_APP_API_KEY}
    `);
  return res.data;
}

async function getClips(movieId) {
  const res =
    await axios.get(`https://api.themoviedb.org/3/movie/${movieId}/videos?api_key=${process.env.REACT_APP_API_KEY}

    `);
  return res.data.results;
}

function MoviePage() {
  const { movieId } = useParams();
  const [movie, setMovie] = useState('loading');
  const navigate = useNavigate();
  const [width, setWidth] = useState(window.screen.availWidth);
  const [clips, setClips] = useState([]);
  let mt = width > 786 ? (width * 9) / 16 - 250 : 0;

  window.addEventListener('resize', () => {
    setWidth(window.screen.availWidth);
  });

  useEffect(() => {
    getMovie(movieId)
      .then((res) => {
        setMovie(res);
        getClips(movieId)
          .then((res) => {
            setClips(res);
          })
          .catch((err) => {
            alert(err);
            navigate('/', { replace: true });
          });

        if (width > 786) {
          window.scroll({ top: mt - 100, behavior: 'smooth' });
        }
      })
      .catch((err) => {
        alert(err);
        navigate('/', { replace: true });
      });
  }, [movieId, navigate, mt, width]);

  if (movie === 'loading' || !movie) {
    return (
      <div className='bg-gray-300 h-screen flex items-center justify-center'>
        <img src={loading_spinner} alt='loading' />
      </div>
    );
  }

  return (
    <div className='bg-gray-300 min-h-[100vh] text-white text-3xl sm:text-4xl md:text-3xl lg:text-4xl xl:text-6xl font-bold'>
      {width < 768 ? (
        <NavBar />
      ) : (
        <img
          src={'https://image.tmdb.org/t/p/original/' + movie.backdrop_path}
          alt='backdrop'
          className='w-screen aspect-video absolute top-0'
        />
      )}
      <div
        className='flex flex-col items-center justify-center md:flex-row md:ml-[50px]'
        style={{
          marginTop: `${mt}px`,
        }}
      >
        <img
          src={'https://image.tmdb.org/t/p/original/' + movie.poster_path}
          alt='poster'
          className='rounded-xl border-white border-4 max-w-[min(400px,90%)] sm:max-w-[50%]  md:h-[576px] z-10'
        />
        <h1 className='z-10 md:ml-10 text-center'>{movie.title}</h1>
      </div>

      {/*Clips And Trailers Part */}
      <div className='mt-5 md:mt-10 text-xl md:text-2xl lg:text-4xl pb-[100px] mx-2 sm:mx-5 md:mx-[50px] lg:mx-[100px]'>
        <div className='mt-5 md:mt-10 text-lg md:text-xl lg:text-2xl'>
          <div>
            Release Date :-
            <span className=' font-normal'>{movie.release_date}</span>
          </div>
          <div>
            Duration :-
            <span className=' font-normal'>
              {parseInt(movie.runtime / 60)}:{movie.runtime % 60} hr
            </span>
          </div>
          <div>
            Rating :-
            <span className=' font-normal'>{movie.vote_average}/10</span>
          </div>
        </div>
        Clips And Trailers
        <div className='flex overflow-scroll scrollbar-hide snap-x mt-5 md:mt-10'>
          {clips.map((clip) => (
            <div
              className='ml-5'
              onClick={() => {
                window.open(`https://youtube.com/watch?v=${clip.key}`);
              }}
            >
              <div className='relative flex-shrink-0 h-[180px] md:h-[250px] lg:h-[300px] aspect-video rounded-xl'>
                <img
                  src={`https://img.youtube.com/vi/${clip.key}/hqdefault.jpg`}
                  className='absolute object-cover h-[180px] md:h-[250px] lg:h-[300px] aspect-video rounded-xl '
                  alt='youtube thumbnail'
                />
                <img
                  src={play_icon}
                  alt='play icon'
                  className='absolute inset-0 w-[150px] h-[150px] m-auto'
                />
              </div>
              <p className='text-lg md:text-xl font-normal mt-1'>{clip.name}</p>
            </div>
          ))}
        </div>
        {/* Overview */}
        <div className='mt-5 md:mt-10'>OverView</div>
        <div className='mt-5 md:mt-10 font-normal text-lg md:text-xl lg:text-2xl'>
          {movie.overview}
        </div>
      </div>
    </div>
  );
}

export default MoviePage;
src/pages/PageNotFound.jsx
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router';

function PageNotFound() {
  const [time, setTime] = useState(5);
  const navigate = useNavigate();
  useEffect(() => {
    setTimeout(() => {
      setTime(time - 1);
      if (time === 0) {
        navigate('/', { replace: true });
      }
    }, 1000);
  }, [time, navigate]);
  return (
    <div className='bg-gray-700 h-screen flex flex-col items-center justify-center text-8xl font-bold'>
      <div className='text-red-600'>404</div>
      <div className='text-4xl text-white'>Page Not Found</div>
      <div className='text-4xl text-white'>
        Redirecting to home page in {time} sec
      </div>
    </div>
  );
}

export default PageNotFound;
App.jsx
import { HashRouter, Routes, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import MoviePage from './pages/MoviePage';
import PageNotFound from './pages/PageNotFound';

function App() {
  return (
    <HashRouter>
      <Routes>
        <Route exact path='/' element={<HomePage />} />
        <Route exact path='/movie/:movieId' element={<MoviePage />} />
        <Route exact path='*' element={<PageNotFound />} />
      </Routes>
    </HashRouter>
  );
}

export default App;
index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
.env
REACT_APP_API_KEY=" your key "
tailwind.config.js
module.exports = {
  content: ['./src/**/*.{jsx,js}'],
  theme: {
    extend: {},
  },
  plugins: [require('tailwind-scrollbar-hide')],
};

参考サイト

How To Use Tailwind JIT in React.JS | Tailwind JIT | React | May We Code

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0