diff --git a/src/components/pages/Exercise01/assets/styles.css b/src/components/pages/Exercise01/assets/styles.css index cdba0ca..8ea9660 100644 --- a/src/components/pages/Exercise01/assets/styles.css +++ b/src/components/pages/Exercise01/assets/styles.css @@ -26,6 +26,11 @@ border-radius: .2em; } +.no-movie { + color: red; + font-size: 12px;; +} + .movies__list-card:last-child { margin-bottom: 0; } diff --git a/src/components/pages/Exercise01/components/Cart/Cart.js b/src/components/pages/Exercise01/components/Cart/Cart.js new file mode 100644 index 0000000..cfec7d4 --- /dev/null +++ b/src/components/pages/Exercise01/components/Cart/Cart.js @@ -0,0 +1,26 @@ +export const Cart = ({ movies, addMovieToCart }) => { + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/src/components/pages/Exercise01/components/Movies/Movies.js b/src/components/pages/Exercise01/components/Movies/Movies.js new file mode 100644 index 0000000..b1bde6c --- /dev/null +++ b/src/components/pages/Exercise01/components/Movies/Movies.js @@ -0,0 +1,37 @@ +export const Movies = ({ cart, totalPrice, decrement, increment }) => { + return ( +
+ +
+

Total: ${totalPrice}

+
+
+ ) +} \ No newline at end of file diff --git a/src/components/pages/Exercise01/hooks/useCart.js b/src/components/pages/Exercise01/hooks/useCart.js new file mode 100644 index 0000000..26312d9 --- /dev/null +++ b/src/components/pages/Exercise01/hooks/useCart.js @@ -0,0 +1,93 @@ +import { useState } from 'react' + +import { discountRules } from '../utils/discountRules'; + +export const useCart = () => { + const [cart, setCart] = useState([ + { + id: 1, + name: 'Star Wars', + price: 20, + quantity: 2 + } + ]); + + const decrement = ({ id }) => { + const checkMovieQuantity = cart.find((movie) => movie.id === id && movie.quantity === 1); + + if(checkMovieQuantity) { + const confirmRemove = window.confirm('Are you sure? This action will remove the movie!'); + + if(confirmRemove) { + const removeMovie = cart.filter((movie) => movie.id !== id); + + return setCart(removeMovie); + }; + + return; + } + + const movies = cart.map((movie) => { + if(movie.id === id) { + return { + ...movie, + quantity: movie.quantity - 1 + } + } + + return movie; + }); + + setCart(movies); + } + + const increment = ({ id }) => { + const movies = cart.map((movie) => { + if(movie.id === id) { + return { + ...movie, + quantity: movie.quantity + 1 + } + } + + return movie; + }); + + setCart(movies); + } + + const addMovieToCart = ({ movie }) => { + const movieExist = cart.find(({ id }) => movie.id === id); + + if (movieExist) { + return window.alert('Movie has already been added!'); + } + + setCart([...cart, { ...movie, quantity: 1 }]); + } + + const getTotal = () => { + const totalPrice = cart.reduce((total, { price, quantity }) => { + return total + price * quantity + }, 0) + + const moviesId = cart.map((movie) => movie.id).sort(); + const getDiscount = discountRules.find(({ m: rules }) => JSON.stringify(rules.sort()) === JSON.stringify(moviesId))?.discount + + const calculatePrice = totalPrice - (getDiscount * totalPrice); + + if(getDiscount) { + return calculatePrice.toFixed(2) + } + + return totalPrice; + } + + return { + cart, + totalPrice: getTotal(), + addMovieToCart, + increment, + decrement + } +} \ No newline at end of file diff --git a/src/components/pages/Exercise01/index.js b/src/components/pages/Exercise01/index.js index b36b04f..a90fa67 100644 --- a/src/components/pages/Exercise01/index.js +++ b/src/components/pages/Exercise01/index.js @@ -11,116 +11,37 @@ * You can modify all the code, this component isn't well designed intentionally. You can redesign it as you need. */ -import './assets/styles.css' -import { useState } from 'react' +import { Movies } from './components/Movies/Movies'; +import { Cart } from './components/Cart/Cart'; -export default function Exercise01 () { - const movies = [ - { - id: 1, - name: 'Star Wars', - price: 20 - }, - { - id: 2, - name: 'Minions', - price: 25 - }, - { - id: 3, - name: 'Fast and Furious', - price: 10 - }, - { - id: 4, - name: 'The Lord of the Rings', - price: 5 - } - ] +import { useCart } from './hooks/useCart'; - const discountRules = [ - { - m: [3, 2], - discount: 0.25 - }, - { - m: [2, 4, 1], - discount: 0.5 - }, - { - m: [4, 2], - discount: 0.1 - } - ] +import { movies } from './utils/movies'; - const [cart, setCart] = useState([ - { - id: 1, - name: 'Star Wars', - price: 20, - quantity: 2 - } - ]) +import './assets/styles.css' - const getTotal = () => 0 // TODO: Implement this + +export default function Exercise01 () { + const { + addMovieToCart, + cart, + decrement, + increment, + totalPrice + } = useCart(); return (
-
- -
-
- -
-

Total: ${getTotal()}

-
-
+ +
) } \ No newline at end of file diff --git a/src/components/pages/Exercise01/utils/discountRules.js b/src/components/pages/Exercise01/utils/discountRules.js new file mode 100644 index 0000000..11829cb --- /dev/null +++ b/src/components/pages/Exercise01/utils/discountRules.js @@ -0,0 +1,14 @@ +export const discountRules = [ + { + m: [3, 2], + discount: 0.25 + }, + { + m: [2, 4, 1], + discount: 0.5 + }, + { + m: [4, 2], + discount: 0.1 + } + ] \ No newline at end of file diff --git a/src/components/pages/Exercise01/utils/movies.js b/src/components/pages/Exercise01/utils/movies.js new file mode 100644 index 0000000..492faec --- /dev/null +++ b/src/components/pages/Exercise01/utils/movies.js @@ -0,0 +1,22 @@ +export const movies = [ + { + id: 1, + name: 'Star Wars', + price: 20 + }, + { + id: 2, + name: 'Minions', + price: 25 + }, + { + id: 3, + name: 'Fast and Furious', + price: 10 + }, + { + id: 4, + name: 'The Lord of the Rings', + price: 5 + } + ] \ No newline at end of file diff --git a/src/components/pages/Exercise02/assets/styles.css b/src/components/pages/Exercise02/assets/styles.css index 39e82de..935cc84 100644 --- a/src/components/pages/Exercise02/assets/styles.css +++ b/src/components/pages/Exercise02/assets/styles.css @@ -1,23 +1,44 @@ .movie-library { background-color: var(--black); color: var(--white); - min-height: 100vh; - padding: 2em; + background-image: url('./mountains.jpeg'); + background-repeat: no-repeat; + background-size: cover; + background-position: center -200px; + height: 100vh; } .movie-library__title { - font-size: 2em; + font-size: 3em; margin-bottom: 1em; + font-family: 'Poppins', sans-serif; + font-weight: 600; + padding: 4rem 4rem 0; } .movie-library__actions { display: flex; - margin-bottom: 1em; + padding: 0 4rem; +} + +.movie-library__actions button { + background-color: var(--ms-green); + border: 0; + height: 2.5rem; + border-bottom-right-radius: 0.375rem; + border-top-right-radius: 0.375rem; + font-weight: 600; + font-family: 'Poppins', sans-serif; } .movie-library__actions select { width: 100%; padding: .25em; + border-bottom-left-radius: 0.375rem; + border-top-left-radius: 0.375rem; + font-size: 1.125rem; + font-weight: 600; + border: 2px solid var(--ms-green); } .movie-library__actions button { @@ -33,25 +54,55 @@ } .movie-library__list { - padding: 1em; - background-color: #222222; + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 5rem; + padding: 6em 1rem; + background: linear-gradient(180deg, transparent 0.1%, #222222 0.5%); + min-height: 100vh; } .movie-library__card { + position: relative; + z-index: 1; background-color: #333333; margin-bottom: 1em; display: flex; align-items: center; + flex-direction: column; + width: 20%; + min-width: 21.875rem; + height: 28.125rem; + border-radius: 0.375rem; } .movie-library__card img { - width: 10em; + width: 100%; + height: 100%; + position: absolute; + z-index: 2; + border-radius: 0.375rem; } .movie-library__card ul { + display: flex; + justify-content: flex-end; + flex-direction: column; padding: 1em; + position: relative; + z-index: 5; + width: 100%; + height: 100%; + background: linear-gradient(180deg, transparent 25%, var(--ms-green) 100%); + box-sizing: border-box; +} + +.movie-library__card li:first-child { + font-weight: 600; } .movie-library__card li { line-height: 120%; + margin-bottom: 0.5rem; } diff --git a/src/components/pages/Exercise02/components/Header/Header.js b/src/components/pages/Exercise02/components/Header/Header.js new file mode 100644 index 0000000..ba13e44 --- /dev/null +++ b/src/components/pages/Exercise02/components/Header/Header.js @@ -0,0 +1,17 @@ +export const Header = ({ setGenre, setOrder, order, genres }) => { + return ( + <> +

+ Movie Library +

+
+ + +
+ + ) +} \ No newline at end of file diff --git a/src/components/pages/Exercise02/components/List/List.js b/src/components/pages/Exercise02/components/List/List.js new file mode 100644 index 0000000..2d22069 --- /dev/null +++ b/src/components/pages/Exercise02/components/List/List.js @@ -0,0 +1,20 @@ +export const List = ({ moviesShow, loading }) => { + return ( + + ) +} \ No newline at end of file diff --git a/src/components/pages/Exercise02/hooks/useGenres.js b/src/components/pages/Exercise02/hooks/useGenres.js new file mode 100644 index 0000000..32244ce --- /dev/null +++ b/src/components/pages/Exercise02/hooks/useGenres.js @@ -0,0 +1,38 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { useEffect, useState } from "react" + +export const useGenres = () => { + const [genres, setGenres] = useState([]) + const [genre, setGenre] = useState('All') + + const handleGenresFetch = async () => { + try { + const getGenres = await fetch('http://localhost:3001/genres'); + const results = await getGenres.json() + + setGenres(['All', ...results]) + } catch(error) { + console.error(error) + } + } + + const filterByGenre = ({ movies }) => { + const filtered = movies.filter((movie) => movie.genres.find((genreSelected) => genreSelected === genre)) + + if(genre === 'All') { + return null; + } + + return filtered + } + + useEffect(() => { + handleGenresFetch() + }, []) + + return { + genres, + setGenre, + filterByGenre + } +} \ No newline at end of file diff --git a/src/components/pages/Exercise02/hooks/useMovies.js b/src/components/pages/Exercise02/hooks/useMovies.js new file mode 100644 index 0000000..3caca96 --- /dev/null +++ b/src/components/pages/Exercise02/hooks/useMovies.js @@ -0,0 +1,32 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { useState, useEffect } from "react" + +export const useMovies = () => { + const [movies, setMovies] = useState([]) + const [fetchCount, setFetchCount] = useState(0) + const [loading, setLoading] = useState(false) + + const handleMovieFetch = async () => { + setLoading(true) + setFetchCount(fetchCount + 1) + + try { + const getMovies = await fetch('http://localhost:3001/movies?_limit=50'); + const results = await getMovies.json() + + setMovies(results); + setLoading(false); + } catch(error) { + console.error(error) + } + } + + useEffect(() => { + handleMovieFetch(); + }, []) + + return { + movies, + loading + } +} \ No newline at end of file diff --git a/src/components/pages/Exercise02/index.js b/src/components/pages/Exercise02/index.js index f66932f..f98f1cb 100644 --- a/src/components/pages/Exercise02/index.js +++ b/src/components/pages/Exercise02/index.js @@ -12,65 +12,38 @@ * You can modify all the code, this component isn't well designed intentionally. You can redesign it as you need. */ +import { useState } from "react"; + +import { List } from "./components/List/List"; + +import { useMovies } from "./hooks/useMovies"; +import { useGenres } from "./hooks/useGenres"; + import "./assets/styles.css"; -import { useEffect, useState } from "react"; +import { Header } from "./components/Header/Header"; export default function Exercise02 () { - const [movies, setMovies] = useState([]) - const [fetchCount, setFetchCount] = useState(0) - const [loading, setLoading] = useState(false) + const [order, setOrder] = useState('Descending') + + const { movies, loading } = useMovies() + const { filterByGenre, genres, setGenre } = useGenres() + + const orderMovies = ({ movies }) => { + if(order !== 'Ascending') { + return movies?.sort((a, b) => a.year - b.year) + } - const handleMovieFetch = () => { - 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) - }) - .catch(() => { - console.log('Run yarn movie-api for fake api') - }) + if(order !== 'Descending') { + return movies?.sort((a, b) => b.year - a.year) + } } - useEffect(() => { - handleMovieFetch() - }, [handleMovieFetch]) + const moviesShow = orderMovies({ movies: filterByGenre({ movies }) || movies }) return (
-

- Movie Library -

-
- - -
- {loading ? ( -
-

Loading...

-

Fetched {fetchCount} times

-
- ) : ( - - )} +
+
) } \ No newline at end of file