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 (
+
+
+ {movies.map(movie => (
+ -
+
+ -
+ ID: {movie.id}
+
+ -
+ Name: {movie.name}
+
+ -
+ Price: ${movie.price}
+
+
+
+
+ ))}
+
+
+ )
+}
\ 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 (
+
+
+ {cart.length ? cart.map(movie => (
+ -
+
+ -
+ ID: {movie.id}
+
+ -
+ Name: {movie.name}
+
+ -
+ Price: ${movie.price}
+
+
+
+
+
+ {movie.quantity}
+
+
+
+
+ )) : There no movies in the cart!
}
+
+
+
+ )
+}
\ 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 (
-
-
- {movies.map(o => (
- -
-
- -
- ID: {o.id}
-
- -
- Name: {o.name}
-
- -
- Price: ${o.price}
-
-
-
-
- ))}
-
-
-
+
+
)
}
\ 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 (
+
+ {loading ? (
+
+ ) : moviesShow.map(movie => (
+ -
+
+
+ - {movie.title}
+ - {movie.genres.join(', ')}
+ - {movie.year}
+
+
+ ))}
+
+ )
+}
\ 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
-
- ) : (
-
- {movies.map(movie => (
- -
-
-
- - ID: {movie.id}
- - Title: {movie.title}
- - Year: {movie.year}
- - Runtime: {movie.runtime}
- - Genres: {movie.genres.join(', ')}
-
-
- ))}
-
- )}
+
+
)
}
\ No newline at end of file