diff --git a/README.md b/README.md index 4c8fd7876..eda5fda04 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # Project Movies -Replace this readme with your own information about your project. +How to build multi-page application using React Router. Pass information such as product ids, blog post titles in the URL and pick this up in React router (dynamic content). Practice using APIs in React combining useState and useEffect. -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +This project was ment for pair-programming but I did this solo due to time schedule conflicts. ## The problem - -Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? +My main problem with this project was actually understanding API and how to change it to be able to interract and use the URL with React. I spent hours just trying to figure out the details of that,combining it with all the steps required for React Router and multi-pages that is asked for the app. +I needed a lot of help which former students was able to provide. I redid my code and saved a lot of tryouts in external platforms, but my knowledge is not due just yet in this level of React. I find it stimulating to work with but I need more given time to actually unerstand earlier works with props, map and useEffect. Adding API fetch and many componenets with Routes was way to difficult I felt doing solo. +I worked a lot with turtorials from Technigo, tried the Example project steps but found it very time consuming not sure if it's the best way. Then I booked session with technigo-coach and external help through friends, and former students work to be able to focus on understanding the steps and learning about React Routers wonders. ## View it live -Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. +https://lighthearted-dolphin-81e027.netlify.app/ diff --git a/code/package-lock.json b/code/package-lock.json index bb51e893e..3fa72176e 100644 --- a/code/package-lock.json +++ b/code/package-lock.json @@ -16,7 +16,8 @@ "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.9.0" }, "devDependencies": { "react-scripts": "5.0.1" @@ -3124,6 +3125,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", + "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==", + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -14384,6 +14393,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", + "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", + "dependencies": { + "@remix-run/router": "1.4.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", + "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", + "dependencies": { + "@remix-run/router": "1.4.0", + "react-router": "6.9.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -19509,6 +19548,11 @@ "source-map": "^0.7.3" } }, + "@remix-run/router": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", + "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -27684,6 +27728,23 @@ "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "dev": true }, + "react-router": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", + "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", + "requires": { + "@remix-run/router": "1.4.0" + } + }, + "react-router-dom": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", + "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", + "requires": { + "@remix-run/router": "1.4.0", + "react-router": "6.9.0" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/code/package.json b/code/package.json index 7aad26ebc..9736b71ad 100644 --- a/code/package.json +++ b/code/package.json @@ -11,7 +11,8 @@ "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^6.9.0" }, "scripts": { "start": "react-scripts start", diff --git a/code/src/App.js b/code/src/App.js index f2007d229..d723b835a 100644 --- a/code/src/App.js +++ b/code/src/App.js @@ -1,9 +1,26 @@ import React from 'react'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' +import { NotFound } from 'components/NotFound' +import { MovieList } from 'components/MovieList' +import { Header } from 'components/Header' +import { Details } from 'components/Details' +import { TVSeriesList } from 'components/TVseriesList' +import { TVseriesDetails } from 'components/TVseriesDetails' export const App = () => { return ( -
- Find me in src/app.js! -
+ +
+
+ + } /> + } /> + } /> + } /> + } /> + } /> + +
+
); } diff --git a/code/src/back-button.png b/code/src/back-button.png new file mode 100644 index 000000000..c1f202fc6 Binary files /dev/null and b/code/src/back-button.png differ diff --git a/code/src/components/Details.js b/code/src/components/Details.js new file mode 100644 index 000000000..81c4a9f13 --- /dev/null +++ b/code/src/components/Details.js @@ -0,0 +1,45 @@ +import React, { useEffect, useState } from 'react'; +import { useParams, Link } from 'react-router-dom'; +import { NotFound } from './NotFound'; + +export const Details = () => { + const [movieDetails, setMovieDetails] = useState([]) + const { id } = useParams() + + const FetchDetails = () => { + fetch(`https://api.themoviedb.org/3/movie/${id}?api_key=7f5b30559b3d85aec06d3d1a010f4a39&language=en-US`) + .then((response) => response.json()) + .then((data) => setMovieDetails(data)) + .catch((error) => { + console.log(error) + if (error) { + return ( + + ) + } + }) + } + + useEffect(() => { + FetchDetails() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( +
+ + back button +

Home

+ +
+ movie +
+

{movieDetails.title}

+
+

{movieDetails.overview}

+
+
+ ) +} \ No newline at end of file diff --git a/code/src/components/Header.js b/code/src/components/Header.js new file mode 100644 index 000000000..e6036e8b8 --- /dev/null +++ b/code/src/components/Header.js @@ -0,0 +1,17 @@ +import React from 'react' +import { NavLink } from 'react-router-dom' + +export const Header = () => { + return ( +
+ + movie-icon + + +
+ ) +} diff --git a/code/src/components/Movie.js b/code/src/components/Movie.js new file mode 100644 index 000000000..fbc43f3e9 --- /dev/null +++ b/code/src/components/Movie.js @@ -0,0 +1,28 @@ +import React, { useState } from 'react'; +import { Link } from 'react-router-dom'; + +export const Movie = ({ title, releaseDate, movieID, poster }) => { + const [isHover, setIsHover] = useState(false) + + const handleMouseEnter = () => { + setIsHover(true); + }; + const handleMouseLeave = () => { + setIsHover(false); + }; + + return ( +
+ + + movie +
+

{title}

+

Released {releaseDate}

+
+ +
+ ) +} \ No newline at end of file diff --git a/code/src/components/MovieList.js b/code/src/components/MovieList.js new file mode 100644 index 000000000..c4c50af88 --- /dev/null +++ b/code/src/components/MovieList.js @@ -0,0 +1,35 @@ +import React, { useEffect, useState } from 'react' +import { MOVIES_URL } from 'utils/Urls' +import { Movie } from 'components/Movie' + +export const MovieList = () => { + const [list, setList] = useState([]) + + const FetchMovies = () => { + fetch(MOVIES_URL) + .then((response) => response.json()) + .then((data) => { + console.log(data.results) + setList(data.results) + }) + } + useEffect(() => { + FetchMovies() + }, []) + + return ( +
+ {list.map((movie) => ( +
+ +
+ ))} +
+ ) +} \ No newline at end of file diff --git a/code/src/components/NotFound.js b/code/src/components/NotFound.js new file mode 100644 index 000000000..da2656d19 --- /dev/null +++ b/code/src/components/NotFound.js @@ -0,0 +1,17 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +export const NotFound = () => { + const navigate = useNavigate(); + const returnToHomePage = () => { + navigate('/'); + } + + return ( +
+ not-found +

Ooh noo.... Page not found

+ +
+ ); +} \ No newline at end of file diff --git a/code/src/components/TVseries.js b/code/src/components/TVseries.js new file mode 100644 index 000000000..d9c186678 --- /dev/null +++ b/code/src/components/TVseries.js @@ -0,0 +1,28 @@ +import React, { useState } from 'react'; +import { Link } from 'react-router-dom'; + +export const TVSeries = ({ singleTvAirDate, singleTvID, originalName, poster }) => { + const [isHover, setIsHover] = useState(false) + + const handleMouseEnter = () => { + setIsHover(true); + }; + const handleMouseLeave = () => { + setIsHover(false); + }; + + return ( +
+ + + TVseries +
+

{originalName}

+

First aired {singleTvAirDate.first_air_date}

+
+ +
+ ) +} \ No newline at end of file diff --git a/code/src/components/TVseriesDetails.js b/code/src/components/TVseriesDetails.js new file mode 100644 index 000000000..911a553cd --- /dev/null +++ b/code/src/components/TVseriesDetails.js @@ -0,0 +1,46 @@ +import React, { useEffect, useState } from 'react' +import { useParams, Link } from 'react-router-dom'; +import { NotFound } from './NotFound'; + +export const TVseriesDetails = () => { + const [details, setDetails] = useState([]) + const { id } = useParams() + + const fetchTvDetails = () => { + fetch(`https://api.themoviedb.org/3/tv/${id}?api_key=7f5b30559b3d85aec06d3d1a010f4a39&language=en-US`) + .then((response) => response.json()) + .then((data) => setDetails(data)) + .catch((error) => { + console.log(error) + if (error) { + return ( + + ) + } + }) + } + + useEffect(() => { + fetchTvDetails() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( +
+ + back button +

Home

+ +
+ profile + +
+

{details.title}

+
+

{details.overview}

+
+
+ ) +}; \ No newline at end of file diff --git a/code/src/components/TVseriesList.js b/code/src/components/TVseriesList.js new file mode 100644 index 000000000..5c9cd496e --- /dev/null +++ b/code/src/components/TVseriesList.js @@ -0,0 +1,36 @@ + +import React, { useEffect, useState } from 'react' +import { TVSHOW_URL } from 'utils/Urls' +import { TVSeries } from './TVseries' + +export const TVSeriesList = () => { + const [tvList, setTvList] = useState([]) + + const FetchTvSeriesList = () => { + fetch(TVSHOW_URL) + .then((response) => response.json()) + .then((data) => { + console.log(data.results) + setTvList(data.results) + }) + } + useEffect(() => { + FetchTvSeriesList() + }, []) + + return ( +
+ {tvList.map((tvshow) => ( +
+ +
+ ))} +
+ ) +} \ No newline at end of file diff --git a/code/src/index.css b/code/src/index.css index 4a1df4db7..5919f1247 100644 --- a/code/src/index.css +++ b/code/src/index.css @@ -1,3 +1,7 @@ +html { + min-height: 100%; + background: rgba(0,0,0,0.75); +} body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", @@ -11,3 +15,261 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } + +header { + /* position:fixed; */ + width: 100%; + top: 0; + display: flex; + justify-content: center; + flex-direction: column; + background: #181818e0; + align-items: center; +} + +.navlink-container { + list-style: none; + display: flex; + gap: 10px; +} + + +.movienation { + margin: 10px; + width: 100px; + height: auto; + content: url(red.png) +} + +.navlink:hover { + transform: scale(1.1); + transition: transform 0.4s; + cursor: pointer; +} + +/*TEXT SIZINGS*/ +h1 { + font-weight: bold; + margin: 5px 0px; +} + +p { + font-size: 16px; + letter-spacing: 1px; + margin-bottom: 15px; +} + +.details h1, p{ + color: white; + background: rgb(0 0 0 / 40%); +} + +.not-found { + margin-top: 100px; + display: flex; + flex-direction: column; + align-items: center; + color: #F9F9F9; + padding: 30px; +} + +.not-found img{ + content: url(not-found.png); +} + + +/*DETAIL FOR MOVIES*/ +.background { + background-color: #1e1e1e; + min-height: 100vh; + background-position: center; + background-size: cover; + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; +} + +.movie-detail { + max-width: 70%; + margin-right: 10px; + margin-bottom: 10px; + margin-left: 10px; +} + +/*Main page design with picture*/ +main { + display: flex; + flex-wrap: wrap; +} + +.movie-card { + position: relative; + width: 50%; + background: rgba(0,0,0,0.75); +} + +.movie-card img{ + width: 98%; + border: solid #a09c9c5e 0.6px; +} + +.movie-detail { + display: flex; + flex-direction: column; + margin: 50px; +} + +.movie-card a:hover > img{ + opacity: 0.5; + transition: 0.3s ease-in-out; +} + +/*DESIGN FOR TV */ +.tv-list-card { + position: relative; +} + +.tv-list-card img{ + width: 100%; +} + +.tv-detail { + display: flex; + flex-direction: column; +} + +.tv-list-card a:hover >img{ + opacity: 0.5; + transition: 0.3s ease-in-out; +} + +/*TV detail page*/ +.tv-background { + background-color: #1e1e1e; + min-height: 100vh; + background-position: center; + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + +} + + +.description { + position: absolute; + left: 20px; + bottom: 20px; + color: white; +} + +.hidden-description { + display: none; +} + + +/*Button design*/ +.back-button img{ + content: url(back-button.png); +} +.back-button { + display: flex; + align-items: center; + justify-content: center; + color: white; + cursor: pointer; + margin: 20px; + text-decoration: none; +} + +.back-button p { + margin: 0px; + color: white; + font-size: bold; +} + +.back-button:hover { + transform: scale(1.04); + transition: 0.3s ease-in-out; +} + +.emoji { + font-size: 60px; + margin: 5px; +} + + + +/** MEDIA QUERIES **/ +@media (min-width: 360px){ + main { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-items: center; + } + + .movie-card .tv-card { + width: 70%; + } + + .back-button img{ + content: url(back-button.png); + max-width: 20%; + } + +} + + +@media (min-width: 667px){ + +main { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-items: center; + } + +.movie-card { + width: 50%; +} + +.movie-detail { + flex-direction: row; + align-items: flex-end; +} + +.description p { + font-size: 13px; + margin: 5px 0px; +} + +.back-button img{ + content: url(back-button.png); + max-width: 40%; +} + +h1 { + font-size: 32px; +} + +p { + font-size: 24px; +} + + +@media (min-width: 1024px) { + header { + flex-direction: row; + } + .movie-card { + width: 25%; + } + + .movie-detail p { + max-width: 70%; + } +}} \ No newline at end of file diff --git a/code/src/not-found.png b/code/src/not-found.png new file mode 100644 index 000000000..9f018463f Binary files /dev/null and b/code/src/not-found.png differ diff --git a/code/src/red.png b/code/src/red.png new file mode 100644 index 000000000..8084ef6b5 Binary files /dev/null and b/code/src/red.png differ diff --git a/code/src/utils/Urls.js b/code/src/utils/Urls.js new file mode 100644 index 000000000..cf958937f --- /dev/null +++ b/code/src/utils/Urls.js @@ -0,0 +1,4 @@ +export const MOVIES_URL = 'https://api.themoviedb.org/3/movie/popular?api_key=7f5b30559b3d85aec06d3d1a010f4a39&language=en-US&page=1' +export const TVSHOW_URL = 'https://api.themoviedb.org/3/tv/popular?api_key=7f5b30559b3d85aec06d3d1a010f4a39&language=en-US' +export const ONCINEMA_URL = 'https://api.themoviedb.org/3/movie/upcoming?api_key=7f5b30559b3d85aec06d3d1a010f4a39&language=en-US&page=1' +export const TV_SERIES_DETAILS_URL = (id) => `https://api.themoviedb.org/3/tv/${id}?api_key=$7f5b30559b3d85aec06d3d1a010f4a39&language=en-US` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..e5b379bea --- /dev/null +++ b/package-lock.json @@ -0,0 +1,102 @@ +{ + "name": "project-movies", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "react-router-dom": "^6.9.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", + "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "peer": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-router": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", + "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", + "dependencies": { + "@remix-run/router": "1.4.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", + "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", + "dependencies": { + "@remix-run/router": "1.4.0", + "react-router": "6.9.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..9713d8dbe --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "react-router-dom": "^6.9.0" + } +}