From e02fc82af1e1b7e490c8d03357b86e964e931a39 Mon Sep 17 00:00:00 2001 From: hdariodev Date: Thu, 30 Mar 2023 17:40:02 -0400 Subject: [PATCH 1/3] build: dependency update --- .eslintrc | 14 + .prettierrc | 8 + package.json | 39 +- src/App.js | 18 - src/App.tsx | 13 + src/components/pages/Home/index.js | 24 - src/index.js | 7 +- .../pages/Exercise01/assets/styles.css | 0 .../index.js => pages/Exercise01/index.tsx} | 73 +- .../pages/Exercise02/assets/mountains.jpeg | Bin .../pages/Exercise02/assets/styles.css | 0 .../pages/Exercise02/exercise02.png | Bin .../index.js => pages/Exercise02/index.tsx} | 51 +- .../pages/Home/assets/styles.css | 0 .../pages/Home/components/OttNextLogo.js | 0 src/pages/Home/index.tsx | 19 + src/router/index.tsx | 49 + tsconfig.json | 21 + yarn.lock | 11960 ++++++++-------- 19 files changed, 5973 insertions(+), 6323 deletions(-) create mode 100644 .eslintrc create mode 100644 .prettierrc delete mode 100644 src/App.js create mode 100644 src/App.tsx delete mode 100644 src/components/pages/Home/index.js rename src/{components => }/pages/Exercise01/assets/styles.css (100%) rename src/{components/pages/Exercise01/index.js => pages/Exercise01/index.tsx} (67%) rename src/{components => }/pages/Exercise02/assets/mountains.jpeg (100%) rename src/{components => }/pages/Exercise02/assets/styles.css (100%) rename src/{components => }/pages/Exercise02/exercise02.png (100%) rename src/{components/pages/Exercise02/index.js => pages/Exercise02/index.tsx} (72%) rename src/{components => }/pages/Home/assets/styles.css (100%) rename src/{components => }/pages/Home/components/OttNextLogo.js (100%) create mode 100644 src/pages/Home/index.tsx create mode 100644 src/router/index.tsx create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..ea7992c --- /dev/null +++ b/.eslintrc @@ -0,0 +1,14 @@ +{ + "env": { + "browser": true, + "es6": true + }, + "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended"], + "parser": "@typescript-eslint/parser", + "plugins": ["react", "@typescript-eslint"], + "rules": { + "react/prop-types": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off" + } +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..f7ed3e5 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "none" +} diff --git a/package.json b/package.json index f5b839f..85e4fcf 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,6 @@ "name": "prueba-frontend-react", "version": "1.0.1", "private": true, - "dependencies": { - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.1.0", - "@testing-library/user-event": "^12.1.10", - "json-server": "^0.16.3", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-router-dom": "^5.2.1", - "react-scripts": "4.0.3", - "web-vitals": "^1.0.1" - }, "scripts": { "movie-api": "json-server --watch db.json --port 3001 --delay 1000", "start": "react-scripts start", @@ -20,11 +9,29 @@ "test": "react-scripts test", "eject": "react-scripts eject" }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] + "dependencies": { + "react": "18.2.0", + "react-dom": "18.2.0", + "react-router-dom": "6.10.0", + "swr": "^2.1.1", + "web-vitals": "^1.0.1" + }, + "devDependencies": { + "@testing-library/jest-dom": "^5.11.4", + "@testing-library/react": "^11.1.0", + "@testing-library/user-event": "^12.1.10", + "@types/jest": "^29.5.0", + "@types/node": "^18.15.11", + "@types/react": "^18.0.31", + "@types/react-dom": "^18.0.11", + "@types/react-router-dom": "^5.3.3", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^4.2.1", + "json-server": "^0.16.3", + "prettier": "^2.8.7", + "react-scripts": "5.0.1", + "sass": "^1.60.0", + "typescript": "^5.0.2" }, "browserslist": { "production": [ diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 02561f5..0000000 --- a/src/App.js +++ /dev/null @@ -1,18 +0,0 @@ -import { BrowserRouter, Route, Switch } from 'react-router-dom'; -import Exercise01 from './components/pages/Exercise01'; -import Exercise02 from './components/pages/Exercise02'; -import Home from './components/pages/Home' - -function App() { - return ( - - - - - - - - ); -} - -export default App; diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..19f258b --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import Router from 'router'; + +function App() { + return ( + + + + ); +} + +export default App; diff --git a/src/components/pages/Home/index.js b/src/components/pages/Home/index.js deleted file mode 100644 index 1d33aaf..0000000 --- a/src/components/pages/Home/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import { Link } from "react-router-dom"; -import "./assets/styles.css"; -import OttNextLogo from "./components/OttNextLogo"; - -export default function Home() { - return ( -
-
- -

- OTT Next React Technical Test -

-
-
- - Exercise 01 - - - Exercise 02 - -
-
- ) -} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 4f6301e..19ff0b5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,14 +1,13 @@ import './styles.css'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import App from './App'; import reportWebVitals from './reportWebVitals'; -ReactDOM.render( +ReactDOM.createRoot(document.getElementById('root')).render( - , - document.getElementById('root') + ); // If you want to start measuring performance in your app, pass a function diff --git a/src/components/pages/Exercise01/assets/styles.css b/src/pages/Exercise01/assets/styles.css similarity index 100% rename from src/components/pages/Exercise01/assets/styles.css rename to src/pages/Exercise01/assets/styles.css diff --git a/src/components/pages/Exercise01/index.js b/src/pages/Exercise01/index.tsx similarity index 67% rename from src/components/pages/Exercise01/index.js rename to src/pages/Exercise01/index.tsx index b36b04f..15d9121 100644 --- a/src/components/pages/Exercise01/index.js +++ b/src/pages/Exercise01/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-irregular-whitespace */ /** * Exercise 01: The Retro Movie Store * Implement a shopping cart with the next features for the Movie Store that is selling retro dvds: @@ -7,14 +8,14 @@ * 4. Apply discount rules. You have an array of offers with discounts depending of the combination of movie you have in your cart. * You have to apply all discounts in the rules array (discountRules). * Ex: If m: [1, 2, 3], it means the discount will be applied to the total when the cart has all that products in only. - * + * * You can modify all the code, this component isn't well designed intentionally. You can redesign it as you need. */ +import React from 'react'; +import './assets/styles.css'; +import { useState } from 'react'; -import './assets/styles.css' -import { useState } from 'react' - -export default function Exercise01 () { +export default function Exercise01() { const movies = [ { id: 1, @@ -36,7 +37,7 @@ export default function Exercise01 () { name: 'The Lord of the Rings', price: 5 } - ] + ]; const discountRules = [ { @@ -50,8 +51,8 @@ export default function Exercise01 () { { m: [4, 2], discount: 0.1 - } - ] + } + ]; const [cart, setCart] = useState([ { @@ -60,59 +61,39 @@ export default function Exercise01 () { price: 20, quantity: 2 } - ]) + ]); - const getTotal = () => 0 // TODO: Implement this + const getTotal = () => 0; // TODO: Implement this return (
    - {movies.map(o => ( -
  • + {movies.map((o, index) => ( +
    • -
    • - ID: {o.id} -
    • -
    • - Name: {o.name} -
    • -
    • - Price: ${o.price} -
    • +
    • ID: {o.id}
    • +
    • Name: {o.name}
    • +
    • Price: ${o.price}
    - +
  • ))}
    - {cart.map(x => ( -
  • + {cart.map((x, index) => ( +
    • -
    • - ID: {x.id} -
    • -
    • - Name: {x.name} -
    • -
    • - Price: ${x.price} -
    • +
    • ID: {x.id}
    • +
    • Name: {x.name}
    • +
    • Price: ${x.price}
    - - - {x.quantity} - - + + {x.quantity} +
  • ))} @@ -122,5 +103,5 @@ export default function Exercise01 () {
- ) -} \ No newline at end of file + ); +} diff --git a/src/components/pages/Exercise02/assets/mountains.jpeg b/src/pages/Exercise02/assets/mountains.jpeg similarity index 100% rename from src/components/pages/Exercise02/assets/mountains.jpeg rename to src/pages/Exercise02/assets/mountains.jpeg diff --git a/src/components/pages/Exercise02/assets/styles.css b/src/pages/Exercise02/assets/styles.css similarity index 100% rename from src/components/pages/Exercise02/assets/styles.css rename to src/pages/Exercise02/assets/styles.css diff --git a/src/components/pages/Exercise02/exercise02.png b/src/pages/Exercise02/exercise02.png similarity index 100% rename from src/components/pages/Exercise02/exercise02.png rename to src/pages/Exercise02/exercise02.png diff --git a/src/components/pages/Exercise02/index.js b/src/pages/Exercise02/index.tsx similarity index 72% rename from src/components/pages/Exercise02/index.js rename to src/pages/Exercise02/index.tsx index f66932f..2f1c492 100644 --- a/src/components/pages/Exercise02/index.js +++ b/src/pages/Exercise02/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react-hooks/exhaustive-deps */ /** * Exercise 02: Movie Library * We are trying to make a movie library for internal users. We are facing some issues by creating this, try to help us following the next steps: @@ -8,42 +7,40 @@ * list of movies that belong to that gender (Filter all movies). * 3. Order the movies by year and implement a button that switch between ascending and descending order for the list * 4. Try to recreate the user interface that comes with the exercise (exercise02.png) - * + * * You can modify all the code, this component isn't well designed intentionally. You can redesign it as you need. */ +import React from 'react'; +import './assets/styles.css'; +import { useEffect, useState } from 'react'; -import "./assets/styles.css"; -import { useEffect, useState } from "react"; - -export default function Exercise02 () { - const [movies, setMovies] = useState([]) - const [fetchCount, setFetchCount] = useState(0) - const [loading, setLoading] = useState(false) +export default function Exercise02() { + const [movies, setMovies] = useState([]); + const [fetchCount, setFetchCount] = useState(0); + const [loading, setLoading] = useState(false); const handleMovieFetch = () => { - setLoading(true) - setFetchCount(fetchCount + 1) - console.log('Getting movies') + setLoading(true); + setFetchCount(fetchCount + 1); + console.log('Getting movies'); fetch('http://localhost:3001/movies?_limit=50') - .then(res => res.json()) - .then(json => { - setMovies(json) - setLoading(false) + .then((res) => res.json()) + .then((json) => { + setMovies(json); + setLoading(false); }) .catch(() => { - console.log('Run yarn movie-api for fake api') - }) - } + console.log('Run yarn movie-api for fake api'); + }); + }; useEffect(() => { - handleMovieFetch() - }, [handleMovieFetch]) + handleMovieFetch(); + }, [handleMovieFetch]); return (
-

- Movie Library -

+

Movie Library

updateGender(ev.target.value)} + > + {genders && + genders.map((gender: any) => ( + + ))} + + +
+ ); +}; + +export default Filter; diff --git a/src/pages/Exercise02/components/Filter/index.ts b/src/pages/Exercise02/components/Filter/index.ts new file mode 100644 index 0000000..fc8d9c1 --- /dev/null +++ b/src/pages/Exercise02/components/Filter/index.ts @@ -0,0 +1 @@ +export { default as Filter } from './Filter'; diff --git a/src/pages/Exercise02/components/Loading/Loading.tsx b/src/pages/Exercise02/components/Loading/Loading.tsx new file mode 100644 index 0000000..9a06759 --- /dev/null +++ b/src/pages/Exercise02/components/Loading/Loading.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +const Loading = ({ fetchCount }: any) => { + return ( +
+

Loading...

+
+

Fetched {fetchCount} times

+
+ ); +}; + +export default Loading; diff --git a/src/pages/Exercise02/components/Loading/index.ts b/src/pages/Exercise02/components/Loading/index.ts new file mode 100644 index 0000000..e6efad4 --- /dev/null +++ b/src/pages/Exercise02/components/Loading/index.ts @@ -0,0 +1 @@ +export { default as Loading } from './Loading'; diff --git a/src/pages/Exercise02/hooks/useGender.tsx b/src/pages/Exercise02/hooks/useGender.tsx new file mode 100644 index 0000000..8624385 --- /dev/null +++ b/src/pages/Exercise02/hooks/useGender.tsx @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react'; +import { getGenders } from '../services/genders'; + +export const useGender = () => { + const [genders, setGenders] = useState([]); + + const handleMovieGenders = async () => { + const resp = await getGenders(); + setGenders(resp); + }; + + useEffect(() => { + handleMovieGenders(); + }, []); + + return { + genders + }; +}; diff --git a/src/pages/Exercise02/hooks/useMovie.tsx b/src/pages/Exercise02/hooks/useMovie.tsx new file mode 100644 index 0000000..e0b6bb1 --- /dev/null +++ b/src/pages/Exercise02/hooks/useMovie.tsx @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'react'; +import { getMovies } from '../services/movies'; + +export const useMovie = (gender: string, order: string) => { + const [movies, setMovies] = useState([]); + const [fetchCount, setFetchCount] = useState(0); + const [loading, setLoading] = useState(false); + + const handleMovieFetch = async (gender: string, order: string) => { + setLoading(true); + setFetchCount(fetchCount + 1); + const resp = await getMovies(gender, order); + setMovies(resp); + setLoading(false); + }; + + useEffect(() => { + handleMovieFetch(gender, order); + }, []); + + return { + movies, + loading, + fetchCount, + handleMovieFetch + }; +}; diff --git a/src/pages/Exercise02/index.tsx b/src/pages/Exercise02/index.tsx index ebfac45..0a711f6 100644 --- a/src/pages/Exercise02/index.tsx +++ b/src/pages/Exercise02/index.tsx @@ -13,60 +13,42 @@ import React from 'react'; import './assets/styles.css'; import { useEffect, useState } from 'react'; +import { Filter } from './components/Filter'; +import { Loading } from './components/Loading'; +import { Card } from './components/Card'; +import { useMovie } from './hooks/useMovie'; +import { useGender } from './hooks/useGender'; export default function Exercise02() { - const [movies, setMovies] = useState([]); - const [fetchCount, setFetchCount] = useState(0); - const [loading, setLoading] = useState(false); + const [genderSelected, setGenderSelected] = useState(''); + const [yearOrder, setYearOrder] = useState('desc'); - const handleMovieFetch = () => { - setLoading(true); - setFetchCount(fetchCount + 1); - fetch('http://localhost:3001/movies?_limit=50') - .then((res) => res.json()) - .then((json) => { - setMovies(json); - setLoading(false); - }) - .catch(() => { - console.log('Run yarn movie-api for fake api'); - }); + const { movies, loading, fetchCount, handleMovieFetch } = useMovie(genderSelected, yearOrder); + const { genders } = useGender(); + + const updateMovies = (gender: any, order: any) => { + handleMovieFetch(gender, order); + }; + + const updateGender = async (gender: any) => { + await setGenderSelected(gender); + await updateMovies(gender, yearOrder); }; - useEffect(() => { - handleMovieFetch(); - }, [handleMovieFetch]); + const updateYearOrder = () => { + if (yearOrder === 'asc') { + setYearOrder('desc'); + } else { + setYearOrder('asc'); + } + updateMovies(genderSelected, yearOrder); + }; return (

Movie Library

-
- - -
- {loading ? ( -
-

Loading...

-

Fetched {fetchCount} times

-
- ) : ( -
    - {movies.map((movie: any) => ( -
  • - {movie.title} -
      -
    • ID: {movie.id}
    • -
    • Title: {movie.title}
    • -
    • Year: {movie.year}
    • -
    • Runtime: {movie.runtime}
    • -
    • Genres: {movie.genres.join(', ')}
    • -
    -
  • - ))} -
- )} + + {loading ? : }
); } diff --git a/src/pages/Exercise02/services/genders.tsx b/src/pages/Exercise02/services/genders.tsx new file mode 100644 index 0000000..2ee1be5 --- /dev/null +++ b/src/pages/Exercise02/services/genders.tsx @@ -0,0 +1,10 @@ +import { API_URL } from '../../../utilities/constants'; + +export const getGenders = async () => { + try { + const resp = await fetch(`${API_URL}genres`); + return await resp.json(); + } catch (error) { + console.log('Run yarn movie-api for fake api', error); + } +}; diff --git a/src/pages/Exercise02/services/movies.tsx b/src/pages/Exercise02/services/movies.tsx new file mode 100644 index 0000000..6da0bad --- /dev/null +++ b/src/pages/Exercise02/services/movies.tsx @@ -0,0 +1,12 @@ +import { API_URL } from 'utilities/constants'; + +export const getMovies = async (gender = '', order = '') => { + const hasGender = gender === '' ? '' : `&genres_like=${gender}`; + const hasOrder = order === '' ? '' : `&_sort=year&_order=${order}`; + try { + const resp = await fetch(`${API_URL}movies?_limit=50${hasGender}${hasOrder}`); + return await resp.json(); + } catch (error) { + console.log('Run yarn movie-api for fake api', error); + } +}; diff --git a/src/types/Items.ts b/src/types/Items.ts new file mode 100644 index 0000000..25ffc84 --- /dev/null +++ b/src/types/Items.ts @@ -0,0 +1,16 @@ +export interface Item { + id: number; + title: string; + year: string; + runtime: string; + genres: Genres; + director: string; + actors: string; + plot: string; + posterUrl: string; +} + +export interface Genres { + [x: string]: any; + [index: number]: string; +} diff --git a/src/utilities/constants.ts b/src/utilities/constants.ts index d29715c..0b7bb0b 100644 --- a/src/utilities/constants.ts +++ b/src/utilities/constants.ts @@ -12,3 +12,5 @@ export const DISCOUNT_RULES = [ discount: 0.1 } ]; + +export const API_URL = `http://localhost:3001/`;