├── .editorconfig ├── .env-example ├── .firebase └── hosting.YnVpbGQ.cache ├── .firebaserc ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── firebase.json ├── package-lock.json ├── package.json ├── public ├── apple-touch-icon.png ├── avatar-blue.jpeg ├── avatar-red-1000x1000.jpeg ├── avatar-red.jpeg ├── blur.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── netflix-gpt.png ├── netflix-gpt.psd ├── ngpt-red-300x71-01.png ├── ngpt-red-300x71.png ├── ngpt-white-300x71.png ├── no_movie_poster.png ├── poster-bg.jpeg ├── robots.txt ├── screenshot │ ├── 01-Landing.png │ ├── 02-Signin.png │ ├── 03-Signup.png │ ├── 04-Browse.png │ ├── 05-Movie-List.png │ ├── 06-Shimmer-loading.png │ ├── 07-Search.png │ └── 08-Watch.png └── site.webmanifest ├── src ├── App.css ├── App.js ├── App.test.js ├── NotFound.css ├── auth │ ├── authenticate.js │ └── register.js ├── components │ ├── GenreList.js │ ├── GptSearchBar.js │ ├── MovieCard.js │ ├── MovieCardHover.js │ ├── MovieList.js │ ├── MovieSlider.js │ ├── MovieSliderShimmer.js │ ├── NotFoundPage.js │ ├── SearchCard.js │ ├── SearchResult.js │ ├── Showcase.js │ ├── ShowcaseShimmer.js │ ├── SignInForm.js │ ├── SignUpForm.js │ ├── Spinner.js │ └── VideoBackground.js ├── hooks │ ├── useFontLoader.js │ ├── useMovie.js │ ├── usePlayer.js │ ├── useSearch.js │ ├── useShowCase.js │ └── useTrending.js ├── index.css ├── index.js ├── layout │ ├── AppLayout.js │ ├── DefaultLayout.js │ ├── DefaultNavbar.js │ ├── Footer.js │ ├── Layout.js │ └── Navbar.js ├── pages │ ├── About.js │ ├── Browse.js │ ├── Home.js │ ├── Latest.js │ ├── Movies.js │ ├── Profile.js │ ├── Search.js │ ├── SignIn.js │ ├── SignUp.js │ ├── TvShows.js │ └── Watch.js ├── reportWebVitals.js ├── router │ ├── router.js │ └── routes.js ├── services │ ├── firebase.js │ ├── openai.js │ └── tmdb.js ├── setupTests.js ├── stores │ ├── appSlice.js │ ├── appStore.js │ ├── authSlice.js │ ├── moviesSlice.js │ ├── playerSlice.js │ ├── searchSlice.js │ ├── showCaseSlice.js │ ├── trendingSlice.js │ └── userSlice.js ├── utils │ ├── constants.js │ ├── debounce.js │ └── getGenresName.js └── validator │ └── validate.js └── tailwind.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.env-example: -------------------------------------------------------------------------------- 1 | REACT_APP_BASE_URL = YOUR_APPLICATION_BASE_URL 2 | REACT_APP_FIREBASE_KEY = YOUR_API_KEY_WILL_HERE 3 | REACT_APP_FIREBASE_APP_ID = YOUR_FIREBASE_APP_ID_WIL_HERE 4 | 5 | REACT_APP_OPENAI_KEY=YOUR_API_KEY_WILL_HERE 6 | REACT_APP_TMDB_KEY=YOUR_API_KEY_WILL_HERE 7 | -------------------------------------------------------------------------------- /.firebase/hosting.YnVpbGQ.cache: -------------------------------------------------------------------------------- 1 | asset-manifest.json,1699887270462,d300cbadab1122d21ce4829a33c097ffe3cca74defe3cc2756eada9acccbbf75 2 | avatar-red.jpeg,1699887261743,a65523e330ea3592150b4c63c1ad2c41843109a26ae932972003447b56cf8df6 3 | favicon-16x16.png,1699887261745,d8f2b2a2997ab1e4378320038b0f0942d7925f62bd3ff947f860635ca4b236fa 4 | apple-touch-icon.png,1699887261741,cc447d7d0bde355ed4b3abff1fa078315814e690918d7091007ac19024d31d44 5 | logo192.png,1699887261746,daa73c437f8704bda6f0653811043231421e29fac90a1a3567a719f27a861f5c 6 | favicon-32x32.png,1699887261746,02bf6c3a31198f9e6cff9e039cc753899daff4fa31efec72681e13ccac79ffe4 7 | index.html,1699887270446,0a2aefa421840fbc63aff0139e37bf8bb8ec1492722909d211aab44a2a5e341d 8 | favicon.ico,1699887261746,379df450a7b0d1bf72ac2a2395e74bb9546db1b5027d886a57664f80467a25b7 9 | manifest.json,1699887261747,9731cc6e32f7d0fd1d74b8523d8c1865a257f4b2eb93d473fdd013ded6632475 10 | netflix-gpt.png,1699887261748,8628ef9600fc5393af99193616fd2ea4ef43e2afc4719a2b131b71f6604dafd8 11 | avatar-blue.jpeg,1699887261742,e0571406a1c75b619cf91e9ca2ad19db738da1f5d9a95ea24860904bc58078e6 12 | ngpt-red-300x71.png,1699887261750,399b622ac994919271ea1cb3a332fd9843a0329b4c0ad8a8329c4d2b193d8009 13 | site.webmanifest,1699887261778,438eab4e1bab8df855faa047301dcd8cce4c8a815a1527502b0efc2270ad77fd 14 | no_movie_poster.png,1699887261751,540d98a5e9930611d710b8ea1bc0a2a2cf9a341efcdfcfb59c8587871593dc9a 15 | ngpt-white-300x71.png,1699887261751,6528a56a48066a036a5c137d979f85f5ad0f45705e7ac1f45b1992223a11c4bd 16 | avatar-red-1000x1000.jpeg,1699887261742,4ee3c9d691fa927c844612623c3f36792aa135e37ef8e4d1fcf72c69730007b8 17 | robots.txt,1699887261752,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2 18 | static/css/945.baa28464.chunk.css,1699887270448,cb54eb7d4ac0e8719baf2da28e97d879884cb879701dedf8d1dc9a3237972232 19 | static/css/945.baa28464.chunk.css.map,1699887270456,8c5e4c6ee839310d69ffc85b793988512bf95cd7be12f1d5f2a921a0594f91d0 20 | static/js/266.50579e0a.chunk.js,1699887270448,1c9e9c63cb4be856af09ebdcc21ee34ba4f63252de59263f50dc4a7272a601c5 21 | static/js/266.50579e0a.chunk.js.map,1699887270456,3eb4bf8d0e3c994224b306d34d946733caf8f3af61dccc38fd9c7c490e95229c 22 | static/js/291.a2f82206.chunk.js,1699887270448,62512df04c0617c651394a3385b6765af1129f941f140d59072258c55f3d4cfa 23 | static/js/291.a2f82206.chunk.js.map,1699887270456,8713c5c9170091972c2145fac314fcc8d1e3dbdb9c26f14db24449f2d8c1fa5b 24 | static/js/478.768ac69c.chunk.js,1699887270448,7a8a4d59edf7efa1b32e6cbc7d3576d39a50169e720525815f7f5d6ec20457c4 25 | static/js/549.c853e248.chunk.js.LICENSE.txt,1699887270448,92df3d42027fea6c771952d5654d2b7b1c0aa18030191542b0b79617669314cb 26 | static/js/674.17fc5cb5.chunk.js,1699887270448,fd3b96136cb61699daad23af8efb71b59b8a4244988cec09d0f8015d60e6a379 27 | static/js/674.17fc5cb5.chunk.js.map,1699887270457,47a601ce944e61e99f62fb60431cdf65301f942525e3b8db2b493a19adf3a250 28 | static/css/main.3a3b8bec.css,1699887270448,fdab655dc1adbf2ffad5415e61b4202145df8f1de8bb40ca27397c7726c3cca4 29 | static/js/700.826a9e1b.chunk.js,1699887270447,ae5a05d47388dbd38fdaf243fcd58b60db5f88975a321867d945f176c5f7f332 30 | static/js/700.826a9e1b.chunk.js.map,1699887270457,08476613fdf8dd73685bf62a3cdc7e07fad458c0f9d4704e9ff8c27ffcb0e71a 31 | static/css/main.3a3b8bec.css.map,1699887270456,9e24143254b43326ed6409abae7e86ce5f8e62275f7cf0164143c0b111998cf7 32 | static/js/787.8e11e584.chunk.js,1699887270456,0e4f804981c1a7cbe1d871d33e7bf989a0a6a7d6613822dbdc8a0916ca1c881f 33 | static/js/764.1902ec35.chunk.js.map,1699887270462,af25ffc282af9da0d2e2fb8c9c82b4dff0cef26448140b9550606298423f7dad 34 | static/js/478.768ac69c.chunk.js.map,1699887270457,d21d2862d033a1151cd4275492d095329631e0b4e41fecd82934fa1a146409c2 35 | static/js/764.1902ec35.chunk.js,1699887270448,029ee3cba62ec912f8f36a08afcce51790c793c86a18ebab2db222753ff86826 36 | ngpt-red-300x71-01.png,1699887261750,799d627332c55893f0e1d9d6666a30395d32c0f057f43ad00679a0115b330e2d 37 | screenshot/06-Shimmer-loading.png,1699887261773,22a5953e20ca7e7a9b1ff73ccc7f9e79420442741b96ba8f98d5f8add77bc3ad 38 | static/js/787.8e11e584.chunk.js.map,1699887270463,bd58d13d5312bd6291f66aa7538000a654dd2fc48099fae2b81e911c21d11c4e 39 | static/js/870.59a7795a.chunk.js,1699887270448,96c49cc2ccc0d163d1756b4559f5291c4833551f8e0be068b1ce0e99814969da 40 | static/js/876.c1ee6734.chunk.js,1699887270447,e50c249b18fd6d0b75d7f8dca60c76a95877c0a6cb56fc781ec639cd4aeb3d26 41 | static/js/876.c1ee6734.chunk.js.map,1699887270457,30d6d585b1c63af4fb7a746f72233d3fd0517c9f523bc2133fd7dccc546208df 42 | static/js/937.b499db48.chunk.js,1699887270447,757949d3187c5ee5370e9b58b46bf8dabd4caf619e969e8a9599160211bcaaa9 43 | static/js/870.59a7795a.chunk.js.map,1699887270463,4384238794e15ade6b76b3da3ad7e4de3805a6dcfd0800cd0d7714f426380e92 44 | static/js/859.fade9dc8.chunk.js,1699887270456,f810baaccae491f428cf7229e06d8abcea9c1169b75413ce896358527bc0f47a 45 | static/js/945.4928754f.chunk.js,1699887270447,adf6e734f4653f908ba4d7e7bda76b5c1a6015ca6ab145fcc5de63fa76e25284 46 | static/js/964.d23b4f6d.chunk.js,1699887270456,28a9c32aa2010d3d5b56febf53480da6fd566f13a021fffe4fa339e679b0f0e2 47 | static/js/937.b499db48.chunk.js.map,1699887270457,81bf4d3a9206cea5c708ac32e9f178cbdace576ce36e6f4a9421b9fe56cf4ea0 48 | static/js/964.d23b4f6d.chunk.js.map,1699887270463,ec5d77dc856ca093fd64f0374429f97be236d2727e8e715ab02f0db002ba018e 49 | static/js/main.3d676668.js.LICENSE.txt,1699887270448,6e629ecfea3ae1f48f2f2e08e52abcac05b4418c629fb4b3b86a06a53c54e62f 50 | static/js/945.4928754f.chunk.js.map,1699887270456,994cf7aafaf6987451505f383134ded608107b004ca9914eb7b2a7566de54ce0 51 | logo512.png,1699887261747,33ce2e3312132c09602b877fdafbdf1fff6e682e7284a85a49fcf40d1e7f4f79 52 | static/js/549.c853e248.chunk.js,1699887270456,0191b516d9bb4c0bc4b4b5e4da77807c13b682421c1f5ecfc69852165a68f55e 53 | blur.png,1699887261745,91f54c319a44963b23c916c738bd378fe8459857f4d53412399cb775651fc0c3 54 | static/js/859.fade9dc8.chunk.js.map,1699887270463,0bf7f380926819fd13eaaf5b48d4c70f327da2182a5656b2a604a5d8f02e5cf3 55 | static/js/main.3d676668.js,1699887270447,e495d2efd6f48f26e828e2e41c64c44b1b0efe08cc0c26001b20fe9fba3509b2 56 | poster-bg.jpeg,1699887261752,936849e45414ed194173b19a88ccf787b6be7ede0cd2197fecf36ea250c077e0 57 | netflix-gpt.psd,1699887261749,72ed367082d38c4b7c6459855360e6ad58276b2120001e63fc4c3e6fd0fa0042 58 | screenshot/07-Search.png,1699887261774,9f45c276fba92ee71ac86100890e5ca8f2ce11dc94eb14d0568ff57e2ce943cf 59 | screenshot/08-Watch.png,1699887261777,15be7b591b03200b7d6ed017e3ad1a085fc84e157f01d5b810684b5d2aca27c1 60 | screenshot/04-Browse.png,1699887261767,9f8e83c093200e7c4bbbcb570f249da2b4f97507274393bbce45d82695d4d8f9 61 | screenshot/05-Movie-List.png,1699887261769,fc9de8b3eff16dabf254cea88800742b68ea8fc6846fe6a6d63084781a8c6a38 62 | static/js/549.c853e248.chunk.js.map,1699887270463,82feb1c1b75a68d8d10b378913afc2a841fd175367e25d5e1883ad5413db9657 63 | screenshot/02-Signin.png,1699887261761,ad6d73b9706678ddbb3016ecb264bafa82b62f758ff9ca7770468f5f4eb81f8d 64 | screenshot/03-Signup.png,1699887261763,cef2fd18a812bcadc5ce8d26400fb6a42efe5d4e9c8960ab0cee2144b7bc13a8 65 | screenshot/01-Landing.png,1699887261756,79e3df083450fbbce4f8808d5d7eca3ca2bd62607ebc3c9f89ed17e305eea85a 66 | static/js/main.3d676668.js.map,1699887270456,12bfc1fc7234424f2e1bc95b18b60f22e9fe801ebde24a9687941821f8f30996 67 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "netflixgpt-aaf25" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.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 17 | .env.local 18 | .env.development 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Transcriptase 2 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 3 | 4 | - Reporting a bug 5 | - Discussing the current state of the code 6 | - Submitting a fix 7 | - Proposing new features 8 | - Becoming a maintainer 9 | 10 | ## We Develop with Github 11 | We use github to host code, to track issues and feature requests, as well as accept pull requests. 12 | 13 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests 14 | Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests: 15 | 16 | 1. Fork the repo and create your branch from `master`. 17 | 2. If you've added code that should be tested, add tests. 18 | 3. If you've changed APIs, update the documentation. 19 | 4. Ensure the test suite passes. 20 | 5. Make sure your code lints. 21 | 6. Issue that pull request! 22 | 23 | ## Any contributions you make will be under the MIT Software License 24 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 25 | 26 | ## Report bugs using Github's [issues](https://github.com/okNeeraj/netflix-gpt/issues) 27 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy! 28 | 29 | **Great Bug Reports** tend to have: 30 | 31 | - A quick summary and/or background 32 | - Steps to reproduce 33 | - Be specific! 34 | - What you expected would happen 35 | - What actually happens 36 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 37 | 38 | People *love* thorough bug reports. I'm not even kidding. 39 | 40 | ## Use a Consistent Coding Style 41 | 42 | * 2 spaces for indentation rather than tabs 43 | * You can try running `npm run lint` for style unification 44 | 45 | ## License 46 | By contributing, you agree that your contributions will be licensed under its MIT License. 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Neeraj Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Netflix GPT 2 | 3 | Movies recomandation with AI. 4 | 5 | ## Setup 6 | 7 | - Install react app using create-react-app (CRA) 8 | 9 | ```js 10 | npx create-react-app netflix-gpt 11 | ``` 12 | 13 | - Create `.env` file and put configure 14 | 15 | ```js 16 | REACT_APP_BASE_URL = YOUR_APPLICATION_BASE_URL; // http://localhost:300 17 | REACT_APP_OPENAI_KEY = YOUR_API_KEY_WILL_HERE; 18 | REACT_APP_TMDB_KEY = YOUR_API_KEY_WILL_HERE; 19 | ``` 20 | 21 | - Install and init tailwind css 22 | 23 | ```js 24 | npm install -D tailwindcss 25 | npx tailwindcss init 26 | ``` 27 | 28 | - Configure tailwind css in your project 29 | 30 | `npx tailwindcss init` command will create a file `tailwind.config.js` in your project's root directory. 31 | Open `tailwind.config.js` and replace all content with below code. 32 | 33 | ```js 34 | /** @type {import('tailwindcss').Config} */ 35 | module.exports = { 36 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 37 | theme: { 38 | extend: {}, 39 | }, 40 | plugins: [], 41 | }; 42 | ``` 43 | 44 | - Add the @tailwind directives for each of Tailwind’s layers to your ./src/index.css file. 45 | 46 | ```css 47 | @tailwind base; 48 | @tailwind components; 49 | @tailwind utilities; 50 | ``` 51 | 52 | - Now you created a react app with tailwind css successfully. Now run the below command on your terminal to start your local development server. 53 | 54 | ```js 55 | npm start 56 | ``` 57 | 58 | ## Features 59 | 60 | - Home Page (is user !authorised) 61 | 62 | - Signin/Signup Page 63 | - SignInForm / SignUpForm 64 | 65 | - Browse Page 66 | 67 | - Navbar 68 | - Showcase 69 | - Trendings 70 | - MoviesSuggestion 71 | - MoviesList \* N 72 | 73 | - NetflixGPT 74 | - Search 75 | - MoviesSuggestion 76 | 77 | ## Screen Shot 78 | 79 | Live Demo : [Live Demo](https://okneeraj.github.io/netflix-gpt "Live Demo") 80 | 81 | ## Screen Shot 82 | 83 | - Landing Page 84 | 85 | ![Landing Page](https://okneeraj.github.io/netflix-gpt/screenshot/01-Landing.png) 86 | 87 | - Signin Page 88 | 89 | ![Signin Page](https://okneeraj.github.io/netflix-gpt/screenshot/02-Signin.png) 90 | 91 | - Signup Page 92 | 93 | ![Signup Page](https://okneeraj.github.io/netflix-gpt/screenshot/03-Signup.png) 94 | 95 | - Browse Page 96 | 97 | ![Browse Page](https://okneeraj.github.io/netflix-gpt/screenshot/04-Browse.png) 98 | 99 | - Movie List 100 | 101 | ![Movie List](https://okneeraj.github.io/netflix-gpt/screenshot/05-Movie-List.png) 102 | 103 | - Shimmer Loading 104 | 105 | ![Shimmer Loading](https://okneeraj.github.io/netflix-gpt/screenshot/06-Shimmer-loading.png) 106 | 107 | - Search Page 108 | 109 | ![Search Page](https://okneeraj.github.io/netflix-gpt/screenshot/07-Search.png) 110 | 111 | - Watch Now Page 112 | 113 | ![Watch Now Page](https://okneeraj.github.io/netflix-gpt/screenshot/08-Watch.png) 114 | 115 | # 💖 Support This Project 116 | 117 | Thank you for taking the time to explore NetflixGPT! This project represents an in-depth implementation of features extracted from the inspiring course "Namaste-React" by Akshay Saini. It's been a rewarding journey, and I'm genuinely grateful for the opportunity to create and share this with the community. 118 | 119 | I want to express my heartfelt thanks to everyone who has shown interest and provided feedback. Your support and involvement mean a lot to me. 120 | 121 | If you have any questions, suggestions, or just want to connect, feel free to reach out. 122 | 123 | `` 124 | 125 | ## 🙏 Thank You 🙏 126 | 127 | Feel free to modify it to suit the tone and style of your project. The goal is to encourage participation, collaboration and learning. 128 | 129 | Made with ❤️ and React. 130 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 1.0.x | :white_check_mark: | 8 | 9 | 10 | ### New features 11 | 12 | New features will only be added to the master branch and will not be made available in point releases. 13 | 14 | ### Bug fixes 15 | 16 | Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from. 17 | 18 | ### Security issues 19 | 20 | Only the latest release series will receive patches and new versions in case of a security issue. 21 | 22 | ### Severe security issues 23 | 24 | For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team. 25 | 26 | ### Unsupported Release Series 27 | 28 | When a release series is no longer supported, it's your own responsibility to deal with bugs and security issues. If you are not comfortable maintaining your own versions, you should upgrade to a supported version. 29 | 30 | ## Reporting a Vulnerability 31 | 32 | To report a security vulnerability, please follow these steps: 33 | 34 | 1. Do not create a public GitHub issue. Security issues are sensitive, and we want to ensure they are handled appropriately. 35 | 2. Contact us privately via email at [okneerajsingh@gmail.com]. 36 | 3. Provide as much information as possible to help us understand and reproduce the issue. This may include steps to reproduce, affected versions, and any potential mitigations. 37 | 4. Allow us a reasonable amount of time to investigate and address the issue before disclosing it publicly. 38 | 39 | We appreciate your help in keeping the Netflix-GPT project secure. 40 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build", 4 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], 5 | "rewrites": [ 6 | { 7 | "source": "**", 8 | "destination": "/index.html" 9 | } 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netflix-gpt", 3 | "version": "0.1.0", 4 | "homepage": "https://okneeraj.github.io/netflix-gpt", 5 | "private": true, 6 | "dependencies": { 7 | "@emotion/react": "^11.11.1", 8 | "@emotion/styled": "^11.11.0", 9 | "@loadable/component": "^5.15.3", 10 | "@mui/icons-material": "^5.14.19", 11 | "@mui/material": "^5.14.19", 12 | "@testing-library/jest-dom": "^5.17.0", 13 | "@testing-library/react": "^13.4.0", 14 | "@testing-library/user-event": "^13.5.0", 15 | "firebase": "^10.4.0", 16 | "openai": "^4.11.0", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "react-lazy-load-image-component": "^1.6.0", 20 | "react-redux": "^8.1.2", 21 | "react-router-dom": "^6.16.0", 22 | "react-scripts": "5.0.1", 23 | "react-youtube": "^10.1.0", 24 | "swiper": "^10.3.1", 25 | "web-vitals": "^2.1.4", 26 | "youtube-player": "^5.6.0" 27 | }, 28 | "scripts": { 29 | "start": "react-scripts start", 30 | "build": "react-scripts build", 31 | "test": "react-scripts test", 32 | "eject": "react-scripts eject", 33 | "predeploy": "npm run build", 34 | "deploy": "gh-pages -d build" 35 | }, 36 | "eslintConfig": { 37 | "extends": [ 38 | "react-app", 39 | "react-app/jest" 40 | ] 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "@reduxjs/toolkit": "^1.9.5", 56 | "gh-pages": "^6.0.0", 57 | "tailwindcss": "^3.3.3" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/avatar-blue.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/avatar-blue.jpeg -------------------------------------------------------------------------------- /public/avatar-red-1000x1000.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/avatar-red-1000x1000.jpeg -------------------------------------------------------------------------------- /public/avatar-red.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/avatar-red.jpeg -------------------------------------------------------------------------------- /public/blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/blur.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 30 | 31 | 40 | NetflixGPT - Your Netflix Assistant 41 | 42 | 43 | 44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "NetflixGPT", 3 | "name": "NetflixGPT - Movie Recommandation AI", 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": "#e50914", 24 | "background_color": "rgba(20, 20, 20, 0.9)", 25 | "background": { 26 | "theme_color": "#e50914", 27 | "theme_color_state": "opaque", 28 | "maskable_icon": false, 29 | "image": "blur.png", 30 | "color": "white" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /public/netflix-gpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/netflix-gpt.png -------------------------------------------------------------------------------- /public/netflix-gpt.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/netflix-gpt.psd -------------------------------------------------------------------------------- /public/ngpt-red-300x71-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/ngpt-red-300x71-01.png -------------------------------------------------------------------------------- /public/ngpt-red-300x71.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/ngpt-red-300x71.png -------------------------------------------------------------------------------- /public/ngpt-white-300x71.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/ngpt-white-300x71.png -------------------------------------------------------------------------------- /public/no_movie_poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/no_movie_poster.png -------------------------------------------------------------------------------- /public/poster-bg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/poster-bg.jpeg -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/screenshot/01-Landing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/screenshot/01-Landing.png -------------------------------------------------------------------------------- /public/screenshot/02-Signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/screenshot/02-Signin.png -------------------------------------------------------------------------------- /public/screenshot/03-Signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/screenshot/03-Signup.png -------------------------------------------------------------------------------- /public/screenshot/04-Browse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/screenshot/04-Browse.png -------------------------------------------------------------------------------- /public/screenshot/05-Movie-List.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/screenshot/05-Movie-List.png -------------------------------------------------------------------------------- /public/screenshot/06-Shimmer-loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/screenshot/06-Shimmer-loading.png -------------------------------------------------------------------------------- /public/screenshot/07-Search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/screenshot/07-Search.png -------------------------------------------------------------------------------- /public/screenshot/08-Watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okNeeraj/netflix-gpt/0b9841ebaa9c4fff7560c9f7abe14be6ecf75a0b/public/screenshot/08-Watch.png -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .icon-line { 2 | font-variation-settings: 3 | 'FILL' 0, 4 | 'wght' 300, 5 | 'GRAD' 0, 6 | 'opsz' 24; 7 | font-family: "Material Symbols Outlined"; 8 | font-weight: normal; 9 | font-style: normal; 10 | /* font-size: 24px; */ 11 | line-height: 1; 12 | letter-spacing: normal; 13 | text-transform: none; 14 | /* display: inline-block; */ 15 | white-space: nowrap; 16 | word-wrap: normal; 17 | direction: ltr; 18 | -webkit-font-feature-settings: "liga"; 19 | -webkit-font-smoothing: antialiased; 20 | } 21 | 22 | .icon-fill { 23 | font-variation-settings: 24 | 'FILL' 1, 25 | 'wght' 300, 26 | 'GRAD' 0, 27 | 'opsz' 24; 28 | font-family: "Material Symbols Outlined"; 29 | font-weight: normal; 30 | font-style: normal; 31 | /* font-size: 24px; */ 32 | line-height: 1; 33 | letter-spacing: normal; 34 | text-transform: none; 35 | /* display: inline-block; */ 36 | white-space: nowrap; 37 | word-wrap: normal; 38 | direction: ltr; 39 | -webkit-font-feature-settings: "liga"; 40 | -webkit-font-smoothing: antialiased; 41 | } 42 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router-dom'; 2 | import { Provider } from 'react-redux'; 3 | 4 | import './App.css'; 5 | import Layout from './layout/Layout'; 6 | import appStore from './stores/appStore'; 7 | 8 | const App = () => { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | } 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/NotFound.css: -------------------------------------------------------------------------------- 1 | /*====================== 2 | 404 page 3 | =======================*/ 4 | 5 | .page_404 { 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | height: 100vh; 10 | padding: 40px 0; 11 | background: #fff; 12 | color: #000; 13 | font-family: 'Arial', serif; 14 | } 15 | 16 | .page_404 img { 17 | width: 100%; 18 | } 19 | 20 | .four_zero_four_bg { 21 | 22 | background-image: url(https://cdn.dribbble.com/users/285475/screenshots/2083086/dribbble_1.gif); 23 | height: 400px; 24 | background-position: center; 25 | } 26 | 27 | 28 | .four_zero_four_bg h1 { 29 | font-size: 80px; 30 | } 31 | 32 | .four_zero_four_bg h3 { 33 | font-size: 80px; 34 | } 35 | 36 | .link_404 { 37 | color: #fff !important; 38 | padding: 10px 20px; 39 | background: #e50815; 40 | margin: 20px 0; 41 | display: inline-block; 42 | } 43 | 44 | .contant_box_404 { 45 | margin-top: -50px; 46 | } 47 | -------------------------------------------------------------------------------- /src/auth/authenticate.js: -------------------------------------------------------------------------------- 1 | import { signInWithEmailAndPassword } from "firebase/auth"; 2 | import { auth } from "../services/firebase"; 3 | 4 | const authenticate = async (email, password) => { 5 | const response = {}; 6 | try { 7 | const userCredential = await signInWithEmailAndPassword(auth, email, password); 8 | const user = userCredential.user; 9 | response.user = user; 10 | } catch (error) { 11 | response.error = error; 12 | } 13 | return response; 14 | } 15 | 16 | export default authenticate; 17 | -------------------------------------------------------------------------------- /src/auth/register.js: -------------------------------------------------------------------------------- 1 | import { createUserWithEmailAndPassword } from "firebase/auth"; 2 | import { auth } from "../services/firebase"; 3 | 4 | const register = async (email, password) => { 5 | const response = {}; 6 | try { 7 | const userCredential = await createUserWithEmailAndPassword(auth, email, password); 8 | const user = userCredential.user; 9 | response.user = user; 10 | } catch (error) { 11 | response.error = error; 12 | } 13 | return response; 14 | } 15 | 16 | export default register; 17 | -------------------------------------------------------------------------------- /src/components/GenreList.js: -------------------------------------------------------------------------------- 1 | const GenreList = ({ genre }) => { 2 | return ( 3 | 5 | {genre} 6 | 7 | ) 8 | } 9 | 10 | export default GenreList; 11 | -------------------------------------------------------------------------------- /src/components/GptSearchBar.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import openai from "../services/openai"; 3 | import { TMDB_API_URL, TMDB_OPTIONS } from "../services/tmdb"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { setGptSearch } from "../stores/searchSlice"; 6 | import SearchOutlinedIcon from '@mui/icons-material/SearchOutlined'; 7 | import CloseIcon from '@mui/icons-material/Close'; 8 | 9 | const GptSearchBar = ({ searchOpacity }) => { 10 | const userEmail = useSelector((store) => store?.user?.email); 11 | const [user, setUser] = useState(userEmail) 12 | const [loadingBtn, setLoadingBtn] = useState(false); 13 | const [searchPrompt, setSearchPrompt] = useState(''); 14 | const dispatch = useDispatch(); 15 | 16 | const handlePrompt = (event) => { 17 | setSearchPrompt(event.target.value) 18 | } 19 | 20 | const handleClearPrompt = () => { 21 | setSearchPrompt('') 22 | } 23 | 24 | const searchMovies = async (endpoint, query) => { 25 | try { 26 | const response = await fetch(`${TMDB_API_URL}/search/${endpoint}?query=${query}&language=en-US&page=1`, TMDB_OPTIONS); 27 | const results = await response.json(); 28 | return results; 29 | } catch (error) { 30 | console.error('Error fetching movies:', error); 31 | } 32 | } 33 | 34 | const handleSearch = async () => { 35 | setLoadingBtn(true); 36 | if (user === 'gpt4@gmail.com') { 37 | try { 38 | const prompt = ` 39 | Act as a movie recommendation system and suggest some movies for the query : ${searchPrompt}. 40 | Only give me name of 5 movies with comma seperated. 41 | result should always look like - Spider Man, Elemental, Phir Hera Pheri 42 | ` 43 | const gptResponse = await openai.chat.completions.create({ 44 | messages: [{ role: 'user', content: prompt }], 45 | model: 'gpt-3.5-turbo', 46 | }); 47 | 48 | const gptResults = gptResponse?.choices[0]?.message?.content.split(", ") 49 | const data = gptResults.map((query) => searchMovies('movie', query)); 50 | const searchResults = await Promise.all(data); 51 | 52 | if (searchResults) { 53 | setLoadingBtn(false) 54 | } 55 | 56 | dispatch(setGptSearch({ searchResults: gptResults, actionType: 'gptResults' })) 57 | dispatch(setGptSearch({ searchResults: searchResults, actionType: 'movies' })) 58 | } catch (error) { 59 | console.error('Error:', error); 60 | } 61 | } else { // GPT will not work for all user 62 | const searchTerm = [searchPrompt]; 63 | const data = searchTerm.map((query) => searchMovies('movie', query)); 64 | const searchResults = await Promise.all(data); 65 | 66 | if (searchResults) { 67 | setLoadingBtn(false) 68 | } 69 | 70 | dispatch(setGptSearch({ searchResults: searchTerm, actionType: 'gptResults' })) 71 | dispatch(setGptSearch({ searchResults: searchResults, actionType: 'movies' })) 72 | } 73 | } 74 | 75 | return ( 76 | <> 77 |
78 |

