├── .env.example
├── .eslintrc.js
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo.png
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── app
└── store.js
├── assets
├── genres
│ ├── action.png
│ ├── adventure.png
│ ├── animation.png
│ ├── comedy.png
│ ├── crime.png
│ ├── documentary.png
│ ├── drama.png
│ ├── family.png
│ ├── fantasy.png
│ ├── history.png
│ ├── horror.png
│ ├── index.js
│ ├── music.png
│ ├── mystery.png
│ ├── popular.png
│ ├── romance.png
│ ├── science fiction.png
│ ├── thriller.png
│ ├── top rated.png
│ ├── tv movie.png
│ ├── upcoming.png
│ ├── war.png
│ └── western.png
└── images
│ ├── Filmpire.jpg
│ ├── cinema.png
│ ├── cinemas.svg
│ ├── darkmode.png
│ ├── infodark.png
│ ├── infolight.png
│ ├── lightmode.png
│ ├── movieNight.svg
│ ├── moviesflix.png
│ └── nightmode.svg
├── components
├── Actors
│ ├── Actors.jsx
│ └── styles.js
├── Alan.jsx
├── App.jsx
├── FeaturedMovie
│ ├── FeaturedMovie.jsx
│ └── styles.js
├── Movie
│ ├── Movie.jsx
│ └── styles.js
├── MovieInfo
│ ├── MovieInfo.jsx
│ └── styles.js
├── MovieList
│ ├── MovieList.jsx
│ └── styles.js
├── Movies
│ └── Movies.jsx
├── Navbar
│ ├── Navbar.jsx
│ └── styles.js
├── Pagination
│ ├── Pagination.jsx
│ └── styles.js
├── Profile
│ └── Profile.jsx
├── RatedCards
│ ├── RatedCards.jsx
│ └── styles.js
├── Search
│ ├── Search.jsx
│ └── styles.js
├── Sidebar
│ ├── Sidebar.jsx
│ └── styles.js
├── index.js
└── styles.js
├── features
├── auth.js
└── currentGenreOrCategory.js
├── index.css
├── index.js
├── services
└── TMDB.js
└── utils
├── ToggleColorMode.jsx
└── index.js
/.env.example:
--------------------------------------------------------------------------------
1 | ESLINT_NO_DEV_ERRORS = true
2 | REACT_APP_TMDB_KEY
3 | REACT_APP_ALAN_SDK_KEY
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: [
7 | 'plugin:react/recommended',
8 | 'airbnb',
9 | ],
10 | parserOptions: {
11 | ecmaFeatures: {
12 | jsx: true,
13 | },
14 | ecmaVersion: 'latest',
15 | sourceType: 'module',
16 | },
17 | plugins: [
18 | 'react',
19 | ],
20 | rules: {
21 | 'import/extensions': 0,
22 | 'react/prop-types': 0,
23 | 'linebreak-style': 0,
24 | 'react/state-in-constructor': 0,
25 | 'import/prefer-default-export': 0,
26 | 'max-len': [2, 250],
27 | 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 1 }],
28 | 'no-underscore-dangle': ['error', { allow: ['_d', '_dh', '_h', '_id', '_m', '_n', '_t', '_text'] }],
29 | 'object-curly-newline': 0,
30 | 'react/jsx-filename-extension': 0,
31 | 'react/jsx-one-expression-per-line': 0,
32 | 'jsx-a11y/click-events-have-key-events': 0,
33 | 'jsx-a11y/alt-text': 0,
34 | 'jsx-a11y/no-autofocus': 0,
35 | 'jsx-a11y/no-static-element-interactions': 0,
36 | 'react/no-array-index-key': 0,
37 | 'no-param-reassign': 0,
38 | 'react/react-in-jsx-scope': 0,
39 | 'jsx-a11y/anchor-is-valid': ['error', { components: ['Link'], specialLink: ['to', 'hrefLeft', 'hrefRight'], aspects: ['noHref', 'invalidHref', 'preferButton'] }],
40 | },
41 | };
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | .env
3 | build
4 | .env.production
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Filmpire
2 |
3 | 
4 |
5 | Filmpire combines the desire to unleash powerful creativity with the industry's most advanced JavaScript tools including React.js, Redux, Material UI, Alan AI, and more.
6 |
7 | This application includes user authentication, dark mode, sort movie on the basis of categories or genres, viewing movie and actor details, adding a movie to favorites or watchlist and many more functionalities.
8 |
9 | Alan works as in-app voice assistant which create conversational experiences for filmpire.
10 |
11 | # Getting Started with Create React App
12 |
13 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
14 |
15 | ## Available Scripts
16 |
17 | In the project directory, you can run:
18 |
19 | ### `npm start`
20 |
21 | Runs the app in the development mode.\
22 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
23 |
24 | The page will reload when you make changes.\
25 | You may also see any lint errors in the console.
26 |
27 | ### `npm test`
28 |
29 | Launches the test runner in the interactive watch mode.\
30 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
31 |
32 | ### `npm run build`
33 |
34 | Builds the app for production to the `build` folder.\
35 | It correctly bundles React in production mode and optimizes the build for the best performance.
36 |
37 | The build is minified and the filenames include the hashes.\
38 | Your app is ready to be deployed!
39 |
40 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
41 |
42 | ### `npm run eject`
43 |
44 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
45 |
46 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
47 |
48 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
49 |
50 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
51 |
52 | ## Learn More
53 |
54 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
55 |
56 | To learn React, check out the [React documentation](https://reactjs.org/).
57 |
58 | ### Code Splitting
59 |
60 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
61 |
62 | ### Analyzing the Bundle Size
63 |
64 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
65 |
66 | ### Making a Progressive Web App
67 |
68 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
69 |
70 | ### Advanced Configuration
71 |
72 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
73 |
74 | ### Deployment
75 |
76 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
77 |
78 | ### `npm run build` fails to minify
79 |
80 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
81 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "filmpire",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@alan-ai/alan-sdk-web": "^1.8.33",
7 | "@emotion/react": "^11.9.0",
8 | "@emotion/styled": "^11.8.1",
9 | "@mui/icons-material": "^5.8.2",
10 | "@mui/material": "^5.8.2",
11 | "@mui/styles": "^5.8.0",
12 | "@reduxjs/toolkit": "^1.8.2",
13 | "@testing-library/jest-dom": "^5.16.4",
14 | "@testing-library/react": "^13.3.0",
15 | "@testing-library/user-event": "^13.5.0",
16 | "axios": "^0.27.2",
17 | "react": "^18.1.0",
18 | "react-dom": "^18.1.0",
19 | "react-redux": "^8.0.2",
20 | "react-router-dom": "^6.3.0",
21 | "react-scripts": "5.0.1",
22 | "web-vitals": "^2.1.4"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | },
48 | "devDependencies": {
49 | "eslint": "^8.17.0",
50 | "eslint-config-airbnb": "^19.0.4",
51 | "eslint-plugin-import": "^2.26.0",
52 | "eslint-plugin-jsx-a11y": "^6.5.1",
53 | "eslint-plugin-react": "^7.30.0",
54 | "eslint-plugin-react-hooks": "^4.5.0"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Filmpire
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/public/logo.png
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.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 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/app/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import { tmdbApi } from '../services/TMDB';
3 | import genreOrCategoryReducer from '../features/currentGenreOrCategory';
4 | import userReducer from '../features/auth';
5 |
6 | export default configureStore({
7 | reducer: {
8 | [tmdbApi.reducerPath]: tmdbApi.reducer,
9 | currentGenreOrCategory: genreOrCategoryReducer,
10 | user: userReducer,
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/src/assets/genres/action.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/action.png
--------------------------------------------------------------------------------
/src/assets/genres/adventure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/adventure.png
--------------------------------------------------------------------------------
/src/assets/genres/animation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/animation.png
--------------------------------------------------------------------------------
/src/assets/genres/comedy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/comedy.png
--------------------------------------------------------------------------------
/src/assets/genres/crime.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/crime.png
--------------------------------------------------------------------------------
/src/assets/genres/documentary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/documentary.png
--------------------------------------------------------------------------------
/src/assets/genres/drama.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/drama.png
--------------------------------------------------------------------------------
/src/assets/genres/family.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/family.png
--------------------------------------------------------------------------------
/src/assets/genres/fantasy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/fantasy.png
--------------------------------------------------------------------------------
/src/assets/genres/history.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/history.png
--------------------------------------------------------------------------------
/src/assets/genres/horror.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/horror.png
--------------------------------------------------------------------------------
/src/assets/genres/index.js:
--------------------------------------------------------------------------------
1 | import action from './action.png';
2 | import adventure from './adventure.png';
3 | import animation from './animation.png';
4 | import comedy from './comedy.png';
5 | import crime from './crime.png';
6 | import documentary from './documentary.png';
7 | import drama from './drama.png';
8 | import family from './family.png';
9 | import fantasy from './fantasy.png';
10 | import horror from './horror.png';
11 | import history from './history.png';
12 | import mystery from './mystery.png';
13 | import music from './music.png';
14 | import romance from './romance.png';
15 | import scienceFiction from './science fiction.png';
16 | import thriller from './thriller.png';
17 | import tvMovie from './tv movie.png';
18 | import war from './war.png';
19 | import western from './western.png';
20 |
21 | import popular from './popular.png';
22 | import topRated from './top rated.png';
23 | import upcoming from './upcoming.png';
24 |
25 | export default {
26 | action,
27 | adventure,
28 | animation,
29 | comedy,
30 | crime,
31 | documentary,
32 | drama,
33 | family,
34 | fantasy,
35 | horror,
36 | history,
37 | mystery,
38 | music,
39 | romance,
40 | 'science fiction': scienceFiction,
41 | thriller,
42 | 'tv movie': tvMovie,
43 | war,
44 | western,
45 | popular,
46 | 'top rated': topRated,
47 | upcoming,
48 | };
49 |
--------------------------------------------------------------------------------
/src/assets/genres/music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/music.png
--------------------------------------------------------------------------------
/src/assets/genres/mystery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/mystery.png
--------------------------------------------------------------------------------
/src/assets/genres/popular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/popular.png
--------------------------------------------------------------------------------
/src/assets/genres/romance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/romance.png
--------------------------------------------------------------------------------
/src/assets/genres/science fiction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/science fiction.png
--------------------------------------------------------------------------------
/src/assets/genres/thriller.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/thriller.png
--------------------------------------------------------------------------------
/src/assets/genres/top rated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/top rated.png
--------------------------------------------------------------------------------
/src/assets/genres/tv movie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/tv movie.png
--------------------------------------------------------------------------------
/src/assets/genres/upcoming.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/upcoming.png
--------------------------------------------------------------------------------
/src/assets/genres/war.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/war.png
--------------------------------------------------------------------------------
/src/assets/genres/western.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/genres/western.png
--------------------------------------------------------------------------------
/src/assets/images/Filmpire.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/images/Filmpire.jpg
--------------------------------------------------------------------------------
/src/assets/images/cinema.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/images/cinema.png
--------------------------------------------------------------------------------
/src/assets/images/cinemas.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/darkmode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/images/darkmode.png
--------------------------------------------------------------------------------
/src/assets/images/infodark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/images/infodark.png
--------------------------------------------------------------------------------
/src/assets/images/infolight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/images/infolight.png
--------------------------------------------------------------------------------
/src/assets/images/lightmode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/images/lightmode.png
--------------------------------------------------------------------------------
/src/assets/images/movieNight.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/moviesflix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoderGhost37/Filmpire/bbf76e6da89961d3e6a71a32b88bdee2964a7e5f/src/assets/images/moviesflix.png
--------------------------------------------------------------------------------
/src/assets/images/nightmode.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Actors/Actors.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Box, Button, CircularProgress, Grid, Typography } from '@mui/material';
3 | import { useNavigate, useParams } from 'react-router-dom';
4 | import { ArrowBack } from '@mui/icons-material';
5 |
6 | import useStyles from './styles';
7 | import { useGetActorQuery, useGetMoviesByActorIdQuery } from '../../services/TMDB';
8 | import { MovieList, Pagination } from '../index';
9 |
10 | function Actors() {
11 | const classes = useStyles();
12 | const [page, setPage] = useState(1);
13 | const navigate = useNavigate();
14 | const { id } = useParams();
15 | const { data, isFetching, error } = useGetActorQuery(id);
16 | const { data: movies } = useGetMoviesByActorIdQuery({ id, page });
17 |
18 | if (isFetching) {
19 | return (
20 |
21 |
22 |
23 | );
24 | }
25 |
26 | if (error) {
27 | return (
28 |
29 | } onClick={() => navigate(-1)} color="primary">
30 | Go Back
31 |
32 |
33 | );
34 | }
35 |
36 | return (
37 | <>
38 |
39 |
40 |
45 |
46 |
47 | {data?.name}
48 | Born: {new Date(data?.birthday).toDateString()}
49 | {data?.biography || 'Sorry, no biography yet...'}
50 |
51 |
52 | } onClick={() => navigate(-1)} color="primary">Back
53 |
54 |
55 |
56 |
57 | Movies
58 | {movies && }
59 |
60 |
61 | >
62 | );
63 | }
64 |
65 | export default Actors;
66 |
--------------------------------------------------------------------------------
/src/components/Actors/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles(() => ({
4 | image: {
5 | maxWidth: '90%',
6 | borderRadius: '20px',
7 | objectFit: 'cover',
8 | boxShadow: '0.5em 0.5em 1em',
9 | },
10 | btns: {
11 | marginTop: '2rem',
12 | display: 'flex',
13 | justifyContent: 'space-around',
14 | },
15 | }));
16 |
--------------------------------------------------------------------------------
/src/components/Alan.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useContext } from 'react';
2 | import alanBtn from '@alan-ai/alan-sdk-web';
3 | import { useDispatch } from 'react-redux';
4 | import { useNavigate } from 'react-router-dom';
5 |
6 | import { ColorModeContext } from '../utils/ToggleColorMode';
7 | import { fetchToken } from '../utils/index';
8 | import { selectGenreOrCategory, searchMovie } from '../features/currentGenreOrCategory';
9 |
10 | function useAlan() {
11 | const { setMode } = useContext(ColorModeContext);
12 | const dispatch = useDispatch();
13 | const navigate = useNavigate();
14 |
15 | useEffect(() => {
16 | alanBtn({
17 | key: process.env.REACT_APP_ALAN_SDK_KEY,
18 | onCommand: ({ command, mode, genres, genreOrCategory, query }) => {
19 | if (command === 'chooseGenre') {
20 | const foundGenre = genres.find((g) => g.name.toLowerCase() === genreOrCategory.toLowerCase());
21 | if (foundGenre) {
22 | navigate('/');
23 | dispatch(selectGenreOrCategory(foundGenre.id));
24 | } else {
25 | const category = genreOrCategory.startsWith('top') ? 'top_rated' : genreOrCategory;
26 | navigate('/');
27 | dispatch(selectGenreOrCategory(category));
28 | }
29 | } else if (command === 'changeMode') {
30 | if (mode === 'light') {
31 | setMode('light');
32 | } else {
33 | setMode('dark');
34 | }
35 | } else if (command === 'login') {
36 | fetchToken();
37 | } else if (command === 'logout') {
38 | localStorage.clear();
39 | navigate('/');
40 | } else if (command === 'search') {
41 | dispatch(searchMovie(query));
42 | }
43 | },
44 | });
45 | }, []);
46 | }
47 |
48 | export default useAlan;
49 |
--------------------------------------------------------------------------------
/src/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import { CssBaseline } from '@mui/material';
3 | import { Routes, Route } from 'react-router-dom';
4 |
5 | import useStyles from './styles';
6 | import useAlan from './Alan';
7 |
8 | import { Movies, Actors, MovieInfo, Navbar, Profile } from './index';
9 |
10 | function App() {
11 | const classes = useStyles();
12 | const alanBtnContainer = useRef();
13 |
14 | useAlan();
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 | } />
24 | } />
25 | } />
26 | } />
27 | } />
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default App;
36 |
--------------------------------------------------------------------------------
/src/components/FeaturedMovie/FeaturedMovie.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Box, Typography, Card, CardContent, CardMedia } from '@mui/material';
3 | import { Link } from 'react-router-dom';
4 |
5 | import useStyles from './styles';
6 |
7 | function FeaturedMovie({ movie }) {
8 | const classes = useStyles();
9 |
10 | if (!movie) return null;
11 |
12 | return (
13 |
14 |
15 |
22 |
23 |
24 | {movie.title}
25 | {movie.overview}
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | export default FeaturedMovie;
34 |
--------------------------------------------------------------------------------
/src/components/FeaturedMovie/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | featuredCardContainer: {
5 | marginBottom: '20px',
6 | display: 'flex',
7 | justifyContent: 'center',
8 | height: '490px',
9 | textDecoration: 'none',
10 | },
11 | card: {
12 | width: '100%',
13 | display: 'flex',
14 | justifyContent: 'flex-end',
15 | flexDirection: 'column',
16 | },
17 | cardRoot: {
18 | position: 'relative',
19 | },
20 | cardMedia: {
21 | position: 'absolute',
22 | top: 0,
23 | right: 0,
24 | height: '100%',
25 | width: '100%',
26 | backgroundColor: 'rgba(0,0,0,0.575)',
27 | backgroundBlendMode: 'darken',
28 | },
29 | cardContent: {
30 | color: '#fff',
31 | width: '40%',
32 | [theme.breakpoints.down('sm')]: {
33 | width: '100%',
34 | },
35 | },
36 | cardContentRoot: {
37 | position: 'relative',
38 | backgroundColor: 'transparent',
39 | },
40 | }));
41 |
--------------------------------------------------------------------------------
/src/components/Movie/Movie.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Typography, Grid, Grow, Tooltip, Rating } from '@mui/material';
3 | import { Link } from 'react-router-dom';
4 |
5 | import useStyles from './styles';
6 |
7 | function Movie({ movie, i }) {
8 | const classes = useStyles();
9 |
10 | return (
11 |
12 |
13 |
14 |
19 | {movie.title}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | export default Movie;
32 |
--------------------------------------------------------------------------------
/src/components/Movie/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | movie: {
5 | padding: '10px',
6 | },
7 | title: {
8 | color: theme.palette.text.primary,
9 | textOverflow: 'ellipsis',
10 | width: '230px',
11 | overflow: 'hidden',
12 | whiteSpace: 'nowrap',
13 | marginTop: '10px',
14 | marginBottom: 0,
15 | textAlign: 'center',
16 | },
17 | links: {
18 | alignItems: 'center',
19 | fontWeight: 'bolder',
20 | textDecoration: 'none',
21 | [theme.breakpoints.up('xs')]: {
22 | display: 'flex',
23 | flexDirection: 'column',
24 | },
25 | '&:hover': {
26 | cursor: 'pointer',
27 | },
28 | },
29 | image: {
30 | borderRadius: '20px',
31 | height: '300px',
32 | marginBottom: '10px',
33 | '&:hover': {
34 | transform: 'scale(1.05)',
35 | },
36 | },
37 | }));
38 |
--------------------------------------------------------------------------------
/src/components/MovieInfo/MovieInfo.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Modal, Typography, Button, ButtonGroup, Grid, Box, CircularProgress, Rating } from '@mui/material';
3 | import { Movie as MovieIcon, Theaters, Language, PlusOne, Favorite, FavoriteBorderOutlined, Remove, ArrowBack } from '@mui/icons-material';
4 | import { Link, useParams } from 'react-router-dom';
5 | import { useDispatch, useSelector } from 'react-redux';
6 | import axios from 'axios';
7 |
8 | import useStyles from './styles';
9 | import { MovieList } from '../index';
10 | import { useGetMovieQuery, useGetRecommendationsQuery, useGetListQuery } from '../../services/TMDB';
11 | import { selectGenreOrCategory } from '../../features/currentGenreOrCategory';
12 | import genreIcons from '../../assets/genres';
13 |
14 | function MovieInfo() {
15 | const classes = useStyles();
16 | const dispatch = useDispatch();
17 | const { user } = useSelector((state) => state.user);
18 | const { id } = useParams();
19 |
20 | const { data, error, isFetching } = useGetMovieQuery(id);
21 | const { data: favoriteMovies } = useGetListQuery({ listName: 'favorite/movies', accountId: user.id, sessionId: localStorage.getItem('session_id'), page: 1 });
22 | const { data: watchlistMovies } = useGetListQuery({ listName: 'watchlist/movies', accountId: user.id, sessionId: localStorage.getItem('session_id'), page: 1 });
23 | const { data: recommendations } = useGetRecommendationsQuery({ list: '/recommendations', movie_id: id });
24 |
25 | const [open, setOpen] = useState(false);
26 | const [isMovieFavorited, setIsMovieFavorited] = useState(false);
27 | const [isMovieWatchlisted, setIsMovieWatchlisted] = useState(false);
28 |
29 | useEffect(() => {
30 | setIsMovieFavorited(!!favoriteMovies?.results?.find((movie) => movie?.id === data?.id));
31 | }, [favoriteMovies, data]);
32 | useEffect(() => {
33 | setIsMovieWatchlisted(!!watchlistMovies?.results?.find((movie) => movie?.id === data?.id));
34 | }, [watchlistMovies, data]);
35 |
36 | const addToFavorites = async () => {
37 | await axios.post(`https://api.themoviedb.org/3/account/${user.id}/favorite?api_key=${process.env.REACT_APP_TMDB_KEY}&session_id=${localStorage.getItem('session_id')}`, {
38 | media_type: 'movie',
39 | media_id: id,
40 | favorite: !isMovieFavorited,
41 | });
42 |
43 | setIsMovieFavorited((prev) => !prev);
44 | };
45 |
46 | const addToWatchList = async () => {
47 | await axios.post(`https://api.themoviedb.org/3/account/${user.id}/watchlist?api_key=${process.env.REACT_APP_TMDB_KEY}&session_id=${localStorage.getItem('session_id')}`, {
48 | media_type: 'movie',
49 | media_id: id,
50 | watchlist: !isMovieWatchlisted,
51 | });
52 |
53 | setIsMovieWatchlisted((prev) => !prev);
54 | };
55 |
56 | if (isFetching) {
57 | return (
58 |
59 |
60 |
61 | );
62 | }
63 |
64 | if (error) {
65 | return (
66 |
67 | Something went wrong - Go back.
68 |
69 | );
70 | }
71 |
72 | return (
73 |
74 |
75 |
80 |
81 |
82 |
83 | {data?.title} ({data.release_date.split('-')[0]})
84 |
85 |
86 | {data?.tagline}
87 |
88 |
89 |
90 |
91 |
92 | {data?.vote_average} / 10
93 |
94 |
95 | {data?.runtime}min
96 |
97 |
98 | {data?.genres?.map((genre) => (
99 | dispatch(selectGenreOrCategory(genre.id))}>
100 |
101 | {genre?.name}
102 |
103 | ))}
104 |
105 | Overview
106 | {data?.overview}
107 | Top Cast
108 |
109 | {data && data?.credits?.cast?.map((character, i) => (
110 | character.profile_path && (
111 |
112 |
117 | {character?.name}
118 |
119 | {character.character.split('/')[0]}
120 |
121 |
122 | )
123 | )).slice(0, 6)}
124 |
125 |
126 |
127 |
128 |
129 | }>Website
130 | }>IMDB
131 |
132 |
133 |
134 |
135 |
136 | : }>
137 | {isMovieFavorited ? 'Unfavorite' : 'Favorite'}
138 |
139 | : }>
140 | Watchlist
141 |
142 | } sx={{ borderColor: 'primary.main' }}>
143 |
144 | Back
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | You might also like
155 |
156 | {recommendations
157 | ?
158 | : Sorry, nothing was found.}
159 |
160 | setOpen(false)}
165 | >
166 | {data?.videos?.results?.length > 0 && (
167 |
175 | )}
176 |
177 |
178 | );
179 | }
180 |
181 | export default MovieInfo;
182 |
--------------------------------------------------------------------------------
/src/components/MovieInfo/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | containerSpaceAround: {
5 | display: 'flex',
6 | justifyContent: 'space-around',
7 | margin: '10px 0 !important',
8 | [theme.breakpoints.down('sm')]: {
9 | flexDirection: 'column',
10 | flexWrap: 'wrap',
11 | },
12 | },
13 | poster: {
14 | borderRadius: '20px',
15 | boxShadow: '0.5em 1em 1em rgb(64, 64, 70)',
16 | width: '80%',
17 | [theme.breakpoints.down('md')]: {
18 | margin: '0 auto !imporatant',
19 | width: '50%',
20 | },
21 | [theme.breakpoints.down('sm')]: {
22 | margin: '0 auto !imporatant',
23 | width: '100%',
24 | height: '350px',
25 | marginBottom: '30px',
26 | },
27 | },
28 | genresContainer: {
29 | margin: '10px 0 !imaportant',
30 | display: 'flex',
31 | justifyContent: 'space-around',
32 | flexWrap: 'wrap',
33 | },
34 | genreImage: {
35 | filter: theme.palette.mode === 'dark' && 'invert(1)',
36 | marginRight: '10px',
37 | },
38 | links: {
39 | display: 'flex',
40 | justifyContent: 'center',
41 | alignItems: 'center',
42 | textDecoration: 'none',
43 | [theme.breakpoints.down('sm')]: {
44 | padding: '0.5rem 1rem',
45 | },
46 | },
47 | castImage: {
48 | width: '100%',
49 | maxWidth: '7em',
50 | height: '8em',
51 | objectFit: 'cover',
52 | borderRadius: '10px',
53 | },
54 | buttonContainer: {
55 | display: 'flex',
56 | justifyContent: 'space-between',
57 | width: '100%',
58 | [theme.breakpoints.down('sm')]: {
59 | flexDirection: 'column',
60 | },
61 | },
62 | modal: {
63 | display: 'flex',
64 | justifyContent: 'center',
65 | alignItems: 'center',
66 | },
67 | video: {
68 | width: '50%',
69 | height: '50%',
70 | [theme.breakpoints.down('sm')]: {
71 | width: '90%',
72 | height: '90%',
73 | },
74 | },
75 | }));
76 |
--------------------------------------------------------------------------------
/src/components/MovieList/MovieList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Grid } from '@mui/material';
3 |
4 | import useStyles from './styles';
5 | import { Movie } from '../index';
6 |
7 | function MovieList({ movies, numberOfMovies, excludeFirst }) {
8 | const classes = useStyles();
9 | const startFrom = excludeFirst ? 1 : 0;
10 |
11 | return (
12 |
13 | {movies.results.slice(startFrom, numberOfMovies).map((movie, i) => (
14 |
15 | ))}
16 |
17 | );
18 | }
19 |
20 | export default MovieList;
21 |
--------------------------------------------------------------------------------
/src/components/MovieList/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | movieContainer: {
5 | display: 'flex',
6 | justifyContent: 'space-between',
7 | overflow: 'auto',
8 | flexWrap: 'wrap',
9 | [theme.breakpoints.down('sm')]: {
10 | justifyContent: 'center',
11 | },
12 | },
13 | }));
14 |
--------------------------------------------------------------------------------
/src/components/Movies/Movies.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Box, CircularProgress, useMediaQuery, Typography } from '@mui/material';
3 | import { useSelector } from 'react-redux';
4 |
5 | import { MovieList, Pagination, FeaturedMovie } from '../index';
6 | import { useGetMoviesQuery } from '../../services/TMDB';
7 |
8 | function Movies() {
9 | const [page, setPage] = useState(1);
10 | const { genreIdOrCategoryName, searchQuery } = useSelector((state) => state.currentGenreOrCategory);
11 | const { data, error, isFetching } = useGetMoviesQuery({ genreIdOrCategoryName, page, searchQuery });
12 |
13 | const lg = useMediaQuery((theme) => theme.breakpoints.only('lg'));
14 | const numberOfMovies = lg ? 17 : 19;
15 |
16 | if (isFetching) {
17 | return (
18 |
19 |
20 |
21 | );
22 | }
23 |
24 | if (!data.results.length) {
25 | return (
26 |
27 |
28 | No movies that match that name.
29 |
30 | Please searh for something else.
31 |
32 |
33 | );
34 | }
35 |
36 | if (error) return 'An error has occured.';
37 |
38 | return (
39 |
44 | );
45 | }
46 |
47 | export default Movies;
48 |
--------------------------------------------------------------------------------
/src/components/Navbar/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useContext } from 'react';
2 | import { AppBar, IconButton, Toolbar, Drawer, Button, Avatar, useMediaQuery } from '@mui/material';
3 | import { Menu, AccountCircle, Brightness4, Brightness7 } from '@mui/icons-material';
4 | import { useTheme } from '@mui/material/styles';
5 | import { Link } from 'react-router-dom';
6 | import { useDispatch, useSelector } from 'react-redux';
7 |
8 | import useStyles from './styles';
9 | import { Search, Sidebar } from '../index';
10 | import { setUser } from '../../features/auth';
11 | import { fetchToken, createSessionId, moviesApi } from '../../utils/index';
12 | import { ColorModeContext } from '../../utils/ToggleColorMode';
13 |
14 | function Navbar() {
15 | const classes = useStyles();
16 | const isMobile = useMediaQuery('(max-width:600px)');
17 | const theme = useTheme();
18 | const dispatch = useDispatch();
19 | const { isAuthenticated, user } = useSelector((state) => state.user);
20 | const [mobileOpen, setMobileOpen] = useState(false);
21 |
22 | const colorMode = useContext(ColorModeContext);
23 |
24 | const token = localStorage.getItem('request_token');
25 | const sessionIdFromLocalStorage = localStorage.getItem('session_id');
26 |
27 | useEffect(() => {
28 | const logInUser = async () => {
29 | if (token) {
30 | if (sessionIdFromLocalStorage) {
31 | const { data: userData } = await moviesApi.get(`/account?session_id=${sessionIdFromLocalStorage}`);
32 | dispatch(setUser(userData));
33 | } else {
34 | const sessionId = await createSessionId();
35 | const { data: userData } = await moviesApi.get(`/account?session_id=${sessionId}`);
36 | dispatch(setUser(userData));
37 | }
38 | }
39 | };
40 |
41 | logInUser();
42 | }, [token]);
43 |
44 | return (
45 | <>
46 |
47 |
48 | {isMobile && (
49 | setMobileOpen((prevMobileOpen) => !prevMobileOpen)}
54 | className={classes.menuButton}
55 | >
56 |
57 |
58 | )}
59 |
64 | {theme.palette.mode === 'dark' ? : }
65 |
66 | {!isMobile && }
67 |
68 | {!isAuthenticated ? (
69 |
72 | ) : (
73 |
86 | )}
87 |
88 | {isMobile && }
89 |
90 |
91 |
92 |
110 |
111 | >
112 | );
113 | }
114 |
115 | export default Navbar;
116 |
--------------------------------------------------------------------------------
/src/components/Navbar/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | const drawerWidth = 240;
4 |
5 | export default makeStyles((theme) => ({
6 | toolbar: {
7 | height: '80px',
8 | display: 'flex',
9 | justifyContent: 'space-between',
10 | marginLeft: '240px',
11 | [theme.breakpoints.down('sm')]: {
12 | marginLeft: 0,
13 | flexWrap: 'wrap',
14 | },
15 | },
16 | menuButton: {
17 | marginRight: theme.spacing(2),
18 | [theme.breakpoints.up('sm')]: {
19 | display: 'none',
20 | },
21 | },
22 | drawer: {
23 | [theme.breakpoints.up('sm')]: {
24 | width: drawerWidth,
25 | flexShrink: 0,
26 | },
27 | },
28 | drawerPaper: {
29 | width: drawerWidth,
30 | },
31 | linkButton: {
32 | '&:hover': {
33 | color: 'white !important',
34 | textDecoration: 'none',
35 | },
36 | },
37 | }));
38 |
--------------------------------------------------------------------------------
/src/components/Pagination/Pagination.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Typography, Button } from '@mui/material';
3 |
4 | import useStyles from './styles';
5 |
6 | function Pagination({ currentPage, setPage, totalPages }) {
7 | const classes = useStyles();
8 |
9 | const handlePrev = () => {
10 | if (currentPage !== 1) { setPage((prevPage) => prevPage - 1); }
11 | };
12 | const handleNext = () => {
13 | if (currentPage !== totalPages) { setPage((prevPage) => prevPage + 1); }
14 | };
15 |
16 | if (totalPages === 0) return null;
17 |
18 | return (
19 |
20 |
21 | {currentPage}
22 |
23 |
24 | );
25 | }
26 |
27 | export default Pagination;
28 |
--------------------------------------------------------------------------------
/src/components/Pagination/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | container: {
5 | display: 'flex',
6 | justifyContent: 'center',
7 | alignItems: 'center',
8 | },
9 | button: {
10 | margin: '30px 2px',
11 | },
12 | pageNumber: {
13 | margin: '0 20px !important',
14 | color: theme.palette.text.primary,
15 | },
16 | }));
17 |
--------------------------------------------------------------------------------
/src/components/Profile/Profile.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { Box, Button, Typography } from '@mui/material';
3 | import { ExitToApp } from '@mui/icons-material';
4 | import { useSelector } from 'react-redux';
5 |
6 | import { useGetListQuery } from '../../services/TMDB';
7 | import { RatedCards } from '../index';
8 |
9 | function Profile() {
10 | const { user } = useSelector((state) => state.user);
11 | const { data: favoriteMovies, refetch: refetchFavorites } = useGetListQuery({ listName: 'favorite/movies', accountId: user.id, sessionId: localStorage.getItem('session_id'), page: 1 });
12 | const { data: watchlistMovies, refetch: refetchWatchlisted } = useGetListQuery({ listName: 'watchlist/movies', accountId: user.id, sessionId: localStorage.getItem('session_id'), page: 1 });
13 |
14 | useEffect(() => {
15 | refetchFavorites();
16 | refetchWatchlisted();
17 | }, []);
18 |
19 | const logout = () => {
20 | localStorage.clear();
21 | window.location.href = '/';
22 | };
23 |
24 | return (
25 |
26 |
27 | My Profile
28 |
31 |
32 | {!favoriteMovies?.results?.length && !watchlistMovies?.results?.length
33 | ? Add favourite or watchlist same movies to see them here!
34 | : (
35 |
36 |
37 |
38 |
39 | )}
40 |
41 | );
42 | }
43 |
44 | export default Profile;
45 |
--------------------------------------------------------------------------------
/src/components/RatedCards/RatedCards.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Typography, Box } from '@mui/material';
3 |
4 | import useStyles from './styles';
5 | import { Movie } from '../index';
6 |
7 | function RatedCards({ title, movies }) {
8 | const classes = useStyles();
9 |
10 | return (
11 |
12 | {title}
13 |
14 | {movies?.results.map((movie, i) => (
15 |
16 | ))}
17 |
18 |
19 | );
20 | }
21 |
22 | export default RatedCards;
23 |
--------------------------------------------------------------------------------
/src/components/RatedCards/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles(() => ({
4 | container: {
5 | margin: '20px 0',
6 | },
7 | }));
8 |
--------------------------------------------------------------------------------
/src/components/Search/Search.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { TextField, InputAdornment } from '@mui/material';
3 | import { Search as SearchIcon } from '@mui/icons-material';
4 | import { useDispatch } from 'react-redux';
5 |
6 | import useStyles from './styles';
7 | import { searchMovie } from '../../features/currentGenreOrCategory';
8 |
9 | function Search() {
10 | const classes = useStyles();
11 | const [query, setQuery] = useState('');
12 | const dispatch = useDispatch();
13 |
14 | const handleKeyPress = (e) => {
15 | if (e.key === 'Enter') {
16 | dispatch(searchMovie(query));
17 | }
18 | };
19 |
20 | return (
21 |
22 | setQuery(e.target.value)}
26 | variant="standard"
27 | InputProps={{
28 | className: classes.input,
29 | startAdornment: (
30 |
31 |
32 |
33 | ),
34 | }}
35 | />
36 |
37 | );
38 | }
39 |
40 | export default Search;
41 |
--------------------------------------------------------------------------------
/src/components/Search/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | searchContainer: {
5 | [theme.breakpoints.down('sm')]: {
6 | display: 'flex',
7 | justifyContainer: 'center',
8 | width: '100%',
9 | },
10 | },
11 | input: {
12 | color: theme.palette.mode === 'light' && 'dark',
13 | filter: theme.palette.mode === 'light' && 'invert(1)',
14 | [theme.breakpoints.down('sm')]: {
15 | marginTop: '-10px',
16 | marginBottom: '10px',
17 | },
18 | },
19 | }));
20 |
--------------------------------------------------------------------------------
/src/components/Sidebar/Sidebar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { Divider, List, ListItem, ListItemText, ListSubheader, ListItemIcon, Box, CircularProgress } from '@mui/material';
3 | import { Link } from 'react-router-dom';
4 | import { useTheme } from '@mui/styles';
5 | import { useDispatch, useSelector } from 'react-redux';
6 |
7 | import useStyles from './styles';
8 | import { useGetGenresQuery } from '../../services/TMDB';
9 | import { selectGenreOrCategory } from '../../features/currentGenreOrCategory';
10 | import genreIcons from '../../assets/genres';
11 |
12 | const redLogo = 'https://fontmeme.com/permalink/210930/8531c658a743debe1e1aa1a2fc82006e.png';
13 | const blueLogo = 'https://fontmeme.com/permalink/210930/6854ae5c7f76597cf8680e48a2c8a50a.png';
14 |
15 | const categories = [
16 | { label: 'Popular', value: 'popular' },
17 | { label: 'Top Rated', value: 'top_rated' },
18 | { label: 'Upcoming', value: 'upcoming' },
19 | ];
20 |
21 | function Sidebar({ setMobileOpen }) {
22 | const theme = useTheme();
23 | const classes = useStyles();
24 | const dispatch = useDispatch();
25 | const { data, isFetching } = useGetGenresQuery();
26 | const { genreIdOrCategoryName } = useSelector((state) => state.currentGenreOrCategory);
27 |
28 | useEffect(() => {
29 | setMobileOpen(false);
30 | }, [genreIdOrCategoryName]);
31 |
32 | return (
33 | <>
34 |
35 |
40 |
41 |
42 |
43 | Categories
44 | {categories.map(({ label, value }) => (
45 |
46 | dispatch(selectGenreOrCategory(value))}>
47 |
48 |
49 |
50 |
51 |
52 |
53 | ))}
54 |
55 |
56 |
57 | Genres
58 | {isFetching ? (
59 |
60 |
61 |
62 | )
63 | : data?.genres?.map(({ name, id }) => (
64 |
65 | dispatch(selectGenreOrCategory(id))}>
66 |
67 |
68 |
69 |
70 |
71 |
72 | ))}
73 |
74 | >
75 | );
76 | }
77 |
78 | export default Sidebar;
79 |
--------------------------------------------------------------------------------
/src/components/Sidebar/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles((theme) => ({
4 | imageLink: {
5 | display: 'flex',
6 | justifyContent: 'center',
7 | padding: '10% 0',
8 | },
9 | image: {
10 | width: '70%',
11 | },
12 | links: {
13 | color: theme.palette.text.primary,
14 | textDecoration: 'none',
15 | },
16 | genreImages: {
17 | filter: theme.palette.mode === 'dark' ? 'invert(1)' : 'dark',
18 | },
19 | bigText: {
20 | color: 'primary',
21 | fontSize: 30,
22 | },
23 | }));
24 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Actors } from './Actors/Actors';
2 | export { default as Movies } from './Movies/Movies';
3 | export { default as MovieInfo } from './MovieInfo/MovieInfo';
4 | export { default as Profile } from './Profile/Profile';
5 | export { default as Navbar } from './Navbar/Navbar';
6 | export { default as Sidebar } from './Sidebar/Sidebar';
7 | export { default as MovieList } from './MovieList/MovieList';
8 | export { default as Movie } from './Movie/Movie';
9 | export { default as Search } from './Search/Search';
10 | export { default as Pagination } from './Pagination/Pagination';
11 | export { default as RatedCards } from './RatedCards/RatedCards';
12 | export { default as FeaturedMovie } from './FeaturedMovie/FeaturedMovie';
13 |
--------------------------------------------------------------------------------
/src/components/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@mui/styles';
2 |
3 | export default makeStyles(() => ({
4 | root: {
5 | display: 'flex',
6 | height: '100%',
7 | },
8 | content: {
9 | flexGrow: 1,
10 | padding: '6em 2em 2em',
11 | },
12 | toolkit: {
13 | height: '70px',
14 | },
15 | }));
16 |
--------------------------------------------------------------------------------
/src/features/auth.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | const initialState = {
4 | user: {},
5 | isAuthenticated: false,
6 | sessionId: '',
7 | };
8 |
9 | const authSlice = createSlice({
10 | name: 'user',
11 | initialState,
12 | reducers: {
13 | setUser: (state, action) => {
14 | state.user = action.payload;
15 | state.isAuthenticated = true;
16 | state.sessionId = localStorage.getItem('session_id');
17 |
18 | localStorage.setItem('accountId', action.payload.id);
19 | },
20 | },
21 | });
22 |
23 | export const { setUser } = authSlice.actions;
24 |
25 | export default authSlice.reducer;
26 |
--------------------------------------------------------------------------------
/src/features/currentGenreOrCategory.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | export const genreOrCategory = createSlice({
4 | name: 'genreOrCategory',
5 | initialState: {
6 | genreIdOrCategoryName: '',
7 | page: 1,
8 | searchQuery: '',
9 | },
10 | reducers: {
11 | selectGenreOrCategory: (state, action) => {
12 | state.genreIdOrCategoryName = action.payload;
13 | state.searchQuery = '';
14 | },
15 | searchMovie: (state, action) => {
16 | state.searchQuery = action.payload;
17 | },
18 | },
19 | });
20 |
21 | export const { selectGenreOrCategory, searchMovie } = genreOrCategory.actions;
22 |
23 | export default genreOrCategory.reducer;
24 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | ::-webkit-scrollbar {
2 | width: 7px;
3 | }
4 |
5 | ::-webkit-scrollbar-track {
6 | background: #cfcfcf;
7 | }
8 |
9 | ::-webkit-scrollbar-thumb {
10 | background: #a5a5a5;
11 | }
12 |
13 | * {
14 | text-decoration: none;
15 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import { Provider } from 'react-redux';
5 | import store from './app/store';
6 | import ToggleColorMode from './utils/ToggleColorMode';
7 | import App from './components/App';
8 | import './index.css';
9 |
10 | const root = ReactDOM.createRoot(document.getElementById('root'));
11 | root.render(
12 |
13 |
14 |
15 |
16 |
17 |
18 | ,
19 | );
20 |
--------------------------------------------------------------------------------
/src/services/TMDB.js:
--------------------------------------------------------------------------------
1 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
2 |
3 | const tmdbApiKey = process.env.REACT_APP_TMDB_KEY;
4 |
5 | export const tmdbApi = createApi({
6 | reducerPath: 'tmdbApi',
7 | baseQuery: fetchBaseQuery({ baseUrl: 'https://api.themoviedb.org/3' }),
8 | endpoints: (builder) => ({
9 |
10 | // Get Genres
11 | getGenres: builder.query({
12 | query: () => `/genre/movie/list?api_key=${tmdbApiKey}`,
13 | }),
14 |
15 | // Get Movies by [Type]
16 | getMovies: builder.query({
17 | query: ({ genreIdOrCategoryName, page, searchQuery }) => {
18 | // Get Movies by Search
19 | if (searchQuery) {
20 | return `/search/movie?query=${searchQuery}&page=${page}&api_key=${tmdbApiKey}`;
21 | }
22 |
23 | // Get Movies by Category
24 | if (genreIdOrCategoryName && typeof genreIdOrCategoryName === 'string') {
25 | return `/movie/${genreIdOrCategoryName}?page=${page}&api_key=${tmdbApiKey}`;
26 | }
27 |
28 | // Get Movies by Genre
29 | if (genreIdOrCategoryName && typeof genreIdOrCategoryName === 'number') {
30 | return `discover/movie?with_genres=${genreIdOrCategoryName}&page=${page}&api_key=${tmdbApiKey}`;
31 | }
32 |
33 | // Get popular movies by default
34 | return `/movie/popular?page=${page}&api_key=${tmdbApiKey}`;
35 | },
36 | }),
37 |
38 | // Get Movie
39 | getMovie: builder.query({
40 | query: (id) => `/movie/${id}?append_to_response=videos,credits&api_key=${tmdbApiKey}`,
41 | }),
42 |
43 | // Get Recommendations
44 | getRecommendations: builder.query({
45 | query: ({ movie_id, list }) => `/movie/${movie_id}/${list}?api_key=${tmdbApiKey}`,
46 | }),
47 |
48 | // Get Actor
49 | getActor: builder.query({
50 | query: (id) => `person/${id}?api_key=${tmdbApiKey}`,
51 | }),
52 |
53 | // Get Movies by Actor
54 | getMoviesByActorId: builder.query({
55 | query: ({ id, page }) => `/discover/movie?with_cast=${id}&page=${page}&api_key=${tmdbApiKey}`,
56 | }),
57 |
58 | // Get User Specific Lists
59 | getList: builder.query({
60 | query: ({ listName, accountId, sessionId, page }) => `/account/${accountId}/${listName}?api_key=${tmdbApiKey}&session_id=${sessionId}&page=${page}`,
61 | }),
62 | }),
63 | });
64 |
65 | export const {
66 | useGetGenresQuery,
67 | useGetMoviesQuery,
68 | useGetMovieQuery,
69 | useGetRecommendationsQuery,
70 | useGetActorQuery,
71 | useGetMoviesByActorIdQuery,
72 | useGetListQuery,
73 | } = tmdbApi;
74 |
--------------------------------------------------------------------------------
/src/utils/ToggleColorMode.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useMemo, createContext } from 'react';
2 | import { ThemeProvider, createTheme } from '@mui/material/styles';
3 |
4 | export const ColorModeContext = createContext();
5 |
6 | function ToggleColorMode({ children }) {
7 | const [mode, setMode] = useState('light');
8 |
9 | const toggleColorMode = () => {
10 | setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
11 | };
12 |
13 | const theme = useMemo(() => createTheme({
14 | palette: {
15 | mode,
16 | },
17 | }), [mode]);
18 |
19 | return (
20 |
21 |
22 | {children}
23 |
24 |
25 | );
26 | }
27 |
28 | export default ToggleColorMode;
29 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const moviesApi = axios.create({
4 | baseURL: 'https://api.themoviedb.org/3',
5 | params: {
6 | api_key: process.env.REACT_APP_TMDB_KEY,
7 | },
8 | });
9 |
10 | export const fetchToken = async () => {
11 | try {
12 | const { data } = await moviesApi.get('/authentication/token/new');
13 |
14 | const token = data.request_token;
15 |
16 | if (data.success) {
17 | localStorage.setItem('request_token', token);
18 | window.location.href = `https://www.themoviedb.org/authenticate/${token}?redirect_to=${window.location.origin}/approved`;
19 | }
20 | } catch (error) {
21 | console.log('Sorry, your token could not be created.');
22 | }
23 | };
24 |
25 | export const createSessionId = async () => {
26 | const token = localStorage.getItem('request_token');
27 |
28 | if (token) {
29 | try {
30 | const { data: { session_id } } = await moviesApi.post('authentication/session/new', {
31 | request_token: token,
32 | });
33 | localStorage.setItem('session_id', session_id);
34 |
35 | return session_id;
36 | } catch (error) {
37 | console.log(error);
38 | }
39 | }
40 | };
41 |
--------------------------------------------------------------------------------