├── src ├── config.java ├── App.css ├── setupTests.js ├── App.test.js ├── axios.js ├── index.css ├── reportWebVitals.js ├── index.js ├── requests.js ├── Nav.css ├── Row.css ├── Nav.js ├── App.js ├── Banner.js ├── Banner.css ├── logo.svg ├── Row.js └── serviceWorker.js ├── public ├── favicon.jfif ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── .gitignore ├── README.md ├── .github └── workflows │ ├── main.yml │ ├── npm-publish-github-packages.yml │ ├── codeql-analysis.yml │ └── google.yml └── package.json /src/config.java: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/favicon.jfif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanBinu007/Netflix_Clone_ReactJs/HEAD/public/favicon.jfif -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanBinu007/Netflix_Clone_ReactJs/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanBinu007/Netflix_Clone_ReactJs/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | 5 | This is just UseLess 6 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | 2 | /* Applies to the html document */ 3 | * { 4 | margin:0; 5 | } 6 | /* Styling app component */ 7 | .app { 8 | background-color: #111; 9 | } -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/axios.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | // Function to create a request to the tmdb API 4 | // API fetching from themoviedb 5 | const instance = axios.create({ 6 | baseURL: "https://api.themoviedb.org/3", 7 | }); 8 | 9 | // Exporting instance object.Making it available 10 | export default instance; 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Netflix Clone 3 | 4 | **Project Link** - ***https://alanbinu-netflix.web.app/*** 5 | 6 | ## Tech We Used 7 | 8 | - ReactJS 9 | - Firebase Hosting 10 | - Material UI 11 | - axios API 12 | - React-Dom 13 | - themoviedb API 14 | 15 | ## Features 16 | 17 | - Video Playback Feature 18 | - Neat and clean UI 19 | 20 | 21 | ## Steps to run in your machine 22 | 23 | #### Run the following commands 24 | ``` 25 | npm i 26 | npm run start 27 | ``` 28 | 29 | #### Hope you liked this project, don't forget to ⭐ the repo. 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /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 | 27 | -------------------------------------------------------------------------------- /src/requests.js: -------------------------------------------------------------------------------- 1 | // API KEY 2 | const API_KEY = "2a0a294a9f6e0594afb7d514a796f71c"; 3 | 4 | // Object for various requests for movies 5 | const request = { 6 | fetchTrending: `/trending/all/week?api_key=${API_KEY}&language=en-US`, 7 | fetchNetflixOriginals: `discover/tv?api_key=${API_KEY}&with_networks=213`, 8 | fetchTopRated: `/movie/top_rated?api_key=${API_KEY}&language=en-US`, 9 | fetchActionMovies: `/discover/movie?api_key=${API_KEY}&with_genres=28`, 10 | fetchComedyMovies: `/discover/movie?api_key=${API_KEY}&with_genres=35`, 11 | fetchHorrorMovies: `/discover/movie?api_key=${API_KEY}&with_genres=27`, 12 | fetchRomanceMovies: `/discover/movie?api_key=${API_KEY}&with_genres=10749`, 13 | fetchDocumentaries: `/discover/movie?api_key=${API_KEY}&with_genres=99`, 14 | }; 15 | 16 | // Exporting request object. Making it available 17 | export default request; 18 | -------------------------------------------------------------------------------- /src/Nav.css: -------------------------------------------------------------------------------- 1 | /* Styling and positioning navbar container */ 2 | .nav { 3 | position: fixed; 4 | top: 0; 5 | display: flex; 6 | width: 100%; 7 | justify-content: space-between; 8 | padding: 20px; 9 | height: 30px; 10 | /* Navbar always stays on top of page */ 11 | z-index: 1; 12 | 13 | /* Animations */ 14 | transition-timing-function: ease-in; 15 | transition:all 0.5s; 16 | } 17 | 18 | /* Adds a black color on navbar */ 19 | .nav__black { 20 | background-color: #111; 21 | } 22 | 23 | /* Styling and positioning navbar logo */ 24 | .nav__logo { 25 | position: fixed; 26 | left: 20px; 27 | margin-top: -10px; 28 | width: 80px; 29 | object-fit: contain; 30 | 31 | } 32 | 33 | /* Styling and positioning navbar avatar */ 34 | .nav__avatar { 35 | position: fixed; 36 | right: 20px; 37 | width: 30px; 38 | object-fit: contain; 39 | } -------------------------------------------------------------------------------- /src/Row.css: -------------------------------------------------------------------------------- 1 | 2 | /* Positioning rows to right */ 3 | .row { 4 | margin-left: 20px; 5 | color: white; 6 | } 7 | /* Positioning in flex */ 8 | .row__posters { 9 | display: flex; 10 | /* Makes title stay while scrolling */ 11 | overflow-y: hidden; 12 | overflow-x: scroll; 13 | padding: 20px; 14 | } 15 | 16 | /* Removes scrollbar, but keeps functionality */ 17 | .row__posters::-webkit-scrollbar { 18 | display:none; 19 | } 20 | 21 | /* Positioning of rows */ 22 | .row__poster { 23 | width: 100%; 24 | object-fit: contain; 25 | max-height: 100px; 26 | margin-right: 10px; 27 | /* Delay effect */ 28 | transition: transform 450ms; 29 | } 30 | 31 | .row__poster:hover { 32 | /* Bigger effect */ 33 | transform: scale(1.08); 34 | opacity:1; 35 | } 36 | 37 | /* Adds a larger row */ 38 | .row__posterLarge { 39 | max-height: 250px; 40 | } 41 | 42 | /* Hover effect os larger row */ 43 | .row__posterLarge:hover { 44 | transform:scale(1.09); 45 | opacity: 1; 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI + CD 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | Staging: 8 | runs-on: ubuntu-latest 9 | environment: Staging 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Run a script 13 | run: echo "Running in Staging" 14 | 15 | Quality_Assurance: 16 | runs-on: ubuntu-latest 17 | environment: Quality_Assurance 18 | needs: Staging 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Run a script 22 | run: echo "Running in QA" 23 | 24 | Production: 25 | runs-on: ubuntu-latest 26 | environment: Development 27 | needs: Quality_Assurance 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Run a script 31 | run: echo "Running in production" 32 | 33 | Development: 34 | runs-on: ubuntu-latest 35 | environment: Production 36 | needs: Production 37 | steps: 38 | - uses: actions/checkout@v2 39 | - name: Run a script 40 | run: echo "Deployed" 41 | 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netflix_clone", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.10", 7 | "@testing-library/react": "^11.2.6", 8 | "@testing-library/user-event": "^12.8.3", 9 | "axios": "^0.21.1", 10 | "movie-trailer": "^2.1.0", 11 | "react": "^17.0.2", 12 | "react-dom": "^17.0.2", 13 | "react-scripts": "4.0.3", 14 | "react-youtube": "^7.13.1", 15 | "web-vitals": "^1.1.1" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish-github-packages.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v2 16 | with: 17 | node-version: 16 18 | - run: npm ci 19 | - run: npm test 20 | 21 | publish-gpr: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | permissions: 25 | contents: read 26 | packages: write 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions/setup-node@v2 30 | with: 31 | node-version: 16 32 | registry-url: https://npm.pkg.github.com/ 33 | - run: npm ci 34 | - run: npm publish 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 37 | -------------------------------------------------------------------------------- /src/Nav.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "./Nav.css"; 3 | 4 | function Nav() { 5 | const [show, handleShow] = useState(false); 6 | 7 | // When scroll is a 100px (down in page) we add the navbar visibilty 8 | // if not remove visibility on navbar 9 | 10 | useEffect(() => { 11 | window.addEventListener("scroll", () => { 12 | if (window.scrollY > 100) { 13 | handleShow(true); 14 | } else handleShow(false); 15 | }); 16 | return () => { 17 | window.removeEventListener("scroll"); 18 | }; 19 | }, []); 20 | 21 | return ( 22 | // Always use nav css, except when scrolling 100px down we want the black__navbar class 23 |
24 | {/* Big main logo */} 25 | 30 | {/* Small corner logo */} 31 | 36 |
37 | ); 38 | } 39 | 40 | export default Nav; 41 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./App.css"; 3 | import Row from "./Row"; 4 | import requests from "./requests"; 5 | import Banner from "./Banner"; 6 | import "./Banner.css"; 7 | import Nav from "./Nav"; 8 | 9 | function App() { 10 | return ( 11 |
12 | {/* Inserting design components and fetch param from tmdb API */} 13 |
31 | ); 32 | } 33 | 34 | // Exporting App function. Making it available 35 | export default App; 36 | -------------------------------------------------------------------------------- /src/Banner.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import axios from "./axios"; 3 | import requests from "./requests"; 4 | 5 | function Banner() { 6 | // Setting up useState 7 | const [movie, setMovie] = useState([]); 8 | 9 | // useEffect (short term memory) 10 | useEffect(() => { 11 | async function fetchData() { 12 | const request = await axios.get(requests.fetchNetflixOriginals); 13 | setMovie( 14 | request.data.results[ 15 | Math.floor(Math.random() * request.data.results.length - 1) 16 | ] 17 | ); 18 | } 19 | // Pulling data from API 20 | fetchData(); 21 | }, []); 22 | 23 | // console.log(movie); 24 | 25 | // Showing less movie info (plot) 26 | function truncate(str, n) { 27 | return str?.length > n ? str.substr(0, n - 1) + "..." : str; 28 | } 29 | 30 | return ( 31 | // Contents of header 32 |
40 |
41 |