Let AI be your Movie Guru!

79 |

Discover Family-Friendly Flicks for a Perfect Movie Night

80 |
81 |
82 |
e.preventDefault()}> 83 |
84 |
85 | 86 | 87 | 88 | 95 | 96 | {searchPrompt && 97 | 98 | } 99 |
100 | 117 |
118 |

119 | Note: Movie recommendations powered by GPT are available on request due to paid APIs. 120 | Request now 121 |

122 |
123 |
124 | 125 | ) 126 | } 127 | 128 | export default GptSearchBar 129 | -------------------------------------------------------------------------------- /src/components/MovieCard.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { LazyLoadImage } from "react-lazy-load-image-component"; 4 | import { TMDB_CDN_URL } from "../services/tmdb"; 5 | import { createPortal } from "react-dom"; 6 | import MovieCardHover from "./MovieCardHover"; 7 | import { PAGE } from "../router/routes"; 8 | import { NO_POSTER } from "../utils/constants"; 9 | 10 | const MovieCard = ({ data }) => { 11 | const [isLargeScreen, setIsLargeScreen] = useState(false); 12 | const [hovered, setHovered] = useState(false); 13 | const [centerPosition, setCenterPosition] = useState({ left: 0, right: 0, top: 0, bottom: 0 }); 14 | 15 | 16 | const handleHover = () => { 17 | if (data !== null) { 18 | clearTimeout(hoverTimeout); 19 | 20 | hoverTimeout = setTimeout(() => { 21 | setHovered(true); 22 | if (cardRef.current) { 23 | const triggerRect = cardRef.current.getBoundingClientRect(); 24 | const offsetFromTop = triggerRect.top + window.scrollY; 25 | const positionFromRight = window.innerWidth - triggerRect.right; 26 | setCenterPosition({ 27 | left: triggerRect.left, 28 | right: positionFromRight, 29 | top: offsetFromTop, 30 | bottom: offsetFromTop, 31 | }); 32 | } 33 | }, 1000); 34 | } 35 | }; 36 | 37 | const handleLeave = () => { 38 | clearTimeout(hoverTimeout); 39 | setHovered(false); 40 | }; 41 | 42 | let hoverTimeout; 43 | const cardRef = useRef(null); 44 | 45 | useEffect(() => { 46 | const handleResize = () => { 47 | setIsLargeScreen(window.innerWidth > 1024); 48 | }; 49 | 50 | window.addEventListener('resize', handleResize); 51 | handleResize(); // Initialize isLargeScreen 52 | 53 | return () => { 54 | window.removeEventListener('resize', handleResize); 55 | }; 56 | }, []); 57 | 58 | if (data === null) return null; 59 | 60 | const { id, title, poster_path, backdrop_path } = data; 61 | 62 | return ( 63 |
69 |
70 | 71 | 82 | 83 |
84 | 85 | {/* {title === 'The Creator' && */} 86 | {hovered && isLargeScreen && 87 | createPortal( 88 | , 89 | document.body 90 | ) 91 | } 92 |
93 | ); 94 | }; 95 | 96 | export const WithTrending = (MovieCard) => { 97 | return (props) => { 98 | const { index } = props; 99 | return ( 100 |
101 |
{index}
102 |
103 | 104 |
105 |
106 | ) 107 | } 108 | } 109 | 110 | 111 | export default MovieCard; 112 | -------------------------------------------------------------------------------- /src/components/MovieCardHover.js: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import { PAGE } from "../router/routes"; 3 | import { TMDB_CDN_URL } from "../services/tmdb"; 4 | import { NO_POSTER } from "../utils/constants"; 5 | import { LazyLoadImage } from "react-lazy-load-image-component"; 6 | import getGenresName from "../utils/getGenresName"; 7 | import GenreList from "./GenreList"; 8 | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; 9 | import ThumbUpOffAltIcon from '@mui/icons-material/ThumbUpOffAlt'; 10 | import AddIcon from '@mui/icons-material/Add'; 11 | import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; 12 | 13 | 14 | 15 | const MovieCardHover = ({ data, centerPosition, hovered }) => { 16 | const { id, title, poster_path, backdrop_path, release_date, genre_ids, vote_average } = data; 17 | const genres = getGenresName(genre_ids); 18 | 19 | const releaseYear = release_date && release_date.split("-")[0]; 20 | 21 | let safeTranslateX = '-82px'; 22 | let safeTranslateY = '-50px'; 23 | 24 | if (centerPosition.left <= 48) { 25 | safeTranslateX = '-8px' 26 | } else if (centerPosition.right <= 48) { 27 | safeTranslateX = '-168px' 28 | } 29 | 30 | const hoverCardStyle = { 31 | left: centerPosition.left, 32 | top: centerPosition.top, 33 | transform: `translate(${safeTranslateX}, ${safeTranslateY})`, 34 | }; 35 | 36 | return ( 37 |
41 |
42 |
43 | 44 | 54 | 55 |
56 |
57 |
58 | 59 | {/* play_arrow */} 60 | 61 | 62 | 66 | 70 |
71 | 75 |
76 |
77 | 78 |
79 |

{title}

80 |
81 |
82 | {vote_average} Rating 83 | {releaseYear} 84 |
85 |
86 | { 87 | genres.map((genre) => ( 88 | 89 | )) 90 | } 91 |
92 |
93 |
94 |
95 |
96 |
97 | ); 98 | }; 99 | 100 | 101 | export default MovieCardHover; 102 | -------------------------------------------------------------------------------- /src/components/MovieList.js: -------------------------------------------------------------------------------- 1 | import MovieCard from "./MovieCard"; 2 | 3 | const MovieList = ({ heading, data }) => { 4 | const { results } = data; 5 | 6 | return ( 7 |
8 |

{heading}

9 |
10 | { 11 | results.map((movie) => ) 12 | } 13 |
14 |
15 | ) 16 | } 17 | 18 | export default MovieList 19 | -------------------------------------------------------------------------------- /src/components/MovieSlider.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import SwiperCore from 'swiper'; 3 | import { Swiper, SwiperSlide } from 'swiper/react'; 4 | import { Navigation, Pagination } from 'swiper/modules'; 5 | import 'swiper/css'; 6 | import 'swiper/css/navigation'; 7 | 8 | import MovieCard, { WithTrending } from './MovieCard'; 9 | 10 | SwiperCore.use([Navigation, Pagination]); 11 | 12 | const MovieSlider = ({ type, heading, data }) => { 13 | const [slidesPerGroup, setSlidesPerGroup] = useState(2); 14 | const TrendingMovieCard = WithTrending(MovieCard); 15 | 16 | const handleResize = () => { 17 | const slidesInView = Math.floor(window.innerWidth / 144); // Adjust 200 to your slide width 18 | setSlidesPerGroup(slidesInView); 19 | }; 20 | 21 | if (!data) return; 22 | const movies = data.results; 23 | 24 | return ( 25 |
26 |

{heading}

27 | 39 | { 40 | movies.map((movie, index) => ( 41 | 42 | {type === 'trending' ? : } 43 | ) 44 | ) 45 | } 46 | 47 |
48 | ) 49 | } 50 | 51 | export default MovieSlider 52 | -------------------------------------------------------------------------------- /src/components/MovieSliderShimmer.js: -------------------------------------------------------------------------------- 1 | const MovieSliderShimmer = ({ dimention }) => { 2 | return ( 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ) 19 | } 20 | 21 | export default MovieSliderShimmer; 22 | -------------------------------------------------------------------------------- /src/components/NotFoundPage.js: -------------------------------------------------------------------------------- 1 | import '../NotFound.css'; 2 | 3 | const NotFoundPage = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 |

404

12 |
13 |
14 |

Look like you're lost

15 |

The page you are looking for is not available!

16 | Go to Home 17 |
18 |
19 |
20 |
21 |
22 |
23 | ); 24 | }; 25 | 26 | export default NotFoundPage; 27 | -------------------------------------------------------------------------------- /src/components/SearchCard.js: -------------------------------------------------------------------------------- 1 | import { TMDB_CDN_URL } from "../services/tmdb"; 2 | 3 | const SearchCard = ({ data }) => { 4 | if (data === null) return; 5 | const { title, poster_path } = data; 6 | 7 | if (!poster_path) return null; 8 | 9 | return ( 10 |
11 | {title} 15 | {/*

{title}

*/} 16 |
17 | ) 18 | } 19 | 20 | export default SearchCard; 21 | -------------------------------------------------------------------------------- /src/components/SearchResult.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import MovieSlider from "./MovieSlider"; 3 | 4 | const SearchResult = () => { 5 | const { gptResults, movies } = useSelector((store) => store.search); 6 | if (!gptResults) return null; 7 | 8 | return ( 9 | <> 10 | {/* */} 11 | {gptResults.map((title, index) => )} 12 | 13 | ) 14 | } 15 | 16 | export default SearchResult 17 | -------------------------------------------------------------------------------- /src/components/Showcase.js: -------------------------------------------------------------------------------- 1 | import VideoBackground from './VideoBackground'; 2 | import { TMDB_CDN_URL } from "../services/tmdb"; 3 | import { PAGE } from '../router/routes'; 4 | import { Link } from 'react-router-dom'; 5 | import { useDispatch, useSelector } from 'react-redux'; 6 | import { setApp } from '../stores/appSlice'; 7 | import getGenresName from '../utils/getGenresName'; 8 | import GenreList from './GenreList'; 9 | import { LazyLoadComponent } from 'react-lazy-load-image-component'; 10 | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; 11 | import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; 12 | import VolumeMuteIcon from '@mui/icons-material/VolumeMute'; 13 | import VolumeOffIcon from '@mui/icons-material/VolumeOff'; 14 | 15 | const Showcase = ({ data }) => { 16 | const isMuted = useSelector((store) => store.app.isMuted); 17 | const dispatch = useDispatch(); 18 | 19 | if (!data) return
; 20 | const { id, title, overview, backdrop_path, poster_path, genre_ids } = data?.info; 21 | const genres = getGenresName(genre_ids); 22 | const { results } = data?.videos 23 | const contentId = id; 24 | 25 | const toggleMute = () => { 26 | dispatch(setApp({ appState: 'isMuted', appData: !isMuted })) 27 | } 28 | 29 | return ( 30 |
31 |
32 | 33 | 34 | 35 |
36 | {/* For Tablet and Desktop */} 37 |
38 |
39 |

{title}

40 |

{overview}

41 |
42 | 43 | {/* play_arrow */} 44 | 45 | Play 46 | 47 | 52 |
53 |
54 |
55 |
56 | 64 |
65 |
66 |
67 | 68 | {/* For Mobile Onlye */} 69 |
70 |
71 |
72 |
73 | {title} 74 |
75 |
76 |

{title}

77 | {/*

{overview}

*/} 78 |
79 | { 80 | genres.map((genre) => ( 81 | 82 | )) 83 | } 84 |
85 | 86 |
87 | 88 | {/* play_arrow */} 89 | 90 | Play 91 | 92 | 97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | ) 106 | } 107 | 108 | export default Showcase 109 | -------------------------------------------------------------------------------- /src/components/ShowcaseShimmer.js: -------------------------------------------------------------------------------- 1 | const ShowcaseShimmer = () => { 2 | return ( 3 |
6 |
7 |
8 | {/* For Tablet and Desktop */} 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 |
21 | 22 | {/* For Mobile Onlye */} 23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 |
44 | ) 45 | } 46 | 47 | export default ShowcaseShimmer; 48 | -------------------------------------------------------------------------------- /src/components/SignInForm.js: -------------------------------------------------------------------------------- 1 | import { Link, useNavigate } from "react-router-dom"; 2 | import validate from "../validator/validate"; 3 | import { PAGE } from "../router/routes"; 4 | import { useRef, useState } from "react"; 5 | import authenticate from "../auth/authenticate"; 6 | 7 | const SignInform = () => { 8 | const [loadingBtn, setLoadingBtn] = useState(false); 9 | const [authError, setAuthError] = useState(null); 10 | const [errorMessage, setErrorMessage] = useState(null); 11 | 12 | const [emailPhone, setEmailPhone] = useState('test@demo.com'); 13 | const [password, setPassword] = useState('Test@123'); 14 | const navigate = useNavigate(); 15 | 16 | const handleEmailPhone = (event) => { 17 | setEmailPhone(event.target.value); 18 | }; 19 | 20 | const handlePassword = (event) => { 21 | setPassword(event.target.value); 22 | }; 23 | 24 | const handleSignIn = async () => { 25 | // Client side validation 26 | const isError = validate( 27 | emailPhone, 28 | password 29 | ); 30 | setErrorMessage(isError); 31 | if (isError) return; 32 | setLoadingBtn(true); 33 | 34 | // Send provided credential to server for validation 35 | const userCredential = await authenticate(emailPhone, password); 36 | let errorMessage = null; 37 | switch (userCredential?.error?.code) { 38 | case "auth/user-disabled": 39 | errorMessage = "Your account has been disabled!" 40 | break; 41 | 42 | case "auth/invalid-login-credentials": 43 | errorMessage = "Invalid login credentials." 44 | break; 45 | 46 | default: 47 | errorMessage = "Something went wrong with your credentials." 48 | break; 49 | } 50 | setAuthError(errorMessage); 51 | setLoadingBtn(false) 52 | 53 | if (userCredential?.error) return; 54 | navigate(PAGE.BROWSE); 55 | } 56 | 57 | return ( 58 |
59 |
60 |

Sign In

61 | {authError && ( 62 |
{authError}
63 | )} 64 |
65 | 66 |
{errorMessage?.emailPhone}
67 |
68 |
e.preventDefault()}> 69 |
70 | 71 |
{errorMessage?.password}
72 |
73 |
74 | 79 |
80 |
81 |
82 |
83 | 84 | 85 |
86 |
87 | Need Help? 88 |
89 |
90 |
91 | 92 | New to NetflixGPT? 93 | 94 | Sign up now 95 |
96 |
97 |
98 | ) 99 | } 100 | 101 | export default SignInform; 102 | -------------------------------------------------------------------------------- /src/components/SignUpForm.js: -------------------------------------------------------------------------------- 1 | import { useState, useRef } from "react"; 2 | import { useDispatch } from 'react-redux'; 3 | import { Link } from "react-router-dom"; 4 | import register from "../auth/register"; 5 | import { PAGE } from "../router/routes"; 6 | import validate from "../validator/validate"; 7 | import { addUser } from "../stores/userSlice"; 8 | 9 | const SignUpForm = () => { 10 | const [loadingBtn, setLoadingBtn] = useState(false); 11 | const [authError, setAuthError] = useState(null); 12 | const [errorMessage, setErrorMessage] = useState(null); 13 | const dispatch = useDispatch(); 14 | const emailPhone = useRef(null); 15 | const password = useRef(null); 16 | const confirmPassword = useRef(null); 17 | 18 | const handleSignUp = async () => { 19 | // Client side validation 20 | const isError = validate( 21 | emailPhone.current.value, 22 | password.current.value, 23 | confirmPassword.current.value 24 | ); 25 | setErrorMessage(isError); 26 | if (isError) return; 27 | setLoadingBtn(true); 28 | 29 | // Send provided credential to server for validation 30 | const userCredential = await register(emailPhone.current.value, password.current.value); 31 | let errorMessage = null; 32 | switch (userCredential?.error?.code) { 33 | case "auth/email-already-in-use": 34 | errorMessage = "Email you provided is already registered." 35 | break; 36 | 37 | default: 38 | errorMessage = "Something went wrong with your credentials." 39 | break; 40 | } 41 | setAuthError(errorMessage); 42 | setLoadingBtn(false); 43 | 44 | if (userCredential?.error) return; 45 | 46 | const { uid, displayName, email, photoURL, phoneNumber } = userCredential.user; 47 | dispatch(addUser({ 48 | uid: uid, 49 | displayName: displayName, 50 | photoURL: photoURL, 51 | email: email, 52 | phoneNumber: phoneNumber 53 | })); 54 | } 55 | 56 | return ( 57 |
58 |
59 |

Sign Up

60 | {authError && ( 61 |
{authError}
62 | )} 63 |
64 | 65 |
{errorMessage?.emailPhone}
66 |
67 |
68 | 69 |
{errorMessage?.password}
70 |
71 |
72 | 73 |
{errorMessage?.confirmPassword}
74 |
75 |
76 | 81 |
82 |
83 | 84 | Already have an Account? 85 | 86 | Sign in now 87 |
88 |
89 |
90 | ) 91 | } 92 | 93 | export default SignUpForm; 94 | -------------------------------------------------------------------------------- /src/components/Spinner.js: -------------------------------------------------------------------------------- 1 | const Spinner = () => { 2 | return ( 3 |
4 |
5 |
6 | ) 7 | } 8 | 9 | export default Spinner; 10 | -------------------------------------------------------------------------------- /src/components/VideoBackground.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import { TMDB_CDN_URL } from "../services/tmdb"; 3 | import { useEffect, useRef } from "react"; 4 | 5 | const VideoBackground = ({ videos, title, backdrop, poster }) => { 6 | const trailers = videos.filter((trailer) => trailer.type === "Trailer"); 7 | const trailerKey = trailers[0]?.key; 8 | const isMuted = useSelector((store) => store?.app?.isMuted); 9 | 10 | return ( 11 |
12 | {title} 13 | 14 | {trailerKey && 15 |
16 | 23 |
24 | } 25 |
26 | ) 27 | } 28 | 29 | export default VideoBackground 30 | -------------------------------------------------------------------------------- /src/hooks/useFontLoader.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | const useFontLoader = () => { 4 | const [fontLoaded, setFontLoaded] = useState(false); 5 | 6 | useEffect(() => { 7 | const loadFont = async () => { 8 | const font = new FontFaceObserver('Material Symbols Outlined'); 9 | try { 10 | await font.load('test'); 11 | console.log('Font is available'); 12 | document.body.classList.remove('no-icon'); 13 | setFontLoaded(true); 14 | } catch (error) { 15 | console.log('Font is not available'); 16 | setFontLoaded(false); 17 | } 18 | }; 19 | 20 | // Execute the code when the document is fully loaded 21 | if (document.readyState === 'complete') { 22 | loadFont(); 23 | } else { 24 | window.addEventListener('load', loadFont); 25 | } 26 | 27 | // Cleanup event listener on component unmount 28 | return () => { 29 | window.removeEventListener('load', loadFont); 30 | }; 31 | }, []); 32 | 33 | return { fontLoaded }; 34 | }; 35 | 36 | export default useFontLoader; 37 | 38 | -------------------------------------------------------------------------------- /src/hooks/useMovie.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import { TMDB_API_URL, TMDB_OPTIONS } from "../services/tmdb"; 4 | import { setMovie } from '../stores/moviesSlice'; 5 | 6 | /** 7 | * Custom hook to fetch movie data and update the Redux store. 8 | * 9 | * This hook fetches movie data using the provided endpoint, genre ID, and original language, 10 | * and updates the Redux store using the setMovie action. 11 | * 12 | * @param {string} endpoint - The endpoint to fetch data from. 13 | * @param {string} movieState - The movie state to update in the Redux store (e.g., showCase, videos). 14 | * @param {number} genreId - Optional. The genre ID for filtering movies. 15 | * @param {string} originalLanguage - Optional. The original language for filtering movies. 16 | * @returns {void} 17 | * @example 18 | * const endpoint = 'popular'; // Endpoint for popular movies 19 | * const movieState = 'showCase'; // Movie state: showCase, videos, etc. 20 | * const genreId = 28; // Genre ID for Action (optional) 21 | * const originalLanguage = 'en'; // Original language (optional) 22 | * useMovie(endpoint, movieState, genreId, originalLanguage); 23 | */ 24 | 25 | const useMovie = (endpoint, movieState, genreId, originalLanguage) => { 26 | const dispatch = useDispatch(); 27 | const fetchData = async () => { 28 | try { 29 | let apiUrl = `${TMDB_API_URL}/discover/movie?language=en-US&page=1&adult=true`; 30 | 31 | if (genreId) { 32 | apiUrl += `&with_genres=${genreId}`; 33 | } 34 | 35 | if (originalLanguage) { 36 | apiUrl += `&with_original_language=${originalLanguage}`; 37 | } 38 | 39 | const response = await fetch(apiUrl, TMDB_OPTIONS); 40 | const result = await response.json(); 41 | dispatch(setMovie({ movieState, movieData: result })) 42 | } catch (error) { 43 | console.error('Error fetching movies:', error); 44 | } 45 | } 46 | 47 | useEffect(() => { 48 | fetchData() 49 | }, []) 50 | } 51 | 52 | export default useMovie; 53 | -------------------------------------------------------------------------------- /src/hooks/usePlayer.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import { TMDB_API_URL, TMDB_OPTIONS } from "../services/tmdb"; 4 | import { setPlayer } from '../stores/playerSlice'; 5 | 6 | const usePlayer = (endpoint, playerState, contentId) => { 7 | const dispatch = useDispatch(); 8 | const fetchData = async () => { 9 | try { 10 | // Fetch data for {videoState} 11 | const response = await fetch(`${TMDB_API_URL}/${endpoint}/${contentId}?language=en-US`, TMDB_OPTIONS); 12 | const media = await response.json(); 13 | if (!media) return null; 14 | 15 | const { success, status_message } = await media; 16 | 17 | if (success === false) return status_message; 18 | 19 | // Fetch videos for media.id 20 | const videoResponse = await fetch(`${TMDB_API_URL}/movie/${media.id}/videos?language=en-US&page=1`, TMDB_OPTIONS); 21 | const videoResult = await videoResponse.json(); 22 | 23 | const mediaResult = { 24 | info: media, 25 | videos: videoResult 26 | } 27 | dispatch(setPlayer({ playerState, playerData: mediaResult })) 28 | return mediaResult; 29 | } catch (error) { 30 | console.error('Error fetching movies:', error); 31 | } 32 | } 33 | 34 | useEffect(() => { 35 | fetchData(); 36 | }, []) 37 | } 38 | 39 | export default usePlayer; 40 | -------------------------------------------------------------------------------- /src/hooks/useSearch.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import { TMDB_API_URL, TMDB_OPTIONS } from "../services/tmdb"; 4 | import { setGptSearch } from '../stores/searchSlice'; 5 | 6 | const useSearch = (endpoint, query, actionType) => { 7 | const dispatch = useDispatch() 8 | const fetchData = async () => { 9 | try { 10 | const response = await fetch(`${TMDB_API_URL}/search/${endpoint}?query=${query}&language=en-US&page=1`, TMDB_OPTIONS); 11 | const results = await response.json(); 12 | console.log(results); 13 | dispatch(setGptSearch({ actionType, searchResults: results })) 14 | } catch (error) { 15 | console.error('Error fetching movies:', error); 16 | } 17 | } 18 | 19 | useEffect(() => { 20 | fetchData() 21 | }, []) 22 | } 23 | 24 | export default useSearch; 25 | -------------------------------------------------------------------------------- /src/hooks/useShowCase.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { TMDB_API_URL, TMDB_OPTIONS } from "../services/tmdb"; 4 | import { setShowCase } from '../stores/showCaseSlice'; 5 | 6 | /** 7 | * Custom hook to fetch movie data and update the Redux store. 8 | * 9 | * This hook fetches multiple type of data for show case (Hero Banner) using the provided endpoint, genre ID, and original language, 10 | * and updates the Redux store using the setShowCase action. 11 | * 12 | * @param {string} endpoint - The endpoint to fetch data from. 13 | * @param {string} movieState - The showCase state to update in the Redux store (e.g., landingPage, movie, tvShow). 14 | * @param {number} genreId - Optional. The genre ID for filtering data. 15 | * @param {string} originalLanguage - Optional. The original language for filtering data. 16 | * @param {number} showIndex - Optional. Specific index of fetched data. 17 | * @returns {void} 18 | * @example 19 | * const endpoint = 'popular'; // Endpoint for popular movie/tv-show 20 | * const showCaseState = 'movie'; // ShowCase state: landingPage, movie, tvShow, etc. 21 | * const genreId = 28; // Genre ID for Action (optional) 22 | * const originalLanguage = 'en'; // Original language (optional) 23 | * const showIndex = 0; // Index of fetched data (optional) 24 | * useMovie(endpoint, showCaseState, genreId, originalLanguage, showIndex); 25 | */ 26 | 27 | const useShowCase = (endpoint, showCaseState, genreId, originalLanguage, showIndex = 0) => { 28 | const dispatch = useDispatch(); 29 | // const movieData = useSelector((store) => store.showCase[showCaseState]); 30 | const fetchData = async () => { 31 | try { 32 | let apiUrl = `${TMDB_API_URL}/discover/movie?language=en-US&page=1`; 33 | 34 | if (genreId) { 35 | apiUrl += `&with_genres=${genreId}`; 36 | } 37 | 38 | if (originalLanguage) { 39 | apiUrl += `&with_original_language=${originalLanguage}`; 40 | } 41 | 42 | // Fetch data for {showCaseState} 43 | const response = await fetch(apiUrl, TMDB_OPTIONS); 44 | const result = await response.json(); 45 | 46 | // Filter result 47 | const showCaseFilter = result.results[showIndex]; 48 | 49 | // Fetch videos for showCaseFilter.id 50 | const videoResponse = await fetch(`${TMDB_API_URL}/movie/${showCaseFilter.id}/videos?language=en-US&page=1`, TMDB_OPTIONS); 51 | const videoResult = await videoResponse.json(); 52 | 53 | const showCaseResult = { 54 | info: showCaseFilter, 55 | videos: videoResult 56 | } 57 | 58 | dispatch(setShowCase({ showCaseState, showCaseData: showCaseResult })) 59 | } catch (error) { 60 | console.error('Error fetching movies:', error); 61 | } 62 | } 63 | 64 | useEffect(() => { 65 | fetchData() 66 | }, []) 67 | } 68 | 69 | export default useShowCase; 70 | -------------------------------------------------------------------------------- /src/hooks/useTrending.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { TMDB_API_URL, TMDB_OPTIONS } from "../services/tmdb"; 4 | import { setTrending } from '../stores/trendingSlice'; 5 | 6 | const useTrending = (endpoint, trendingType, genreId, originalLanguage) => { 7 | const dispatch = useDispatch(); 8 | const trendingData = useSelector((store) => store.trendings[trendingType]); 9 | const fetchData = async () => { 10 | try { 11 | let apiUrl = `${TMDB_API_URL}/trending/${endpoint}?language=en-US`; 12 | 13 | if (genreId) { 14 | apiUrl += `&with_genres=${genreId}`; 15 | } 16 | 17 | if (originalLanguage) { 18 | apiUrl += `&with_original_language=${originalLanguage}`; 19 | } 20 | const response = await fetch(apiUrl, TMDB_OPTIONS); 21 | const result = await response.json(); 22 | dispatch(setTrending({ trendingType, trendingData: result })) 23 | } catch (error) { 24 | console.error('Error fetching data:', error); 25 | } 26 | } 27 | 28 | useEffect(() => { 29 | !trendingData && fetchData() 30 | }, [dispatch, trendingData]) 31 | } 32 | 33 | export default useTrending; 34 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | html { 7 | scroll-behavior: smooth; 8 | } 9 | 10 | @media only screen and (max-device-width: 480px) and (orientation: portrait), 11 | only screen and (max-device-width: 568px) and (orientation: landscape), 12 | only screen and (max-device-width: 768px) and (orientation: portrait), 13 | only screen and (max-device-width: 1024px) and (orientation: landscape) {} 14 | 15 | body { 16 | @apply bg-[#141414] text-white; 17 | position: relative; 18 | } 19 | 20 | .dark-nav { 21 | @apply bg-[#141414] transition-colors; 22 | } 23 | 24 | .navbar { 25 | transition: 0.5s; 26 | } 27 | 28 | .gradient-nav { 29 | @apply bg-gradient-to-b from-black transition-colors; 30 | } 31 | 32 | h1 { 33 | @apply text-2xl; 34 | } 35 | 36 | h2 { 37 | @apply text-xl; 38 | } 39 | 40 | .bg-shimmer { 41 | /* @apply bg-slate-700; */ 42 | animation-duration: 3.6s; 43 | animation-iteration-count: infinite; 44 | animation-name: shimmerAnimation; 45 | animation-timing-function: ease-in-out; 46 | } 47 | 48 | .bg-shimmer-wave { 49 | animation-duration: 3.6s; 50 | @apply bg-[#141414]; 51 | -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); 52 | mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); 53 | -webkit-mask-size: 200% 100%; 54 | mask-size: 200% 100%; 55 | -webkit-animation: placeholder-wave 2s linear infinite; 56 | animation: shimmerAnimation-wave 2s linear infinite; 57 | } 58 | 59 | @keyframes shimmerAnimation-wave { 60 | 100% { 61 | mask-position: -200% 0%; 62 | } 63 | } 64 | 65 | @keyframes shimmerAnimation { 66 | 0% { 67 | background-color: #1a1a1a; 68 | } 69 | 70 | 25% { 71 | background-color: #333; 72 | } 73 | 74 | 50% { 75 | background-color: #1a1a1a; 76 | } 77 | 78 | 100% { 79 | background-color: #1a1a1a; 80 | } 81 | } 82 | 83 | .trending-card { 84 | transition: 0.3s; 85 | } 86 | 87 | .trending-card:hover { 88 | /* transform: scale(1.2); */ 89 | } 90 | 91 | .moview-by-type { 92 | background: linear-gradient(0deg, #141414 80%, transparent 100%); 93 | } 94 | 95 | .trending-number { 96 | -webkit-text-stroke: 4px rgb(89, 89, 89); 97 | font-weight: bold; 98 | font-family: sans-serif; 99 | color: #000; 100 | line-height: 200px; 101 | } 102 | 103 | .movie-hovered { 104 | display: none; 105 | } 106 | 107 | .movie-hovered.hovered { 108 | display: block; 109 | } 110 | 111 | .hover-container { 112 | opacity: 1; 113 | transform: scale(0.8); 114 | transition: opacity 0.1s ease-in, transform 0.2s ease-in; 115 | } 116 | 117 | .movie-hovered.hovered:hover { 118 | transition: 1s; 119 | } 120 | 121 | .movie-hovered.hovered:hover .hover-container { 122 | transform: scale(1); 123 | opacity: 1; 124 | } 125 | } 126 | 127 | @layer components { 128 | .bg-red-primary { 129 | @apply bg-[#e50914]; 130 | } 131 | 132 | .text-red-primary { 133 | @apply text-[#e50914]; 134 | } 135 | 136 | .btn-blue { 137 | @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded; 138 | } 139 | } 140 | 141 | :root { 142 | --swiper-navigation-size: 32px; 143 | } 144 | 145 | .swiper-button-prev, 146 | .swiper-button-next { 147 | display: none; 148 | color: #fff; 149 | width: 72px; 150 | height: 100%; 151 | top: 0; 152 | margin-top: 0; 153 | } 154 | 155 | .swiper-button-prev:hover::after, 156 | .swiper-button-next:hover::after { 157 | transition: 0.3s; 158 | transform: scale(1.2); 159 | } 160 | 161 | .swiper-button-disabled { 162 | display: none; 163 | } 164 | 165 | .swiper:hover .swiper-button-prev:not(.swiper-button-disabled), 166 | .swiper:hover .swiper-button-next:not(.swiper-button-disabled) { 167 | display: flex; 168 | } 169 | 170 | 171 | 172 | .swiper-button-prev { 173 | left: 0; 174 | background: linear-gradient(90deg, rgb(17, 17, 17), transparent); 175 | } 176 | 177 | .swiper-button-prev:hover { 178 | left: 0; 179 | background: linear-gradient(90deg, black, transparent); 180 | } 181 | 182 | .swiper-button-next { 183 | right: 0; 184 | background: linear-gradient(270deg, rgb(17, 17, 17), transparent); 185 | } 186 | 187 | .swiper-button-next:hover { 188 | right: 0; 189 | background: linear-gradient(270deg, black, transparent); 190 | } 191 | 192 | 193 | @media (max-width: 767px) { 194 | 195 | .swiper-button-prev, 196 | .swiper-button-next { 197 | display: none !important; 198 | opacity: 0; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { RouterProvider } from 'react-router-dom'; 4 | 5 | import router from './router/router'; 6 | import './index.css'; 7 | import reportWebVitals from './reportWebVitals'; 8 | 9 | const root = ReactDOM.createRoot(document.getElementById('root')); 10 | root.render( 11 | // 12 | 13 | // 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /src/layout/AppLayout.js: -------------------------------------------------------------------------------- 1 | import Navbar from "./Navbar"; 2 | import Footer from "./Footer"; 3 | import { useLocation } from "react-router-dom"; 4 | import { PAGE } from "../router/routes"; 5 | 6 | const AppLayout = ({ children }) => { 7 | const location = useLocation(); 8 | const { pathname } = location; 9 | const isWatchPage = pathname.startsWith(PAGE.WATCH); 10 | 11 | return ( 12 | <> 13 | 14 |
15 | {children} 16 |
17 | {!isWatchPage &&