diff --git a/package-lock.json b/package-lock.json index 7641a5e..11cc42c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bootstrap": "^5.3.3", "prop-types": "^15.8.1", "react": "^18.2.0", + "react-bootstrap": "^2.10.2", "react-dom": "^18.2.0" }, "devDependencies": { @@ -18,6 +20,17 @@ "process": "^0.11.10" } }, + "node_modules/@babel/runtime": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@lezer/common": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz", @@ -847,6 +860,68 @@ "@parcel/core": "^2.12.0" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.2.tgz", + "integrity": "sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.8.tgz", + "integrity": "sha512-6ndCv3oZ7r9vuP1Ok9KH55TM1/UkdBnP/fSraW0DFDMbPMzWKhVKeFAIEUCRCSdzayjZDcFYK6xbMlipN9dmMA==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@popperjs/core": "^2.11.6", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.4.9", + "@types/warning": "^3.0.0", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/@swc/core": { "version": "1.4.16", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.16.tgz", @@ -1051,6 +1126,14 @@ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true }, + "node_modules/@swc/helpers": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.10.tgz", + "integrity": "sha512-CU+RF9FySljn7HVSkkjiB84hWkvTaI3rtLvF433+jRSBL2hMu3zX5bGhHS8C80SM++h4xy8hBSnUHFQHmRXSBw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@swc/types": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz", @@ -1060,6 +1143,33 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "node_modules/@types/react": { + "version": "18.2.79", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", + "integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, "node_modules/abortcontroller-polyfill": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", @@ -1117,6 +1227,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -1232,6 +1360,11 @@ "node": ">=6.0" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -1260,6 +1393,19 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -1272,6 +1418,15 @@ "node": ">=0.10" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dotenv": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", @@ -1359,6 +1514,14 @@ "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "dev": true }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1634,6 +1797,18 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -1645,6 +1820,35 @@ "node": ">=0.10.0" } }, + "node_modules/react-bootstrap": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.2.tgz", + "integrity": "sha512-UvB7mRqQjivdZNxJNEA2yOQRB7L9N43nBnKc33K47+cH90/ujmnMwatTCwQLu83gLhrzAl8fsa6Lqig/KLghaA==", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.6.8", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -1662,6 +1866,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1674,6 +1898,11 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1768,6 +1997,25 @@ "node": ">=8.0" } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -1808,6 +2056,14 @@ "node": ">= 4" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/weak-lru-cache": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", diff --git a/package.json b/package.json index 48071b1..a1555e5 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,11 @@ "author": "", "license": "ISC", "dependencies": { + "bootstrap": "^5.3.3", + "prop-types": "^15.8.1", "react": "^18.2.0", - "react-dom": "^18.2.0", - "prop-types": "^15.8.1" + "react-bootstrap": "^2.10.2", + "react-dom": "^18.2.0" }, "devDependencies": { "@parcel/transformer-sass": "^2.12.0", diff --git a/src/components/login-view/login-view.jsx b/src/components/login-view/login-view.jsx index cffaddf..ae45577 100644 --- a/src/components/login-view/login-view.jsx +++ b/src/components/login-view/login-view.jsx @@ -1,8 +1,12 @@ import React, { useState } from "react"; +import Button from "react-bootstrap/Button"; +import Form from "react-bootstrap/Form"; +import Spinner from 'react-bootstrap/Spinner'; -export const LoginView = ({ onLoggedIn }) => { +export const LoginView = ({ onLoggedIn, onShowSignupForm }) => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [loading, setLoading] = useState(false); const handleSubmit = (event) => { // this prevents the default behavior of the form which is to @@ -14,6 +18,8 @@ export const LoginView = ({ onLoggedIn }) => { Password: password }; + setLoading(true); + fetch("https://cf-2-movie-api.onrender.com/login", { method: "POST", headers: { @@ -33,33 +39,58 @@ export const LoginView = ({ onLoggedIn }) => { } else { alert(`Login failed: ${data.error.message}`); } + setLoading(false); }) .catch(error => { + setLoading(false); alert("Something went wrong"); }); }; return ( -
- - - -
+
+
+
+

+ Welcome to + myFlix +

+
+ + setEmail(e.target.value)} + placeholder="Email" + required + /> + + + + setPassword(e.target.value)} + placeholder="Password" + required + /> + + + +
+ +
); }; diff --git a/src/components/main-view/main-view.jsx b/src/components/main-view/main-view.jsx index 9a5aa0c..6a2fd6e 100644 --- a/src/components/main-view/main-view.jsx +++ b/src/components/main-view/main-view.jsx @@ -1,8 +1,10 @@ -import { useState, useEffect } from "react"; +import React, { useState, useEffect } from "react"; import { MovieCard } from "../movie-card/movie-card"; import { MovieView } from "../movie-view/movie-view"; import { LoginView } from "../login-view/login-view"; import { SignupView } from "../signup-view/signup-view"; +import { SimilarMovies } from "../similar-movies/similar-movies"; +import { Row, Col, Button } from "react-bootstrap"; export const MainView = () => { const storedUser = JSON.parse(localStorage.getItem("user")); @@ -11,6 +13,7 @@ export const MainView = () => { const [token, setToken] = useState(storedToken? storedToken : null); const [movies, setMovies] = useState([]); const [selectedMovie, setSelectedMovie] = useState(null); + const [showSignupForm, setShowSignupForm] = useState(false); useEffect(() => { if (!token) { @@ -44,68 +47,68 @@ export const MainView = () => { }); }, [token]); - if (!user) { - return ( - <> - { - setUser(user); - setToken(token); - }} /> - or - - - ); - } - - if (selectedMovie) { - // Bonus Task 2: Similar Movies - const similarMovies = movies.filter(movie => { - const isSimilarMovie = movie.Genre.Name === selectedMovie.Genre.Name; - const isNotTheSelectedMovie = movie.id !== selectedMovie.id; - return isNotTheSelectedMovie && isSimilarMovie; - }); - - return ( - <> - setSelectedMovie(null)} /> -
-

Similar Movies

- {similarMovies.map(movie => ( - { + return ( + <> + {!user ? ( + + {!showSignupForm ? ( + + { + setUser(user); + setToken(token); + }} + onShowSignupForm={() => setShowSignupForm(true)} + /> + + ) : ( + + setShowSignupForm(false)}/> + + )} + + ) : selectedMovie ? ( + + + setSelectedMovie(null)} + /> + + { setSelectedMovie(newSelectedMovie); }} /> - ))} - - ); - } - - if (movies.length === 0) { - return
The list is empty!
; - } - - return ( -
- {movies.map((movie) => ( - { - setSelectedMovie(newSelectedMovie); - }} - /> - ))} - -
+
+ ) : movies.length === 0 ? ( + + The list is empty! + + ) : ( + + {movies.map((movie) => ( + + { + setSelectedMovie(newSelectedMovie); + }} + /> + + ))} + + + )} + ); }; diff --git a/src/components/movie-card/movie-card.jsx b/src/components/movie-card/movie-card.jsx index fd5107d..12c15fb 100644 --- a/src/components/movie-card/movie-card.jsx +++ b/src/components/movie-card/movie-card.jsx @@ -1,20 +1,49 @@ import PropTypes from "prop-types"; +import { Button, Card } from "react-bootstrap"; + +import "./movie-card.scss"; export const MovieCard = ({ movie, onMovieClick }) => { + // temporary randomize which movies are favorite + const isFavorite = Boolean(Math.round(Math.random())); return ( -
{ - onMovieClick(movie); - }} - > - {movie.Title} -
+ + + + {movie.Title} + {movie.Description} +
+ {movie.Genre.Name} + +
+
+
+ {isFavorite ? ( + + + + ) : ( + + + + )} +
+
); }; MovieCard.propTypes = { movie: PropTypes.shape({ - Title: PropTypes.string + ImagePath: PropTypes.string.isRequired, + Title: PropTypes.string, + Director: PropTypes.shape({ + Name: PropTypes.string.isRequired, + }), }).isRequired, onMovieClick: PropTypes.func.isRequired }; diff --git a/src/components/movie-card/movie-card.scss b/src/components/movie-card/movie-card.scss new file mode 100644 index 0000000..97cc263 --- /dev/null +++ b/src/components/movie-card/movie-card.scss @@ -0,0 +1,4 @@ +.card-img-top { + max-height: 165px; + object-fit: cover; +} diff --git a/src/components/movie-view/movie-view.jsx b/src/components/movie-view/movie-view.jsx index 46789a6..03e4cfa 100644 --- a/src/components/movie-view/movie-view.jsx +++ b/src/components/movie-view/movie-view.jsx @@ -1,43 +1,96 @@ +import React from "react"; import PropTypes from "prop-types"; +import { Row, Col, Button, ListGroup, Nav } from "react-bootstrap"; export const MovieView = ({ movie, onBackClick }) => { + // temporary randomize which movies are favorite + const isFavorite = Boolean(Math.round(Math.random())); + + const actors = movie.Actors.map(actor => actor.Name).join(', '); + return ( -
-
- -
-
- Title: - {movie.Title} -
-
- Description: - {movie.Description} -
-
- Genre: - {movie.Genre.Name} -
-
- Director: - {movie.Director.Name} -
-
- Release Year: - {movie.ReleaseYear} -
-
- MPA: - {movie.MPA}  - (Motion Picture Association rating) -
-
- Rating: - {movie.IMDb}  - (IMDb rating) -
- -
+ + +
+

{movie.Title}

+
+
+ {movie.ReleaseYear}• + {movie.MPA} +
+ {movie.Genre.Name} +
+
+ +

{movie.Description}

+ + + + Director + {movie.Director.Name} + + + Actors + + + + +
+
+
+ IMDb RATING +
+
+ + + +
+
{movie.IMDb} / 10
+
+
+ +
+ POPULARITY +
+
+ + + +
+ {/* placeholder: it will count how many times this movie has been favored */} +
10
+
+
+
+ +
+
+
+ + + +
+ +
+ {isFavorite ? ( + + + + ) : ( + + + + )} +
+
+ +
); }; diff --git a/src/components/signup-view/signup-view.jsx b/src/components/signup-view/signup-view.jsx index aacbf0a..3a82c71 100644 --- a/src/components/signup-view/signup-view.jsx +++ b/src/components/signup-view/signup-view.jsx @@ -1,10 +1,14 @@ import React, { useState } from "react"; +import Button from "react-bootstrap/Button"; +import Form from "react-bootstrap/Form"; +import Spinner from 'react-bootstrap/Spinner'; -export const SignupView = () => { +export const SignupView = ({ onShowLoginForm }) => { const [name, setName] = useState(""); const [password, setPassword] = useState(""); const [email, setEmail] = useState(""); const [birthday, setBirthday] = useState(""); + const [loading, setLoading] = useState(false); const handleSubmit = (event) => { // this prevents the default behavior of the form which is to @@ -18,6 +22,8 @@ export const SignupView = () => { Birthday: birthday }; + setLoading(true); + fetch("https://cf-2-movie-api.onrender.com/users", { method: "POST", body: JSON.stringify(data), @@ -35,52 +41,78 @@ export const SignupView = () => { } else { alert(`Signup failed: ${data.error.message}`); } + setLoading(false); }) .catch(error => { + setLoading(false); alert("Something went wrong"); }); }; return ( -
- - - - - -
+
+
+
+

+ Welcome to + myFlix +

+
+ + setEmail(e.target.value)} + placeholder="Email" + required + /> + + + + setPassword(e.target.value)} + placeholder="Password" + required + /> + + + + setName(e.target.value)} + placeholder="Name" + required + minLength="5" + /> + + + + setBirthday(e.target.value)} + placeholder="Birthday" + /> + + + +
+ +
); }; diff --git a/src/components/similar-movies/similar-movies.jsx b/src/components/similar-movies/similar-movies.jsx new file mode 100644 index 0000000..2835d69 --- /dev/null +++ b/src/components/similar-movies/similar-movies.jsx @@ -0,0 +1,44 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { MovieCard } from "../movie-card/movie-card"; +import { Row, Col } from "react-bootstrap"; + +export const SimilarMovies = ({ selectedMovie, movies, onSimilarMovieClick }) => { + // Bonus Task 2: Similar Movies + const similarMovies = movies.filter(movie => { + const isSimilarMovie = movie.Genre.Name === selectedMovie.Genre.Name; + const isNotTheSelectedMovie = movie.id !== selectedMovie.id; + return isNotTheSelectedMovie && isSimilarMovie; + }); + + return ( + <> + {similarMovies.length > 0 && ( +
+

Similar Movies

+ + {similarMovies.map(movie => ( + + onSimilarMovieClick(movie)} + /> + + ))} + +
+ )} + + ); +}; + +MovieCard.propTypes = { + movie: PropTypes.shape({ + ImagePath: PropTypes.string.isRequired, + Title: PropTypes.string, + Director: PropTypes.shape({ + Name: PropTypes.string.isRequired, + }), + }).isRequired, + onMovieClick: PropTypes.func.isRequired +}; diff --git a/src/index.jsx b/src/index.jsx index 8a086f1..6d81700 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,10 +1,15 @@ import { createRoot } from 'react-dom/client'; import { MainView } from "./components/main-view/main-view"; +import Container from 'react-bootstrap/Container'; import "./index.scss"; const App = () => { - return ; + return ( + + + + ); }; const container = document.querySelector("#root"); diff --git a/src/index.scss b/src/index.scss index 88d2159..e2fd94c 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,5 +1,33 @@ -$color: steelblue; +// $primary: SeaGreen; +// $body-bg: Honeydew; -.my-flix { - color: $color; +html, +body { + height: 100%; +} + +#root { + height: 100%; +} + +$enable-negative-margins: true; + +@import '~bootstrap/scss/bootstrap.scss'; + +.fs-7 { + font-size: 0.8rem !important; +} + +ul.dot-decorator { + margin: 0; + padding: 0; + list-style-type: none; +} + +ul.dot-decorator > li:not(:last-child)::after { + content: "ยท"; +} + +ul.dot-decorator > li > a { + text-decoration: none; }