42 | {movie?.title || movie?.name || movie?.original_name} 43 |

44 |
45 | 46 | 47 |
48 | 49 |

50 | {truncate(movie?.overview, 150)} 51 |

52 |
53 | {/* Empty fade div with bottom fade effect */} 54 |
55 |
56 | ); 57 | } 58 | 59 | export default Banner; 60 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Netflix Clone 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Banner.css: -------------------------------------------------------------------------------- 1 | /* Positioning banner and color */ 2 | .banner { 3 | color:white; 4 | object-fit: contain; 5 | height: 448px; 6 | } 7 | 8 | /* Positioning banner content */ 9 | .banner__contents { 10 | margin-left: 30px; 11 | padding-top:140px; 12 | height: 190px; 13 | } 14 | /* Styling banner title */ 15 | .banner__title { 16 | font-size: 3rem; 17 | font-weight: 800; 18 | padding-bottom: 0.3rem;; 19 | } 20 | 21 | /* Styling banner description font*/ 22 | .banner__description { 23 | width: 45rem; 24 | line-height: 1.3; 25 | padding-top:1rem; 26 | font-size:0.8rem; 27 | max-width: 360px; 28 | height:80px; 29 | } 30 | 31 | /* Styling buttons */ 32 | .banner__button { 33 | cursor:pointer; 34 | color:#fff; 35 | outline: none; 36 | border: none; 37 | font-weight: 700; 38 | border-radius: 0.2vw; 39 | padding-left: 2rem; 40 | padding-right: 2rem; 41 | margin-right: 1rem; 42 | padding-top:0.5rem; 43 | padding-bottom: 0.5rem; 44 | background-color: rgba(51, 51, 51, 0.5); 45 | } 46 | 47 | /* Styling hover effect */ 48 | .banner__button:hover { 49 | color:#000; 50 | background-color: #e6e6e6; 51 | transition: all 0.2s; 52 | } 53 | 54 | /* Creates a fade in banner bottom effect */ 55 | .banner--fadeBottom { 56 | height: 7.4rem; 57 | background-image: linear-gradient(180deg, transparent, rgba(37, 37, 37, 0.61), #111) 58 | } 59 | 60 | .banner__button11 { 61 | cursor:pointer; 62 | color:#fff; 63 | outline: none; 64 | border: none; 65 | font-weight: 700; 66 | border-radius: 0.2vw; 67 | padding-left: 2rem; 68 | padding-right: 2rem; 69 | margin-right: 1rem; 70 | padding-top:0.5rem; 71 | padding-bottom: 0.5rem; 72 | background-color: rgba(51, 51, 51, 0.5); 73 | } 74 | 75 | .banner__button111 { 76 | cursor:pointer; 77 | color:#fff; 78 | outline: none; 79 | border: none; 80 | font-weight: 700; 81 | border-radius: 0.2vw; 82 | padding-left: 2rem; 83 | padding-right: 2rem; 84 | margin-right: 1rem; 85 | padding-top:0.5rem; 86 | padding-bottom: 0.5rem; 87 | background-color: rgba(51, 51, 51, 0.5); 88 | } 89 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '30 6 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java', 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.github/workflows/google.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a docker container, publish it to Google Container Registry, and deploy it to GKE when there is a push to the master branch. 2 | # 3 | # To configure this workflow: 4 | # 5 | # 1. Ensure that your repository contains the necessary configuration for your Google Kubernetes Engine cluster, including deployment.yml, kustomization.yml, service.yml, etc. 6 | # 7 | # 2. Set up secrets in your workspace: GKE_PROJECT with the name of the project and GKE_SA_KEY with the Base64 encoded JSON service account key (https://github.com/GoogleCloudPlatform/github-actions/tree/docs/service-account-key/setup-gcloud#inputs). 8 | # 9 | # 3. Change the values for the GKE_ZONE, GKE_CLUSTER, IMAGE, and DEPLOYMENT_NAME environment variables (below). 10 | # 11 | # For more support on how to run the workflow, please visit https://github.com/google-github-actions/setup-gcloud/tree/master/example-workflows/gke 12 | 13 | name: Build and Deploy to GKE 14 | 15 | on: 16 | push: 17 | branches: 18 | - master 19 | 20 | env: 21 | PROJECT_ID: ${{ secrets.GKE_PROJECT }} 22 | GKE_CLUSTER: cluster-1 # TODO: update to cluster name 23 | GKE_ZONE: us-central1-c # TODO: update to cluster zone 24 | DEPLOYMENT_NAME: gke-test # TODO: update to deployment name 25 | IMAGE: static-site 26 | 27 | jobs: 28 | setup-build-publish-deploy: 29 | name: Setup, Build, Publish, and Deploy 30 | runs-on: ubuntu-latest 31 | environment: production 32 | 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v2 36 | 37 | # Setup gcloud CLI 38 | - uses: google-github-actions/setup-gcloud@v0.2.0 39 | with: 40 | service_account_key: ${{ secrets.GKE_SA_KEY }} 41 | project_id: ${{ secrets.GKE_PROJECT }} 42 | 43 | # Configure Docker to use the gcloud command-line tool as a credential 44 | # helper for authentication 45 | - run: |- 46 | gcloud --quiet auth configure-docker 47 | 48 | # Get the GKE credentials so we can deploy to the cluster 49 | - uses: google-github-actions/get-gke-credentials@v0.2.1 50 | with: 51 | cluster_name: ${{ env.GKE_CLUSTER }} 52 | location: ${{ env.GKE_ZONE }} 53 | credentials: ${{ secrets.GKE_SA_KEY }} 54 | 55 | # Build the Docker image 56 | - name: Build 57 | run: |- 58 | docker build \ 59 | --tag "gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA" \ 60 | --build-arg GITHUB_SHA="$GITHUB_SHA" \ 61 | --build-arg GITHUB_REF="$GITHUB_REF" \ 62 | . 63 | 64 | # Push the Docker image to Google Container Registry 65 | - name: Publish 66 | run: |- 67 | docker push "gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA" 68 | 69 | # Set up kustomize 70 | - name: Set up Kustomize 71 | run: |- 72 | curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v3.1.0/kustomize_3.1.0_linux_amd64 73 | chmod u+x ./kustomize 74 | 75 | # Deploy the Docker image to the GKE cluster 76 | - name: Deploy 77 | run: |- 78 | ./kustomize edit set image gcr.io/PROJECT_ID/IMAGE:TAG=gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA 79 | ./kustomize build . | kubectl apply -f - 80 | kubectl rollout status deployment/$DEPLOYMENT_NAME 81 | kubectl get services -o wide 82 | -------------------------------------------------------------------------------- /src/Row.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | // imports DEFAULT(alias) export from axios.js 3 | import axios from "./axios"; 4 | import "./Row.css"; 5 | import YouTube from "react-youtube"; 6 | import movieTrailer from "movie-trailer"; 7 | 8 | const base_URL = "https://image.tmdb.org/t/p/original/"; 9 | 10 | // Row component 11 | function Row({ title, fetchURL, isLargeRow }) { 12 | /* Creating a movie state (short term memory) */ 13 | const [movies, setMovies] = useState([]); 14 | /* Creating a trailer state (short term memory) */ 15 | const [trailerURL, setTrailerURL] = useState(""); 16 | // Pulling information from tmdb API when the pages loads 17 | useEffect(() => { 18 | // Running async call 19 | async function fetchData() { 20 | // Waiting for the promise to come back with movie results, fetchURL(outside the code block) 21 | const request = await axios.get(fetchURL); 22 | setMovies(request.data.results); 23 | return request; 24 | } 25 | // if [empty], run once when the row loads, and dont run again 26 | fetchData(); 27 | }, [fetchURL]); 28 | // console.log(movies); 29 | 30 | const opts = { 31 | height: "390", 32 | width: "100%", 33 | playerVars: { 34 | // https://developers.google.com/youtube/player_parameters 35 | autoplay: 1, 36 | }, 37 | }; 38 | 39 | // When user clicks on the movie picture 40 | const handleClick = (movie) => { 41 | // If trailer is found clear the url 42 | if (trailerURL) { 43 | setTrailerURL(""); 44 | } else { 45 | // Search for movie trailer full url 46 | movieTrailer(movie?.name || "") 47 | .then((url) => { 48 | // https://www.youtube.com/watch?v=aSØDÆømlsdæ 49 | const urlParams = new URLSearchParams(new URL(url).search); // urlParams gives us everthing after the ? 50 | setTrailerURL(urlParams.get("v")); //urlParams gives us everything after v= 51 | // Displays error message if unable to find url 52 | }) 53 | .catch((error) => console.log(error)); 54 | } 55 | }; 56 | 57 | return ( 58 |
59 |

{title}

60 | {/* Container for movie rows */} 61 |
62 | {/* several row poster */} 63 | {/* Looping through movies array API */} 64 | {movies.map((movie) => ( 65 | // returns movie images in new array 66 | handleClick(movie)} 71 | // All poster same size (row__poster) except if you are larger row, then use 72 | // isLargeRow 73 | className={`row__poster ${isLargeRow && "row__posterLarge"}`} 74 | // Loads poster images from base url 75 | src={`${base_URL}${ 76 | isLargeRow ? movie.poster_path : movie.backdrop_path 77 | }`} 78 | alt={movie.name} 79 | /> 80 | ))} 81 | {/* Contain -> posters */} 82 |
83 | {/* Embedding youtube movie trailers to show */} 84 | {trailerURL && } 85 |
86 | ); 87 | } 88 | 89 | // Exporting Row function. Making it available 90 | export default Row; 91 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | --------------------------------------------------------------------------------