├── src
├── App.css
├── Components
│ ├── TopRatedMovies
│ │ ├── TopRatedMovies.css
│ │ └── TopRatedMovies.jsx
│ ├── RecommendedMovies
│ │ ├── RecommendedMovies.css
│ │ └── RecommendedMovies.jsx
│ ├── GenrePage
│ │ ├── GenrePage.css
│ │ └── GenrePage.jsx
│ ├── MoviesPage
│ │ ├── MoviesPage.css
│ │ └── MoviesPage.jsx
│ ├── Pagination
│ │ ├── Pagination.css
│ │ └── Pagination.jsx
│ ├── TrailerComponent
│ │ └── TrailerComponent.jsx
│ ├── Render
│ │ ├── SingleCard.jsx
│ │ ├── Stars.jsx
│ │ ├── MoviesWatched.jsx
│ │ └── CardsRow.jsx
│ ├── NotFound
│ │ └── NotFound.jsx
│ ├── ErrorPage
│ │ └── ErrorPage.jsx
│ ├── WatchList
│ │ └── WatchList.jsx
│ ├── SearchComponent
│ │ └── SearchPage.jsx
│ ├── MainComponent
│ │ └── MainComponent.jsx
│ ├── HeaderComponent
│ │ └── HeaderComponent.jsx
│ ├── ManageUsers
│ │ └── ManageUsers.jsx
│ ├── LoginComponent
│ │ └── LoginComponent.jsx
│ ├── RegisterComponent
│ │ └── RegisterComponent.jsx
│ ├── ProfilePage
│ │ └── ProfilePage.jsx
│ └── MovieDetail
│ │ └── MovieDetail.jsx
├── Shared
│ ├── js
│ │ ├── r42.js
│ │ ├── ProtectedRoute.js
│ │ └── user-context.js
│ └── images
│ │ ├── 404.svg
│ │ ├── Error2.svg
│ │ ├── profile.svg
│ │ ├── login.svg
│ │ └── Register.svg
├── setupTests.js
├── App.test.js
├── index.css
├── reportWebVitals.js
├── index.js
├── App.js
├── Utilities
│ └── apiCalls.js
├── logo.svg
└── App.scss
├── public
├── _redirects
├── movie.ico
├── favicon.ico
├── logo192.png
├── logo512.png
├── robots.txt
├── manifest.json
└── index.html
├── .env-cmdrc.json
├── .gitignore
├── .vscode
└── settings.json
├── README.md
└── package.json
/src/App.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
3 |
--------------------------------------------------------------------------------
/public/movie.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zorgonide/movieflix/HEAD/public/movie.ico
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zorgonide/movieflix/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zorgonide/movieflix/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zorgonide/movieflix/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/Components/TopRatedMovies/TopRatedMovies.css:
--------------------------------------------------------------------------------
1 | .heading {
2 | display: flex;
3 | justify-content: space-between;
4 | align-items: center;
5 | }
--------------------------------------------------------------------------------
/src/Components/RecommendedMovies/RecommendedMovies.css:
--------------------------------------------------------------------------------
1 | .heading {
2 | display: flex;
3 | justify-content: space-between;
4 | align-items: center;
5 | }
--------------------------------------------------------------------------------
/src/Shared/js/r42.js:
--------------------------------------------------------------------------------
1 | export const trackEvent = (dataElement) => {
2 | // window._st('resetTags');
3 | // window._st('addTagProperties', dataElement);
4 | // window._st('loadTags');
5 | };
6 |
--------------------------------------------------------------------------------
/src/Components/GenrePage/GenrePage.css:
--------------------------------------------------------------------------------
1 | .genreList {
2 | display: block;
3 | }
4 | .seeMore {
5 | cursor: pointer;
6 | }
7 | .btn-pill {
8 | border-radius: 15px;
9 | }
10 | .listOfGenres {
11 | display: block;
12 | }
--------------------------------------------------------------------------------
/src/Components/MoviesPage/MoviesPage.css:
--------------------------------------------------------------------------------
1 | /* .carouselSlider {
2 | display: flex;
3 | }
4 | .heading {
5 | display: flex;
6 | justify-content: space-between;
7 | }
8 | .heading a {
9 | display: flex;
10 | align-items: end;
11 | }
12 | */
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/.env-cmdrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "development": {
3 | "REACT_APP_BASE_TOKEN": "e73306b88806162f60257a321d9e2b2c",
4 | "REACT_APP_BASE_URL": "https://api.themoviedb.org"
5 | },
6 | "production": {
7 | "REACT_APP_BASE_TOKEN": "e73306b88806162f60257a321d9e2b2c",
8 | "REACT_APP_BASE_URL": "https://api.themoviedb.org"
9 | }
10 | }
--------------------------------------------------------------------------------
/src/Components/Pagination/Pagination.css:
--------------------------------------------------------------------------------
1 | .pagination {
2 | display: flex;
3 | /* align-items: center; */
4 | }
5 | .pagination .btn {
6 | color: white;
7 | font-size: large;
8 | font-weight: 800;
9 | /* border: 1px #0d6efd solid;
10 | height: 30px; */
11 | }
12 | .heading p{
13 | margin: auto;
14 | }
15 | .pages {
16 | color: white
17 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | build.zip
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import reportWebVitals from "./reportWebVitals";
6 | import "font-awesome/css/font-awesome.css";
7 |
8 | ReactDOM.render(, document.getElementById("root"));
9 |
10 | // If you want to start measuring performance in your app, pass a function
11 | // to log results (for example: reportWebVitals(console.log))
12 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
13 | reportWebVitals();
14 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import 'bootstrap/dist/css/bootstrap.min.css';
2 | import './App.scss';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import MainComponent from './Components/MainComponent/MainComponent';
5 | import { UserProvider } from './Shared/js/user-context';
6 |
7 | function App() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | export default App;
20 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "movie.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/Shared/js/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Navigate, Outlet } from "react-router-dom";
3 | import Header from "../../Components/HeaderComponent/HeaderComponent";
4 | import { useUser } from "./user-context";
5 |
6 | function ProtectedRoute(props) {
7 | const {
8 | state: { user },
9 | } = useUser();
10 | const { dispatch } = useUser();
11 | let verified = localStorage.getItem("user") ? true : false;
12 | if (verified && !user) {
13 | dispatch({ type: "login", user: JSON.parse(localStorage.getItem("user")) });
14 | }
15 | return verified ? (
16 | <>
17 |
18 |
19 | >
20 | ) : (
21 |
22 | );
23 | }
24 |
25 | export default ProtectedRoute;
26 |
--------------------------------------------------------------------------------
/src/Components/TrailerComponent/TrailerComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const TrailerFrame = ({ trailerData }) => {
4 | if (!trailerData) {
5 | return
No trailer...
;
6 | }
7 | const embedUrl = `https://www.youtube.com/embed/${trailerData.key}`;
8 | return (
9 |
10 |
19 |
20 | );
21 | };
22 |
23 | export default TrailerFrame;
24 |
--------------------------------------------------------------------------------
/src/Components/Pagination/Pagination.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./Pagination.css";
3 | const Pagination = ({ paginate, totalPages, currentPage }) => {
4 | const pageNumbers = [];
5 |
6 | for (let i = 1; i <= totalPages; i++) {
7 | pageNumbers.push(i);
8 | }
9 | return (
10 |
11 |
12 |
{`${currentPage}/${totalPages}`}
13 |
14 |
21 |
28 |
29 | );
30 | };
31 |
32 | export default Pagination;
33 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "workbench.colorCustomizations": {
3 | "activityBar.activeBackground": "#5a8dca",
4 | "activityBar.background": "#5a8dca",
5 | "activityBar.foreground": "#15202b",
6 | "activityBar.inactiveForeground": "#15202b99",
7 | "activityBarBadge.background": "#912f5b",
8 | "activityBarBadge.foreground": "#e7e7e7",
9 | "commandCenter.border": "#e7e7e799",
10 | "sash.hoverBorder": "#5a8dca",
11 | "statusBar.background": "#3b73b6",
12 | "statusBar.foreground": "#e7e7e7",
13 | "statusBarItem.hoverBackground": "#5a8dca",
14 | "statusBarItem.remoteBackground": "#3b73b6",
15 | "statusBarItem.remoteForeground": "#e7e7e7",
16 | "titleBar.activeBackground": "#3b73b6",
17 | "titleBar.activeForeground": "#e7e7e7",
18 | "titleBar.inactiveBackground": "#3b73b699",
19 | "titleBar.inactiveForeground": "#e7e7e799"
20 | },
21 | "peacock.color": "#3b73b6"
22 | }
--------------------------------------------------------------------------------
/src/Components/Render/SingleCard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { HideUntilLoaded } from "react-animation";
3 | import { Rings } from "react-loader-spinner";
4 |
5 | function SingleCard({ movie }) {
6 | return (
7 |
8 |
9 |
}
13 | >
14 |
19 |
20 | {movie.imdbRating}
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | export default SingleCard;
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MovieFlix
2 | ### [Project demo link](https://zen-ride-bc37b1.netlify.app/)
3 | 
4 | 
5 | 
6 | 
7 | 
8 |
9 | This application suggests movies based on genres selected using The Movie DB API. You can log in, review movies with comments, and interact with others on the platform. Makes browsing through Netflix a tiny bit easier.
10 |
11 |
12 | # Setup
13 |
14 | Clone this repo –
15 |
16 | git clone https://github.com/zorgonide/movieflix.git
17 |
18 | Move into project directory **movieflix**
19 |
20 | Run command –
21 |
22 | npm install
23 |
24 | This will install the node_modules folder
25 |
26 | Run command –
27 |
28 | npm run start-dev
29 |
30 | To run project on localhost:3000
31 |
32 |
--------------------------------------------------------------------------------
/src/Shared/js/user-context.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const UserContext = React.createContext();
4 |
5 | function userReducer(state, action) {
6 | switch (action.type) {
7 | case "login": {
8 | localStorage.setItem("user", JSON.stringify(action.user));
9 | return {
10 | user: action.user,
11 | loggedIn: true,
12 | genres: action.user.genres,
13 | };
14 | }
15 | case "logout": {
16 | localStorage.removeItem("user");
17 | return { user: {}, loggedIn: false, genres: {} };
18 | }
19 | case "genres": {
20 | return {
21 | user: { ...state.user, Genres: action.genres },
22 | loggedIn: true,
23 | Genres: action.genres,
24 | };
25 | }
26 | default: {
27 | throw new Error(`Unhandled action type: ${action.type}`);
28 | }
29 | }
30 | }
31 |
32 | function UserProvider({ children }) {
33 | const [state, dispatch] = React.useReducer(userReducer, {
34 | user: JSON.parse(localStorage.getItem("user")),
35 | });
36 | const value = { state, dispatch };
37 | return {children};
38 | }
39 |
40 | function useUser() {
41 | const context = React.useContext(UserContext);
42 | if (context === undefined) {
43 | throw new Error("useUser must be used within a UserProvider");
44 | }
45 | return context;
46 | }
47 | export { UserProvider, useUser };
48 |
--------------------------------------------------------------------------------
/src/Components/NotFound/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link } from "react-router-dom";
3 | import NotFound404 from "../../Shared/images/404.svg";
4 |
5 | export class NotFound extends Component {
6 | render() {
7 | return (
8 |
12 |
13 |
14 |
15 |

22 |
Oops!
23 |
404 Not Found
24 |
25 | Sorry, an error has occurred, Requested page was not found!
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 | }
42 |
43 | export default NotFound;
44 |
--------------------------------------------------------------------------------
/src/Components/ErrorPage/ErrorPage.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Pic from "../../Shared/images/Error2.svg";
3 | // import Loader from 'react-loader-spinner'
4 | import { HideUntilLoaded } from "react-animation";
5 |
6 | //Standard error component
7 |
8 | function Error({ error }) {
9 | return (
10 | <>
11 | {/*
12 |
13 |
14 |
15 |
}
19 | >
20 |
21 |
22 |
23 |
Error
24 |
{error}
25 |
26 |
27 |
28 |
29 |
*/}
30 | >
31 | );
32 | }
33 |
34 | export default Error;
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hevo-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@adobe/adobe-client-data-layer": "^2.0.2",
7 | "@testing-library/jest-dom": "^5.14.1",
8 | "@testing-library/react": "^12.0.0",
9 | "@testing-library/user-event": "^13.2.1",
10 | "axios": "^0.24.0",
11 | "bootstrap": "^5.1.3",
12 | "dayjs": "^1.10.7",
13 | "env-cmd": "^10.1.0",
14 | "firebase": "^9.6.3",
15 | "font-awesome": "^4.7.0",
16 | "formik": "^2.2.9",
17 | "react": "^16.8.0",
18 | "react-animation": "^1.2.2",
19 | "react-comments-section": "^2.0.10",
20 | "react-dom": "^16.8.0",
21 | "react-loader-spinner": "^5.0.10",
22 | "react-router-dom": "^6.2.1",
23 | "react-scripts": "5.0.0",
24 | "reactstrap": "^9.0.1",
25 | "sass": "^1.58.3",
26 | "sweetalert2": "^11.7.3",
27 | "web-vitals": "^2.1.0",
28 | "yup": "^0.32.11"
29 | },
30 | "scripts": {
31 | "start-dev": "env-cmd -e development react-scripts start",
32 | "build-dev": "env-cmd -e development react-scripts build",
33 | "start-prod": "env-cmd -e production react-scripts start",
34 | "build-prod": "env-cmd -e production react-scripts build",
35 | "test": "react-scripts test",
36 | "eject": "react-scripts eject"
37 | },
38 | "eslintConfig": {
39 | "extends": [
40 | "react-app",
41 | "react-app/jest"
42 | ]
43 | },
44 | "browserslist": {
45 | "production": [
46 | ">0.2%",
47 | "not dead",
48 | "not op_mini all"
49 | ],
50 | "development": [
51 | "last 1 chrome version",
52 | "last 1 firefox version",
53 | "last 1 safari version"
54 | ]
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Components/Render/Stars.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import "font-awesome/css/font-awesome.min.css";
3 | import { postBackend } from "../../Utilities/apiCalls";
4 |
5 | const StarRating = ({ userRating, onChange, Title, Movie_ID }) => {
6 | const [hoverRating, setHoverRating] = useState(0);
7 |
8 | const handleRatingClick = (rating) => {
9 | postBackend({
10 | url: "api/rating/" + Movie_ID,
11 | data: {
12 | userRating: rating,
13 | },
14 | })
15 | .then(() => {
16 | if (onChange) {
17 | onChange(rating);
18 | }
19 | })
20 | .then(() => {
21 | window.adobeDataLayer = window.adobeDataLayer || [];
22 |
23 | window.adobeDataLayer.push({
24 | event: "rating",
25 | rating: {
26 | id: Movie_ID,
27 | title: Title,
28 | rating: rating,
29 | },
30 | });
31 | });
32 | };
33 |
34 | const handleRatingHover = (rating) => {
35 | setHoverRating(rating);
36 | };
37 |
38 | const stars = [];
39 | const maxRating = 5;
40 |
41 | for (let i = 1; i <= maxRating; i++) {
42 | const filled = i <= (userRating || hoverRating);
43 |
44 | stars.push(
45 | handleRatingHover(i)}
55 | onMouseLeave={() => handleRatingHover(0)}
56 | onClick={() => handleRatingClick(i)}
57 | />
58 | );
59 | }
60 |
61 | return {stars}
;
62 | };
63 |
64 | export default StarRating;
65 |
--------------------------------------------------------------------------------
/src/Components/WatchList/WatchList.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Error from '../ErrorPage/ErrorPage';
3 | import { Rings } from 'react-loader-spinner';
4 | import { getBackend, postBackend } from '../../Utilities/apiCalls';
5 | import MoviesWatched from '../Render/MoviesWatched';
6 |
7 | function WatchList() {
8 | const [error, setError] = useState(null);
9 | const [isLoaded, setIsLoaded] = useState(false);
10 | const [moviesWatched, setMoviesWatched] = useState([]);
11 | const MoviesWatchedFunction = () => {
12 | return getBackend({
13 | url: 'api/watchlist',
14 | })
15 | .then((res) => res.data)
16 | .then((res) => {
17 | setMoviesWatched(res.data.map((movie) => movie.movie));
18 | });
19 | };
20 | useEffect(() => {
21 | Promise.all([MoviesWatchedFunction()])
22 | .then(() => {
23 | setIsLoaded(true);
24 | })
25 | .catch((err) => {
26 | setError(err);
27 | });
28 | }, []);
29 | if (error) {
30 | return ;
31 | } else if (!isLoaded) {
32 | return (
33 |
42 |
43 |
44 | );
45 | } else
46 | return (
47 |
57 | );
58 | }
59 |
60 | export default WatchList;
61 |
--------------------------------------------------------------------------------
/src/Utilities/apiCalls.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | // const backend = "http://127.0.0.1:3000/";
3 | // const backend = "https://guanxinyumovieflix.me/";
4 | const backend = "https://movieflix-wpa8.onrender.com/";
5 |
6 | let cache = {};
7 | export const fget = async ({ url }) => {
8 | if (cache[url]) {
9 | return cache[url];
10 | }
11 | const res = await axios.get(process.env.REACT_APP_BASE_URL + `${url}`, {});
12 | cache[url] = res;
13 | return res;
14 | };
15 |
16 | export const fpatch = async ({ url, data }) => {
17 | if (cache[url]) {
18 | return cache[url];
19 | }
20 | const res = await axios.patch(
21 | process.env.REACT_APP_BASE_URL + `${url}`,
22 | data,
23 | {}
24 | );
25 | cache[url] = res;
26 |
27 | return res;
28 | };
29 |
30 | export const fpost = async ({ url, data }) => {
31 | if (cache[url]) {
32 | return cache[url];
33 | }
34 | const res = await axios.post(
35 | process.env.REACT_APP_BASE_URL + `${url}`,
36 | data,
37 | {}
38 | );
39 | cache[url] = res;
40 |
41 | return res;
42 | };
43 |
44 | export const fdelete = async ({ url }) => {
45 | const res = await axios.delete(process.env.REACT_APP_BASE_URL + `${url}`, {});
46 | return res;
47 | };
48 | export const getBackend = async ({ url }) => {
49 | try {
50 | const res = await axios.get(backend + `${url}`, {
51 | headers: {
52 | Authorization: `Bearer ${localStorage.getItem("token")}`,
53 | },
54 | });
55 | return res;
56 | } catch (err) {
57 | return err;
58 | }
59 | };
60 |
61 | export const patchBackend = async ({ url, data }) => {
62 | try {
63 | const res = await axios.put(backend + `${url}`, data, {
64 | headers: {
65 | Authorization: `Bearer ${localStorage.getItem("token")}`,
66 | },
67 | });
68 | return res;
69 | } catch (err) {
70 | return err;
71 | }
72 | };
73 |
74 | export const postBackend = async ({ url, data }) => {
75 | try {
76 | let res = await axios.post(backend + `${url}`, data, {
77 | headers: {
78 | Authorization: `Bearer ${localStorage.getItem("token")}`,
79 | },
80 | });
81 | return res;
82 | } catch (err) {
83 | return err;
84 | }
85 | };
86 |
87 | export const deleteBackend = async ({ url }) => {
88 | try {
89 | const res = await axios.delete(backend + `${url}`, {
90 | headers: {
91 | Authorization: `Bearer ${localStorage.getItem("token")}`,
92 | },
93 | });
94 | return res;
95 | } catch (err) {
96 | return err;
97 | }
98 | };
99 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Components/Render/MoviesWatched.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 | function MoviesWatched({ movies, title }) {
4 | let navigate = useNavigate();
5 | let moviesArray = movies;
6 | return (
7 |
8 |
9 |
{title}
10 |
11 |
12 | {moviesArray.length ? (
13 | moviesArray.map((ele) => {
14 | return (
15 | -
16 |
18 | navigate(`/movie/${ele.id}`, {
19 | state: { movie: ele },
20 | })
21 | }
22 | >
23 |
24 |

31 |
32 |
33 | {ele.title}
34 |
35 |
36 | IMDB Rating:{' '}
37 | {ele.imdbRating}
38 |
39 |
40 |
41 |
42 |
43 | );
44 | })
45 | ) : (
46 | No movies :(
47 | )}
48 |
49 |
50 |
51 | );
52 | }
53 |
54 | export default MoviesWatched;
55 |
--------------------------------------------------------------------------------
/src/Components/SearchComponent/SearchPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 | import { fget } from '../../Utilities/apiCalls';
4 | import MoviesWatched from '../Render/MoviesWatched';
5 |
6 | function SearchPage() {
7 | const [searchResults, setSearchResults] = useState([]);
8 | const location = useLocation();
9 |
10 | useEffect(() => {
11 | const searchParams = new URLSearchParams(location.search);
12 | const searchTerm = searchParams.get('q');
13 |
14 | fget({
15 | url: `/3/search/movie?api_key=${process.env.REACT_APP_BASE_TOKEN}&query=${searchTerm}&include_adult=false&language=en-US&page=1`,
16 | })
17 | .then((response) => response.data)
18 | .then((data) => {
19 | let refined = data.results.map((ele) => {
20 | return {
21 | id: ele.id,
22 | poster:
23 | 'https://image.tmdb.org/t/p/original' +
24 | ele.poster_path,
25 | imdbRating: Math.round(ele.vote_average * 10) / 10,
26 | title: ele.title,
27 | release_date: ele.release_date,
28 | overview: ele.overview,
29 | poster_path: ele.poster_path,
30 | backdrop_path: ele.backdrop_path,
31 | };
32 | });
33 | // remove movies with no poster or imdbRating 0
34 | refined = refined.filter(
35 | (ele) =>
36 | ele.poster !==
37 | 'https://image.tmdb.org/t/p/originalnull' &&
38 | ele.imdbRating !== 0
39 | );
40 |
41 | setSearchResults(refined);
42 | window.adobeDataLayer.push({
43 | event: 'search',
44 | search: searchTerm,
45 | results: refined.length,
46 | });
47 | })
48 | .catch((error) => console.error(error));
49 | }, [location.search, searchResults.length]);
50 |
51 | return (
52 |
62 | );
63 | }
64 |
65 | export default SearchPage;
66 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
12 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
38 |
39 |
48 | MovieFlix
49 |
50 |
51 |
52 |
53 |
63 |
64 |
--------------------------------------------------------------------------------
/src/Components/TopRatedMovies/TopRatedMovies.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Rings } from "react-loader-spinner";
3 | import { fget } from "../../Utilities/apiCalls";
4 | import Error from "../ErrorPage/ErrorPage";
5 | import "./TopRatedMovies.css";
6 | import CardsRow from "../Render/CardsRow";
7 | import Pagination from "../Pagination/Pagination";
8 |
9 | function TopRatedMovies() {
10 | const [error, setError] = useState(null);
11 | const [isLoaded, setIsLoaded] = useState(false);
12 | const [topRated, setTopRated] = useState([]);
13 | const [currentPage, setCurrentPage] = useState(1);
14 | const [totalPages, setTotalPages] = useState(null);
15 | const paginate = (pageNumber) => {
16 | setCurrentPage(pageNumber);
17 | getMovies(pageNumber);
18 | };
19 | const getMovies = (page = 1) => {
20 | fget({
21 | url: `/3/movie/top_rated?api_key=${process.env.REACT_APP_BASE_TOKEN}&language=en-US&page=${page}`,
22 | })
23 | .then((res) => res.data)
24 | .then(
25 | (result) => {
26 | setTopRated(result.results.slice(0, -2));
27 | setTotalPages(result.total_pages);
28 | setIsLoaded(true);
29 | },
30 | (error) => {
31 | setIsLoaded(true);
32 | setError(error);
33 | }
34 | );
35 | };
36 | useEffect(() => {
37 | getMovies();
38 | }, []);
39 | if (error) {
40 | return ;
41 | } else if (!isLoaded) {
42 | return (
43 |
52 |
53 |
54 | );
55 | } else
56 | return (
57 |
58 |
59 |
60 |
61 |
Top Rated Movies
62 |
63 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | );
75 | }
76 |
77 | export default TopRatedMovies;
78 |
--------------------------------------------------------------------------------
/src/Components/Render/CardsRow.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { HideUntilLoaded } from 'react-animation';
3 | import { Rings } from 'react-loader-spinner';
4 | import * as dayjs from 'dayjs';
5 | import { useNavigate } from 'react-router-dom';
6 |
7 | const MovieCard = ({ ele }) => {
8 | return (
9 |
10 |
(
16 |
17 | )}
18 | >
19 |
26 |
27 |
28 |
37 | {Math.round(ele.vote_average * 10) / 10}
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | const CardsRow = ({ movies }) => {
46 | let navigate = useNavigate();
47 | return (
48 |
49 | {movies.map((ele) => {
50 | return (
51 |
55 | navigate(`/movie/${ele.id}`, {
56 | state: { movie: ele },
57 | })
58 | }
59 | >
60 |
61 |
62 |
66 | {ele.title.length < 30
67 | ? ele.title
68 | : ele.title.slice(0, 30) + '...'}
69 |
70 |
71 | {dayjs(ele.release_date).format('YYYY')}
72 |
73 |
74 |
75 | );
76 | })}
77 |
78 | );
79 | };
80 |
81 | export default CardsRow;
82 |
--------------------------------------------------------------------------------
/src/Components/RecommendedMovies/RecommendedMovies.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Rings } from "react-loader-spinner";
3 | import { useNavigate } from "react-router-dom";
4 | import { fget } from "../../Utilities/apiCalls";
5 | import Error from "../ErrorPage/ErrorPage";
6 | import "./RecommendedMovies.css";
7 | import CardsRow from "../Render/CardsRow";
8 | import Pagination from "../Pagination/Pagination";
9 | import { useUser } from "../../Shared/js/user-context";
10 |
11 | function RecommendedMovies() {
12 | const [error, setError] = useState(null);
13 | const [isLoaded, setIsLoaded] = useState(false);
14 | const [recommended, setRecommended] = useState([]);
15 | const [currentPage, setCurrentPage] = useState(1);
16 | const [totalPages, setTotalPages] = useState(null);
17 | let navigate = useNavigate();
18 | const {
19 | state: { user },
20 | } = useUser();
21 | const genres = Array.isArray(user.genres)
22 | ? user.genres.join(",")
23 | : user.genres;
24 |
25 | const paginate = (pageNumber) => {
26 | setCurrentPage(pageNumber);
27 | getMovies(pageNumber);
28 | };
29 |
30 | const getMovies = (page = 1) => {
31 | fget({
32 | url: `/3/discover/movie?api_key=${process.env.REACT_APP_BASE_TOKEN}&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=${page}&with_watch_monetization_types=flatrate&with_genres=${genres}`,
33 | })
34 | .then((res) => res.data)
35 | .then(
36 | (result) => {
37 | setRecommended(result.results.slice(0, -2));
38 | setTotalPages(result.total_pages);
39 | setIsLoaded(true);
40 | },
41 | (error) => {
42 | setIsLoaded(true);
43 | setError(error);
44 | }
45 | );
46 | };
47 |
48 | useEffect(() => {
49 | if (!genres) {
50 | navigate("/genres");
51 | return;
52 | }
53 | getMovies();
54 | }, []);
55 | if (error) {
56 | return ;
57 | } else if (!isLoaded) {
58 | return (
59 |
68 |
69 |
70 | );
71 | } else
72 | return (
73 |
74 |
75 |
76 |
77 |
Recommended Movies
78 |
79 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | );
91 | }
92 |
93 | export default RecommendedMovies;
94 |
--------------------------------------------------------------------------------
/src/Components/MainComponent/MainComponent.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Route, Routes, useLocation } from 'react-router-dom';
3 | import ProtectedRoute from '../../Shared/js/ProtectedRoute';
4 | import GenrePage from '../GenrePage/GenrePage';
5 | import LoginComponent from '../LoginComponent/LoginComponent';
6 | import ManageUsers from '../ManageUsers/ManageUsers';
7 | import MovieDetail from '../MovieDetail/MovieDetail';
8 | import MoviesPage from '../MoviesPage/MoviesPage';
9 | import NotFound from '../NotFound/NotFound';
10 | import ProfilePage from '../ProfilePage/ProfilePage';
11 | import RecommendedMovies from '../RecommendedMovies/RecommendedMovies';
12 | import RegisterComponent from '../RegisterComponent/RegisterComponent';
13 | import TopRatedMovies from '../TopRatedMovies/TopRatedMovies';
14 | import WatchList from '../WatchList/WatchList';
15 | import { trackEvent } from '../../Shared/js/r42';
16 | import SearchPage from '../SearchComponent/SearchPage';
17 |
18 | export const GenreContext = React.createContext();
19 |
20 | function MainComponent(props) {
21 | const [genres, setGenres] = useState(null);
22 | const location = useLocation();
23 |
24 | // Function to push data to Adobe Data Layer
25 | const pushToAdobeDataLayer = (pageName) => {
26 | window.adobeDataLayer = window.adobeDataLayer || [];
27 | window.adobeDataLayer.push({
28 | event: 'page-view',
29 | page: pageName,
30 | });
31 | };
32 | const pushToRelay42 = (pageName) => {
33 | // let page = pageName.slice(1);
34 | // if (/^\/movie\/\d+$/.test(pageName)) {
35 | // page = 'movie';
36 | // }
37 | // if (page === '') {
38 | // page = 'main';
39 | // }
40 | // window._st('resetTags');
41 | // window._st('setPageStructure', page);
42 | // trackEvent();
43 | };
44 |
45 | // Call pushToAdobeDataLayer whenever the route changes
46 | React.useEffect(() => {
47 | pushToAdobeDataLayer(location.pathname);
48 | pushToRelay42(location.pathname);
49 | }, [location.pathname]);
50 | return (
51 | <>
52 |
53 | } />
54 | } />
55 | }>
56 | }
59 | />
60 |
65 |
66 |
67 | }
68 | />
69 |
74 |
75 |
76 | }
77 | />
78 | } />
79 | }
83 | />
84 | } />
85 | } />
86 | } />
87 | } />
88 |
89 | } />
90 |
91 | >
92 | );
93 | }
94 |
95 | export default MainComponent;
96 |
--------------------------------------------------------------------------------
/src/Components/HeaderComponent/HeaderComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useState } from 'react';
3 | import { Link, useNavigate } from 'react-router-dom';
4 | import { useUser } from '../../Shared/js/user-context';
5 | function Header() {
6 | const [isNavCollapsed, setIsNavCollapsed] = useState(true);
7 | const [searchTerm, setSearchTerm] = useState('');
8 | const handleNavCollapse = () => setIsNavCollapsed(!isNavCollapsed);
9 |
10 | const { dispatch } = useUser();
11 | const {
12 | state: { user },
13 | } = useUser();
14 | let navigate = useNavigate();
15 | return (
16 |
102 | );
103 | }
104 |
105 | export default Header;
106 |
--------------------------------------------------------------------------------
/src/Components/ManageUsers/ManageUsers.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import Swal from "sweetalert2";
3 | import { deleteBackend, getBackend } from "../../Utilities/apiCalls";
4 |
5 | function ManageUsers() {
6 | const [error, setError] = useState(null);
7 | const [isLoaded, setIsLoaded] = useState(false);
8 | const [totalItems, setTotalItems] = useState([]);
9 | const [search, setSearch] = useState("");
10 | useEffect(() => {
11 | fetchUsers();
12 | }, []);
13 | const deleteUser = (id) => {
14 | Swal.fire({
15 | confirmButtonColor: "#e31c5f",
16 | title: "Confirm Deletion",
17 | text: `Do you delete user #${id}`,
18 | icon: "question",
19 | confirmButtonText: "Yes",
20 | showCancelButton: true,
21 | }).then((res) => {
22 | if (res.isConfirmed) {
23 | deleteBackend({
24 | url: `user/`,
25 | data: {
26 | id: id,
27 | },
28 | }).then(() => {
29 | Swal.fire({
30 | confirmButtonColor: "#e31c5f",
31 | title: "User deleted",
32 | icon: "success",
33 | confirmButtonText: "Dismiss",
34 | });
35 | fetchUsers();
36 | });
37 | }
38 | });
39 | };
40 | const fetchUsers = () => {
41 | getBackend({
42 | url: `users/`,
43 | data: {},
44 | })
45 | .then((res) => res.data)
46 | .then(
47 | (result) => {
48 | setTotalItems(result);
49 | setIsLoaded(true);
50 | },
51 | (error) => {
52 | setIsLoaded(true);
53 | setError(error);
54 | }
55 | );
56 | };
57 | const Headers = () => {
58 | return (
59 |
60 |
63 |
66 |
69 |
72 |
75 |
78 |
79 | );
80 | };
81 | const RenderList = () => {
82 | return (
83 | <>
84 | {filteredUsers.map((element, index) => {
85 | return (
86 | navigate(`/action/${element.id}`)}
90 | >
91 |
92 |
93 | {element.id}
94 |
95 |
96 |
97 |
{element.firstName}
98 |
99 |
100 |
{element.lastName}
101 |
102 |
103 |
{element.email}
104 |
105 |
106 |
107 | {element.role === "ADMIN" ? "Admin User" : "Normal User"}
108 |
109 |
110 |
deleteUser(element.id)}
113 | >
114 |
115 |
116 |
117 |
118 |
119 | );
120 | })}
121 | >
122 | );
123 | };
124 | let filteredUsers = totalItems
125 | ? totalItems.filter((user) => {
126 | let fullName = user.firstName + user.lastName;
127 | return fullName.toLowerCase().indexOf(search.toLowerCase()) !== -1;
128 | })
129 | : [];
130 | return (
131 |
132 |
133 |
134 |
135 |
136 |
137 | Manage Users
138 |
139 |
140 |
141 |
142 | setSearch(e.target.value)}
148 | />
149 |
150 |
151 |
152 |
153 | {filteredUsers.length === 0 ? (
154 |
155 |
No user found
156 |
157 | ) : (
158 | <>
159 |
160 |
161 | >
162 | )}
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | );
171 | }
172 |
173 | export default ManageUsers;
174 |
--------------------------------------------------------------------------------
/src/Components/MoviesPage/MoviesPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Rings } from "react-loader-spinner";
3 | import { Link, useNavigate } from "react-router-dom";
4 | import { UncontrolledCarousel } from "reactstrap";
5 | import { fget } from "../../Utilities/apiCalls";
6 | import Error from "../ErrorPage/ErrorPage";
7 | import "./MoviesPage.css";
8 | import CardsRow from "../Render/CardsRow";
9 | import { useUser } from "../../Shared/js/user-context";
10 |
11 | function MoviesPage(props) {
12 | const {
13 | state: { user },
14 | } = useUser();
15 | const [error, setError] = useState(null);
16 | const [isLoaded, setIsLoaded] = useState(false);
17 | const [upcoming, setUpcoming] = useState([]);
18 | const [topRated, setTopRated] = useState([]);
19 | const [recommended, setRecommended] = useState([]);
20 | const genres = Array.isArray(user.genres)
21 | ? user.genres.join(",")
22 | : user.genres;
23 |
24 | let navigate = useNavigate();
25 |
26 | const formatFunctionUpcoming = (movies) => {
27 | return movies.map((ele) => {
28 | return {
29 | key: ele.id,
30 | src: "https://image.tmdb.org/t/p/original" + ele.backdrop_path,
31 | caption: ele.title,
32 | };
33 | });
34 | };
35 | useEffect(() => {
36 | if (!genres) {
37 | navigate("/genres");
38 | return;
39 | }
40 | fget({
41 | url: `/3/movie/upcoming?api_key=${process.env.REACT_APP_BASE_TOKEN}&language=en-US&page=1`,
42 | })
43 | .then((res) => res.data)
44 | .then(
45 | (result) => {
46 | setUpcoming(
47 | formatFunctionUpcoming(result.results.slice(0, 10))
48 | );
49 | setIsLoaded(true);
50 | },
51 | (error) => {
52 | setIsLoaded(true);
53 | setError(error);
54 | }
55 | );
56 | fget({
57 | url: `/3/movie/top_rated?api_key=${process.env.REACT_APP_BASE_TOKEN}&language=en-US&page=1`,
58 | })
59 | .then((res) => res.data)
60 | .then(
61 | (result) => {
62 | setTopRated(result.results.slice(0, 6));
63 | setIsLoaded(true);
64 | },
65 | (error) => {
66 | setIsLoaded(true);
67 | setError(error);
68 | }
69 | );
70 | fget({
71 | url: `/3/discover/movie?api_key=${process.env.REACT_APP_BASE_TOKEN}&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_watch_monetization_types=flatrate&with_genres=${genres}`,
72 | })
73 | .then((res) => res.data)
74 | .then(
75 | (result) => {
76 | setRecommended(result.results.slice(0, 6));
77 | setIsLoaded(true);
78 | },
79 | (error) => {
80 | setIsLoaded(true);
81 | setError(error);
82 | }
83 | );
84 | }, []);
85 | if (error) {
86 | return ;
87 | } else if (!isLoaded) {
88 | return (
89 |
98 |
99 |
100 | );
101 | } else
102 | return (
103 |
104 |
105 |
106 |
107 |
108 |
Recommended Movies
109 |
110 |
111 | More
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
Top Rated Movies
123 |
124 |
125 | More
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
Upcoming Movies
136 |
137 |
138 |
144 |
145 |
146 |
147 |
148 | );
149 | }
150 |
151 | export default MoviesPage;
152 |
--------------------------------------------------------------------------------
/src/Shared/images/404.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Shared/images/Error2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Shared/images/profile.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Components/GenrePage/GenrePage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Rings } from "react-loader-spinner";
3 | import { useNavigate } from "react-router-dom";
4 | import { useUser } from "../../Shared/js/user-context";
5 | import { fget, patchBackend } from "../../Utilities/apiCalls";
6 | import Error from "../ErrorPage/ErrorPage";
7 | import Genre from "../../Shared/images/Register.svg";
8 | import "./GenrePage.css";
9 | import Swal from "sweetalert2";
10 |
11 | function GenrePage(props) {
12 | const [error, setError] = useState(null);
13 | const [isLoaded, setIsLoaded] = useState(false);
14 | const [genres, setGenres] = useState([]);
15 | const [seeMore, setSeeMore] = useState(false);
16 | let [highlightedButtons, setHighlightedButtons] = useState([]);
17 | const { dispatch } = useUser();
18 | let navigate = useNavigate();
19 | useEffect(() => {
20 | fget({
21 | url: `/3/genre/movie/list?api_key=${process.env.REACT_APP_BASE_TOKEN}&language=en-US`,
22 | })
23 | .then((res) => res.data)
24 | .then(
25 | (result) => {
26 | result.genres.forEach((ele) => {
27 | ele.isSelected = false;
28 | });
29 | setGenres(result.genres);
30 | setIsLoaded(true);
31 | },
32 | (error) => {
33 | setIsLoaded(true);
34 | setError(error);
35 | }
36 | );
37 | }, []);
38 | const highlightButton = (ele, index) => {
39 | let newSet = [...genres];
40 | newSet[index].isSelected = !newSet[index].isSelected;
41 | setGenres(() => [...newSet]);
42 | if (highlightedButtons.indexOf(ele.id) === -1)
43 | setHighlightedButtons(() => [...highlightedButtons, ele.id]);
44 | // highlightedButtons.push(ele.id);
45 | else {
46 | // highlightedButtons.splice(highlightedButtons.indexOf(ele.id), 1);
47 | setHighlightedButtons(() => [
48 | ...highlightedButtons.splice(
49 | highlightedButtons.indexOf(ele.id),
50 | 1
51 | ),
52 | ]);
53 | }
54 | };
55 | const GenreList = () => {
56 | let buttonClass = "btn rounded-pill btn-sm btn-outline-danger m-1";
57 | let buttonSelectedClass = "btn rounded-pill btn-sm btn-danger m-1";
58 | return (
59 | <>
60 | {genres.map((ele, index) => {
61 | if (index < 9)
62 | return (
63 |
75 | );
76 | else
77 | return (
78 |
92 | );
93 | })}
94 | >
95 | );
96 | };
97 | const goToMoviesPage = () => {
98 | if (highlightedButtons.length) {
99 | patchBackend({
100 | url: "user/",
101 | data: {
102 | genres: highlightedButtons.join(","),
103 | },
104 | })
105 | .then((res) => res.data)
106 | .then((res) => {
107 | dispatch({ type: "genres", Genres: res.user.genres });
108 | dispatch({ type: "login", user: res.user });
109 | navigate("/");
110 | });
111 | } else {
112 | Swal.fire({
113 | title: "Error",
114 | text: `Select a genre to continue`,
115 | icon: "error",
116 | confirmButtonText: "Dismiss",
117 | });
118 | }
119 | };
120 | if (error) {
121 | return ;
122 | } else if (!isLoaded) {
123 | return (
124 |
133 |
134 |
135 | );
136 | } else
137 | return (
138 |
139 |
140 |
141 |
142 |
143 |
144 |

145 |
146 |
147 | Select the genres you're interested in...
148 |
149 |
150 |
151 |
setSeeMore(!seeMore)}
154 | >
155 | {seeMore ? "See Less" : "See More"}
156 |
157 |
158 |
159 |
160 |
170 |
171 |
172 |
173 |
174 |
175 | );
176 | }
177 | export default GenrePage;
178 |
--------------------------------------------------------------------------------
/src/Components/LoginComponent/LoginComponent.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useNavigate, Link } from "react-router-dom";
3 | import { postBackend } from "../../Utilities/apiCalls";
4 | import * as Yup from "yup";
5 | import { Formik, Field, Form, ErrorMessage } from "formik";
6 | import { useUser } from "../../Shared/js/user-context";
7 | import Swal from "sweetalert2";
8 | import Login from "../../Shared/images/login.svg";
9 |
10 | const LoginSchema = Yup.object().shape({
11 | email: Yup.string().email("Invalid email").required("Required"),
12 | password: Yup.string().required("Required"),
13 | });
14 | function LoginComponent() {
15 | const { dispatch } = useUser();
16 | const navigate = useNavigate();
17 | const [loading, setLoading] = useState(false);
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
Log In
27 |
28 |
29 |

30 |
31 |
{
38 | setLoading(true);
39 | postBackend({
40 | url: `signin`,
41 | data: {
42 | email: values.email,
43 | password: values.password,
44 | },
45 | })
46 | .then((res) => res.data)
47 | .then((res) => {
48 | setLoading(false);
49 | dispatch({
50 | type: "login",
51 | user: res.user,
52 | });
53 | localStorage.setItem(
54 | "token",
55 | res.token
56 | );
57 | window.adobeDataLayer =
58 | window.adobeDataLayer || [];
59 |
60 | window.adobeDataLayer.push({
61 | event: "user-login",
62 | user: res.user.id,
63 | });
64 |
65 | if (res.user.role === "admin")
66 | navigate("/manage");
67 | else
68 | res.user.genres.length
69 | ? navigate("/")
70 | : navigate("/genres");
71 | })
72 | .catch((err) => {
73 | console.log(err);
74 | Swal.fire({
75 | title: "Error",
76 | text: `Incorrect username or password`,
77 | icon: "error",
78 | confirmButtonText: "Dismiss",
79 | });
80 | });
81 | }}
82 | >
83 | {({ errors, touched }) => (
84 |
136 | )}
137 |
138 |
139 |
140 | Not registered? Sign Up
141 |
142 |
143 |
144 |
145 |
146 | );
147 | }
148 |
149 | export default LoginComponent;
150 |
--------------------------------------------------------------------------------
/src/Components/RegisterComponent/RegisterComponent.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useNavigate, Link } from "react-router-dom";
3 | import { postBackend } from "../../Utilities/apiCalls";
4 | import * as Yup from "yup";
5 | import { Formik, Field, Form, ErrorMessage } from "formik";
6 | import Swal from "sweetalert2";
7 |
8 | const SignUpSchema = Yup.object().shape({
9 | email: Yup.string().email("Invalid email").required("Required"),
10 | password: Yup.string()
11 | .label("password")
12 | .required("Required")
13 | .min(8, "Seems a bit short..."),
14 |
15 | // .matches(
16 | // /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/,
17 | // "At least 1 letter, 1 number and 1 special character"
18 | // ),
19 | confirmpassword: Yup.string()
20 | .required("Required")
21 | .label("confirmpassword")
22 | .test("passwords-match", "Passwords don't match", function (value) {
23 | return this.parent.password === value;
24 | }),
25 | firstname: Yup.string()
26 | .matches(/^[A-Za-z]+$/, "Only letters allowed")
27 | .required("Required"), //put in number validation
28 | lastname: Yup.string()
29 | .matches(/^[A-Za-z]+$/, "Only letters allowed")
30 | .required("Required"),
31 | });
32 |
33 | function RegisterComponent() {
34 | const navigate = useNavigate();
35 | const [loading, setLoading] = useState(false);
36 |
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
Register
45 |
46 |
47 |
{
57 | setLoading(true);
58 | postBackend({
59 | url: "user/",
60 | data: {
61 | firstName: values.firstname,
62 | lastName: values.lastname,
63 | email: values.email,
64 | password: values.password,
65 | },
66 | })
67 | .then((res) => res.data)
68 | .then((res) => {
69 | if (res.token) {
70 | setLoading(false);
71 | Swal.fire({
72 | confirmButtonColor: "#e31c5f",
73 | title: "Success",
74 | text: `New user created`,
75 | icon: "success",
76 | confirmButtonText: "Log In",
77 | }).then((res) => {
78 | if (res.isConfirmed) navigate("/login");
79 | });
80 | } else throw new Error();
81 | })
82 | .catch(() => {
83 | Swal.fire({
84 | title: "Error",
85 | text: `User already exists`,
86 | icon: "error",
87 | confirmButtonText: "Dismiss",
88 | });
89 | });
90 | }}
91 | >
92 | {({ errors, touched }) => (
93 |
187 | )}
188 |
189 |
190 |
191 |
192 | Already registered? Log In
193 |
194 |
195 |
196 |
197 |
198 | );
199 | }
200 |
201 | export default RegisterComponent;
202 |
--------------------------------------------------------------------------------
/src/App.scss:
--------------------------------------------------------------------------------
1 | $theme-red: #e31c5f;
2 | $title-text: #9e4784;
3 | $title: #d27685;
4 | .movie-title {
5 | color: $title-text;
6 | font-weight: 500;
7 | font-size: large;
8 | }
9 | .sub-title {
10 | color: whitesmoke;
11 | }
12 | .error {
13 | color: $theme-red;
14 | }
15 | .centered {
16 | height: 100%;
17 | display: flex;
18 | justify-content: center;
19 | align-items: center;
20 | min-height: 100vh;
21 | }
22 | .centered1 {
23 | height: 100%;
24 | display: flex;
25 | justify-content: center;
26 | min-height: 100vh;
27 | }
28 | .row {
29 | width: 100%;
30 | }
31 | .card {
32 | box-shadow: 0 5px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
33 | }
34 | .clickable {
35 | cursor: pointer;
36 | transition: transform 0.3s ease;
37 | .card:hover {
38 | transform: scale(1.05);
39 | }
40 | }
41 | .display-6 {
42 | color: $theme-red;
43 | }
44 | .App {
45 | background-color: #37306b;
46 | min-height: 100vh;
47 | }
48 | .card img {
49 | width: 100%;
50 | height: 18rem;
51 | object-fit: cover;
52 | }
53 | h1 {
54 | position: relative;
55 | padding: 0;
56 | margin: 0;
57 | font-family: 'Raleway', sans-serif;
58 | font-weight: 300;
59 | font-size: 40px;
60 | color: white;
61 | -webkit-transition: all 0.4s ease 0s;
62 | -o-transition: all 0.4s ease 0s;
63 | transition: all 0.4s ease 0s;
64 | }
65 | .error-details {
66 | color: white;
67 | }
68 | h1 span {
69 | display: block;
70 | font-size: 0.5em;
71 | line-height: 1.3;
72 | }
73 | h1 em {
74 | font-style: normal;
75 | font-weight: 600;
76 | }
77 | .ten h1 {
78 | font-size: 24px;
79 | color: $title;
80 | font-weight: 600;
81 | }
82 | .ten h1:before {
83 | background-color: #0d6efd;
84 | border-radius: 0.25rem;
85 | content: '';
86 | display: block;
87 | height: 0.25rem;
88 | width: 42px;
89 | margin-bottom: 1.25rem;
90 | }
91 | .twelve h1 {
92 | font-size: 26px;
93 | font-weight: 700;
94 | letter-spacing: 1px;
95 | text-transform: uppercase;
96 | width: 160px;
97 | text-align: center;
98 | margin: auto;
99 | white-space: nowrap;
100 | padding-bottom: 13px;
101 | color: black;
102 | }
103 | .twelve h1:before {
104 | background-color: $theme-red;
105 | content: '';
106 | display: block;
107 | height: 3px;
108 | width: 75px;
109 | margin-bottom: 5px;
110 | }
111 | .twelve h1:after {
112 | background-color: $theme-red;
113 | content: '';
114 | display: block;
115 | position: absolute;
116 | right: 0;
117 | bottom: 0;
118 | height: 3px;
119 | width: 75px;
120 | margin-bottom: 0.25em;
121 | }
122 | .logo h1 {
123 | font-size: 26px;
124 | font-weight: 700;
125 | letter-spacing: 1px;
126 | text-transform: uppercase;
127 | width: 160px;
128 | text-align: center;
129 | margin: auto;
130 | white-space: nowrap;
131 | padding-bottom: 13px;
132 | color: white;
133 | cursor: pointer;
134 | }
135 | .logo h1:before {
136 | background-color: $theme-red;
137 | content: '';
138 | display: block;
139 | height: 3px;
140 | width: 75px;
141 | margin-bottom: 5px;
142 | }
143 | .logo h1:after {
144 | background-color: $theme-red;
145 | content: '';
146 | display: block;
147 | position: absolute;
148 | right: 0;
149 | bottom: 0;
150 | height: 3px;
151 | width: 75px;
152 | margin-bottom: 0.25em;
153 | }
154 | .button {
155 | background-color: $theme-red; /* Green */
156 | border: none;
157 | color: white;
158 | padding: 16px 32px;
159 | text-align: center;
160 | text-decoration: none;
161 | display: inline-block;
162 | font-size: 16px;
163 | margin: 4px 2px;
164 | transition-duration: 0.4s;
165 | cursor: pointer;
166 | }
167 | .nav-link {
168 | cursor: pointer;
169 | }
170 | .button1 {
171 | background-color: white;
172 | color: $theme-red;
173 | border: 2px solid $theme-red;
174 | font-weight: 600;
175 | letter-spacing: 0.1em;
176 | text-transform: uppercase;
177 | }
178 | .buttonSelected {
179 | background-color: $theme-red;
180 | color: white;
181 | border: 2px solid $theme-red;
182 | font-weight: 600;
183 | letter-spacing: 0.1em;
184 | text-transform: uppercase;
185 | cursor: default;
186 | }
187 |
188 | .button1:hover {
189 | background-color: $theme-red;
190 | color: white;
191 | }
192 | .link {
193 | a:visited {
194 | color: $theme-red !important;
195 | }
196 | a:active {
197 | color: #1b6970 !important;
198 | }
199 | a {
200 | color: $theme-red !important;
201 | text-decoration: none;
202 | }
203 | }
204 | .img {
205 | overflow: auto;
206 | width: fit-content;
207 | }
208 | .view-more {
209 | color: white;
210 | text-decoration: none;
211 | font-size: large;
212 | border-style: solid;
213 | padding: 2px 8px;
214 | border-width: 1px;
215 | border-radius: 5px;
216 | :hover,
217 | :active {
218 | color: $theme-red;
219 | }
220 | }
221 | .logout {
222 | display: flex;
223 | justify-content: flex-end;
224 | text-align: right;
225 | }
226 | .nav-link {
227 | color: white !important;
228 | font-size: larger;
229 | }
230 | .navbar-dark {
231 | background-color: #66347f;
232 | }
233 | .description {
234 | color: white;
235 | font-size: larger;
236 | }
237 | .comments {
238 | .halfDiv,
239 | .no-comDiv {
240 | color: white;
241 | }
242 | .comment-title {
243 | color: $title-text;
244 | }
245 | }
246 |
247 | .movie-list {
248 | list-style: none;
249 | margin: 0;
250 | padding: 0;
251 | }
252 |
253 | .movie {
254 | display: flex;
255 | align-items: center;
256 | margin-bottom: 20px;
257 | :hover {
258 | cursor: pointer;
259 | scale: 1.011;
260 | }
261 | }
262 |
263 | .movie img {
264 | width: 100px;
265 | height: 70px;
266 | margin-right: 20px;
267 | object-fit: scale-down;
268 | }
269 |
270 | .movie-details {
271 | flex: 1;
272 | }
273 |
274 | .movie-title {
275 | font-size: 24px;
276 | margin: 0;
277 | }
278 |
279 | .movie-rating {
280 | font-size: 18px;
281 | margin: 0;
282 | }
283 | .user-rating {
284 | color: white;
285 | }
286 | .profilePic {
287 | border-radius: 50%;
288 | }
289 | .list-card {
290 | text-align: left;
291 | border: 0.5px solid $theme-red;
292 | text-decoration: none;
293 | margin-bottom: 5px;
294 | cursor: pointer;
295 | font-weight: 500;
296 | // font-size: 1.5vw;
297 | :hover {
298 | .name {
299 | color: $theme-red;
300 | }
301 | }
302 | }
303 | .list-card1 {
304 | text-align: left;
305 | border: 0.5px solid $theme-red;
306 | text-decoration: none;
307 | margin-bottom: 5px;
308 | // cursor: pointer;
309 | font-weight: 500;
310 | // font-size: 1.5vw;
311 | }
312 | .deleteButton {
313 | color: red;
314 | padding: 5px;
315 | cursor: pointer;
316 | }
317 | .container-home {
318 | display: flex;
319 | width: 100%;
320 | padding: 0 1rem;
321 | margin-right: auto;
322 | margin-left: auto;
323 | justify-content: center;
324 | flex-direction: column;
325 | align-content: center;
326 | flex-wrap: wrap;
327 | }
328 | .movie-card {
329 | position: relative;
330 | }
331 |
332 | .overlay-card {
333 | position: absolute;
334 | top: 0;
335 | right: 0;
336 | display: none;
337 | justify-content: center;
338 | align-items: center;
339 | width: 100%;
340 | height: 100%;
341 | background-color: rgba(0, 0, 0, 0.5);
342 | }
343 |
344 | .movie-card:hover .overlay-card {
345 | display: flex;
346 | }
347 |
348 | .overlay-card .badge {
349 | position: relative;
350 | z-index: 1;
351 | }
352 |
353 | .card-img-top {
354 | transition: transform 0.3s ease-in-out;
355 | }
356 |
357 | .movie-card:hover .card-img-top {
358 | transform: scale(1.05);
359 | }
360 | .text-overlay {
361 | font-weight: bold;
362 | color: white;
363 | font-size: 1.5rem;
364 | }
365 | .trailer-container {
366 | display: flex;
367 | justify-content: center;
368 | }
369 | .iframe-container {
370 | position: relative;
371 | width: 100%;
372 | padding-bottom: 56.25%;
373 | height: 0;
374 | }
375 | .iframe-container iframe {
376 | position: absolute;
377 | top: 0;
378 | left: 0;
379 | width: 100%;
380 | height: 100%;
381 | }
382 | .search-bar {
383 | box-shadow: inset #abacaf 0 0 0 2px;
384 | border: 0;
385 | height: 100%;
386 | // background: rgba(0, 0, 0, 0);
387 | appearance: none;
388 | width: 100%;
389 | position: relative;
390 | border-radius: 3px;
391 | padding: 9px 12px;
392 | line-height: 1.4;
393 | color: rgb(0, 0, 0);
394 | font-size: 16px;
395 | font-weight: 400;
396 | transition: all 0.2s ease;
397 | :hover {
398 | box-shadow: 0 0 0 0 #fff inset, #1de9b6 0 0 0 2px;
399 | }
400 | :focus {
401 | background: #fff;
402 | outline: 0;
403 | box-shadow: 0 0 0 0 #fff inset, #1de9b6 0 0 0 3px;
404 | }
405 | }
406 |
--------------------------------------------------------------------------------
/src/Shared/images/login.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Components/ProfilePage/ProfilePage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import Error from "../ErrorPage/ErrorPage";
4 | import { Rings } from "react-loader-spinner";
5 | import { ErrorMessage, Field, Form, Formik } from "formik";
6 | import { getBackend, patchBackend } from "../../Utilities/apiCalls";
7 | import Swal from "sweetalert2";
8 | function ProfilePage() {
9 | const [error, setError] = useState(null);
10 | const [isLoaded, setIsLoaded] = useState(false);
11 | const [profile, setProfile] = useState({});
12 |
13 | const fetchUser = async () => {
14 | return getBackend({
15 | url: "user/",
16 | })
17 | .then((res) => res.data)
18 | .then((res) => {
19 | setProfile(res);
20 | })
21 | .catch(() => {
22 | setError(true);
23 | Swal.fire({
24 | title: "Error",
25 | text: `Some error occurred in fetching user`,
26 | icon: "error",
27 | confirmButtonText: "Dismiss",
28 | });
29 | });
30 | };
31 |
32 | useEffect(() => {
33 | fetchUser().then(() => {
34 | setIsLoaded(true);
35 | });
36 | }, []);
37 | if (error) {
38 | return ;
39 | } else if (!isLoaded) {
40 | return (
41 |
50 |
51 |
52 | );
53 | } else
54 | return (
55 |
56 |
57 |
58 |
59 |
60 |
61 | Profile
62 |
63 |
64 |
65 |

75 |
76 |
{
85 | let data = {
86 | firstName: values.firstname,
87 | lastName: values.lastname,
88 | email: values.email,
89 | password: values.password,
90 | };
91 | if (values.password) {
92 | data["Password"] = values.password;
93 | }
94 | patchBackend({
95 | url: "user/",
96 | data: data,
97 | }).then(() => {
98 | fetchUser();
99 | Swal.fire({
100 | confirmButtonColor: "#e31c5f",
101 | title: "Success",
102 | text: `Saved Changes`,
103 | icon: "success",
104 | confirmButtonText: "Dismiss",
105 | });
106 | });
107 | }}
108 | >
109 | {({ errors, touched }) => (
110 |
192 | )}
193 |
194 |
195 |
196 | Change recommendations?{" "}
197 | Edit Genres
198 |
199 |
200 |
201 |
202 |
203 | );
204 | }
205 |
206 | export default ProfilePage;
207 |
--------------------------------------------------------------------------------
/src/Components/MovieDetail/MovieDetail.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Rings } from 'react-loader-spinner';
3 | import { useParams, useLocation } from 'react-router-dom';
4 | import {
5 | deleteBackend,
6 | fget,
7 | getBackend,
8 | patchBackend,
9 | postBackend,
10 | } from '../../Utilities/apiCalls';
11 | import SingleCard from '../Render/SingleCard';
12 | import Error from '../ErrorPage/ErrorPage';
13 | import { CommentSection } from 'react-comments-section';
14 | import 'react-comments-section/dist/index.css';
15 | import { useUser } from '../../Shared/js/user-context';
16 | import StarRating from '../Render/Stars';
17 | import TrailerFrame from '../TrailerComponent/TrailerComponent';
18 | import { trackEvent } from '../../Shared/js/r42';
19 | const watchNowStyle = {
20 | display: 'inline-block',
21 | padding: '10px 20px',
22 | backgroundColor: 'black',
23 | color: 'white',
24 | textDecoration: 'none',
25 | borderRadius: '5px',
26 | fontSize: '16px',
27 | fontWeight: 'bold',
28 | margin: '10px 0',
29 | };
30 | function MovieDetail() {
31 | let { movieId } = useParams();
32 | const location = useLocation();
33 | const [error, setError] = useState(null);
34 | const [isLoaded, setIsLoaded] = useState(false);
35 | const [isCommentLoaded, setIsCommentLoaded] = useState(false);
36 | const [isMovieWatched, setIsMovieWatched] = useState(false);
37 | const [movie, setMovie] = useState(location.state.movie);
38 | const [comments, setComments] = useState(null);
39 | const [backendMovie, setBackendMovie] = useState(null);
40 | const [userRating, setUserRating] = useState(0);
41 | const [trailer, setTrailer] = useState(null);
42 | const [watchOptions, setWatchOptions] = useState(null);
43 | const handleRatingChange = (rating) => {
44 | setUserRating(rating);
45 | };
46 | const {
47 | state: { user },
48 | } = useUser();
49 |
50 | const addToWatchlist = () => {
51 | window.adobeDataLayer.push({
52 | event: 'watchlist',
53 | watchlist: {
54 | id: movieId,
55 | title: backendMovie.title,
56 | },
57 | });
58 | setR42('addToWatchList');
59 | postBackend({
60 | url: `api/watchlist/${movieId}`,
61 | data: {},
62 | }).then(() => isMovieWatchedFunction());
63 | };
64 |
65 | const getTrailer = () => {
66 | return fget({
67 | url: `/3/movie/${movieId}/videos?api_key=${process.env.REACT_APP_BASE_TOKEN}`,
68 | })
69 | .then((res) => res.data)
70 | .then(
71 | (result) => {
72 | if (result.results.length > 0)
73 | setTrailer(
74 | result.results.find(
75 | (ele) => ele.type === 'Trailer'
76 | ) || result.results[0]
77 | );
78 | else setTrailer(null);
79 | },
80 | (error) => {
81 | setError(error);
82 | }
83 | );
84 | };
85 |
86 | const getMovie = () => {
87 | return postBackend({
88 | url: 'api/movie/' + movieId,
89 | data: {
90 | id: +movieId,
91 | title: movie.title || '',
92 | release_date: movie.release_date || '',
93 | overview: movie.overview || '',
94 | imdbRating: +movie.vote_average || 0,
95 | poster_path: movie.poster_path || '',
96 | backdrop_path: movie.backdrop_path || '',
97 | },
98 | })
99 | .then((res) => res.data)
100 | .then(
101 | (result) => {
102 | setBackendMovie(result);
103 | window.adobeDataLayer.push({
104 | event: 'movie-detail',
105 | movie: {
106 | id: movieId,
107 | title: result.title,
108 | release_date: result.year,
109 | },
110 | });
111 | },
112 | (error) => {
113 | setError(error);
114 | }
115 | );
116 | };
117 | const getComments = () => {
118 | return getBackend({
119 | url: `api/comment/${movieId}`,
120 | })
121 | .then((res) => res.data)
122 | .then(
123 | (result) => {
124 | setIsCommentLoaded(true);
125 | let newC = result.data.map((ele) => {
126 | return {
127 | userId: ele.userId,
128 | comId: ele.id,
129 | fullName:
130 | ele.user.firstName + ' ' + ele.user.lastName,
131 | text: ele.comment,
132 | avatarUrl: `https://ui-avatars.com/api/name=${
133 | ele.user.firstName + '+' + ele.user.lastName
134 | }&background=random`,
135 | replies: [],
136 | };
137 | });
138 | setComments(newC);
139 | },
140 | (error) => {
141 | setIsCommentLoaded(true);
142 | setError(error);
143 | }
144 | );
145 | };
146 | const getMovieRating = () => {
147 | return getBackend({
148 | url: `api/rating/${movieId}`,
149 | })
150 | .then((res) => res.data)
151 | .then((res) => {
152 | setUserRating(+res.data);
153 | });
154 | };
155 | const isMovieWatchedFunction = () => {
156 | return postBackend({
157 | url: `api/watchlist/check/${movieId}`,
158 | })
159 | .then((res) => res.data)
160 | .then((res) => {
161 | setIsMovieWatched(res.data);
162 | });
163 | };
164 | const removeFromWatchList = () => {
165 | deleteBackend({
166 | url: `api/watchlist/${movieId}`,
167 | data: {},
168 | }).then(() => isMovieWatchedFunction());
169 | };
170 | const getWatchOptions = () => {
171 | let location = navigator.language.split('-')[1];
172 | return fget({
173 | url: `/3/movie/${movieId}/watch/providers?api_key=${process.env.REACT_APP_BASE_TOKEN}`,
174 | })
175 | .then((res) => res.data)
176 | .then(
177 | (result) => {
178 | setWatchOptions(
179 | result.results[location] ||
180 | result.results.GB ||
181 | result.results.US ||
182 | result.results.IN
183 | );
184 | },
185 | (error) => {
186 | setError(error);
187 | }
188 | );
189 | };
190 | const setR42 = (action) => {
191 | let data = {
192 | movieIMDBRating: movie.vote_average,
193 | movieReleaseDate: movie.release_date,
194 | movieID: movieId,
195 | movieTitle: movie.title,
196 | dateTime: new Date().toISOString(),
197 | };
198 | if (action) {
199 | data['action'] = action;
200 | }
201 |
202 | trackEvent(data);
203 | };
204 |
205 | useEffect(() => {
206 | window.scrollTo(0, 0);
207 | Promise.all([
208 | getMovie(),
209 | getComments(),
210 | isMovieWatchedFunction(),
211 | getTrailer(),
212 | getWatchOptions(),
213 | ])
214 | .then(() => {
215 | getMovieRating();
216 | })
217 | .then(() => {
218 | setIsLoaded(true);
219 | setR42();
220 | });
221 | }, [movie]);
222 | if (error) {
223 | return ;
224 | } else if (!isLoaded) {
225 | return (
226 |
235 |
236 |
237 | );
238 | } else
239 | return (
240 |
241 |
242 |
243 |
244 |
245 |
246 | {!isMovieWatched ? (
247 |
254 | ) : (
255 |
262 | )}
263 |
264 |
265 |
266 |
267 |
268 |
{backendMovie.title}
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
User Rating
279 |
280 |
287 |
288 |
289 |
290 |
IMDB Rating
291 |
292 | {' '}
293 | {Math.round(
294 | movie.vote_average * 10
295 | ) / 10}
296 |
297 |
298 |
299 |
300 |
301 |
302 | {backendMovie.description}
303 |
304 |
317 |
318 | {
329 | postBackend({
330 | url: 'api/comment/' + movieId,
331 | data: {
332 | comment: data.text,
333 | },
334 | })
335 | .then(() => getComments())
336 | .then(() => {
337 | window.adobeDataLayer.push({
338 | event: 'comment',
339 | comment: {
340 | id: movieId,
341 | title: backendMovie.title,
342 | },
343 | });
344 | });
345 | }}
346 | onDeleteAction={(data, rest) => {
347 | deleteBackend({
348 | url:
349 | 'api/comment/' + data.comIdToDelete,
350 | }).then(() => getComments());
351 | }}
352 | onEditAction={(data) => {
353 | patchBackend({
354 | url: 'api/comment/' + movieId,
355 | data: {
356 | id: data.comId,
357 | comment: data.text,
358 | },
359 | }).then(() => getComments());
360 | }}
361 | />
362 |
363 |
364 |
365 |
366 | );
367 | }
368 | export default MovieDetail;
369 |
--------------------------------------------------------------------------------
/src/Shared/images/Register.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------