├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── assets │ ├── img │ │ ├── 404 │ │ │ ├── 404.png │ │ │ ├── guards-404.png │ │ │ └── page-not-found.png │ │ ├── logo.png │ │ ├── google.png │ │ ├── guard.png │ │ ├── guards.png │ │ ├── masks.png │ │ ├── series.png │ │ ├── shopping.png │ │ ├── the boss.png │ │ ├── github-logo.png │ │ ├── series │ │ │ ├── ep1.jpg │ │ │ ├── ep2.jpg │ │ │ ├── ep3.jpg │ │ │ ├── ep4.jpg │ │ │ ├── ep5.jpg │ │ │ ├── ep6.jpg │ │ │ ├── ep7.jpg │ │ │ ├── ep8.jpg │ │ │ └── ep9.jpg │ │ ├── squid-store.png │ │ ├── animated-logo.gif │ │ ├── catalog-title.png │ │ ├── empty-wish-list.png │ │ ├── products │ │ │ ├── saepost.png │ │ │ ├── t-shirt.png │ │ │ ├── dark-mask.png │ │ │ ├── geosyrup.png │ │ │ ├── guard-doll.png │ │ │ └── guard-mask.png │ │ ├── wish-list-title.png │ │ ├── background-series.png │ │ └── bottom-background.jpg │ ├── fonts │ │ ├── Game Of Squids.otf │ │ ├── Game Of Squids.ttf │ │ └── SharpGrotesk-Book15.otf │ ├── api │ │ └── server.js │ ├── data │ │ └── products.json │ ├── css │ │ └── reset.css │ └── js │ │ └── info.js ├── hooks │ ├── index.js │ ├── useToggle.js │ ├── useFirestore.js │ └── useAuth.js ├── components │ ├── ShoppingCartTable │ │ ├── ShoppingCartTable.css │ │ └── ShoppingCartTable.jsx │ ├── ModalMenu │ │ ├── ModalMenu.css │ │ └── ModalMenu.jsx │ ├── SkeletonProduct │ │ ├── SkeletonProduct.css │ │ └── SkeletonProduct.jsx │ ├── Features │ │ ├── Features.jsx │ │ └── Features.css │ ├── SignInButton │ │ └── SignInButton.jsx │ ├── Footer │ │ ├── Footer.css │ │ └── Footer.jsx │ ├── SignInForm │ │ └── SignInForm.jsx │ ├── index.js │ ├── Series │ │ ├── Series.css │ │ └── Series.jsx │ ├── FeatureItem │ │ ├── FeatureItem.jsx │ │ └── FeatureItem.css │ ├── SignUpForm │ │ └── SignUpForm.jsx │ ├── Catalog │ │ ├── Catalog.css │ │ └── Catalog.jsx │ ├── BottomSection │ │ ├── BottomSection.jsx │ │ └── BottomSection.css │ ├── MainSection │ │ ├── MainSection.jsx │ │ └── MainSection.css │ ├── NavBar │ │ ├── NavBar.css │ │ └── NavBar.jsx │ ├── DropdownMenu │ │ └── DropdownMenu.jsx │ └── Product │ │ ├── Product.css │ │ └── Product.jsx ├── index.js ├── contexts │ ├── index.js │ ├── UserContext.js │ ├── WishContext.js │ └── CartContext.js ├── index.css ├── services │ └── api.js ├── pages │ ├── Home │ │ ├── Home.css │ │ └── Home.jsx │ ├── PageNotFound │ │ ├── PageNotFound.css │ │ └── PageNotFound.jsx │ └── Login │ │ ├── Login.jsx │ │ └── Login.css ├── Router.js ├── reducers │ └── wishReducer.js └── firebase.js ├── .gitignore ├── README.md └── package.json /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/google.png -------------------------------------------------------------------------------- /src/assets/img/guard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/guard.png -------------------------------------------------------------------------------- /src/assets/img/guards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/guards.png -------------------------------------------------------------------------------- /src/assets/img/masks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/masks.png -------------------------------------------------------------------------------- /src/assets/img/series.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/series.png -------------------------------------------------------------------------------- /src/assets/img/404/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/404/404.png -------------------------------------------------------------------------------- /src/assets/img/shopping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/shopping.png -------------------------------------------------------------------------------- /src/assets/img/the boss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/the boss.png -------------------------------------------------------------------------------- /src/assets/img/github-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/github-logo.png -------------------------------------------------------------------------------- /src/assets/img/series/ep1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/series/ep1.jpg -------------------------------------------------------------------------------- /src/assets/img/series/ep2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/series/ep2.jpg -------------------------------------------------------------------------------- /src/assets/img/series/ep3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/series/ep3.jpg -------------------------------------------------------------------------------- /src/assets/img/series/ep4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/series/ep4.jpg -------------------------------------------------------------------------------- /src/assets/img/series/ep5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/series/ep5.jpg -------------------------------------------------------------------------------- /src/assets/img/series/ep6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/series/ep6.jpg -------------------------------------------------------------------------------- /src/assets/img/series/ep7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/series/ep7.jpg -------------------------------------------------------------------------------- /src/assets/img/series/ep8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/series/ep8.jpg -------------------------------------------------------------------------------- /src/assets/img/series/ep9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/series/ep9.jpg -------------------------------------------------------------------------------- /src/assets/img/squid-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/squid-store.png -------------------------------------------------------------------------------- /src/assets/img/404/guards-404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/404/guards-404.png -------------------------------------------------------------------------------- /src/assets/img/animated-logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/animated-logo.gif -------------------------------------------------------------------------------- /src/assets/img/catalog-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/catalog-title.png -------------------------------------------------------------------------------- /src/assets/fonts/Game Of Squids.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/fonts/Game Of Squids.otf -------------------------------------------------------------------------------- /src/assets/fonts/Game Of Squids.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/fonts/Game Of Squids.ttf -------------------------------------------------------------------------------- /src/assets/img/empty-wish-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/empty-wish-list.png -------------------------------------------------------------------------------- /src/assets/img/products/saepost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/products/saepost.png -------------------------------------------------------------------------------- /src/assets/img/products/t-shirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/products/t-shirt.png -------------------------------------------------------------------------------- /src/assets/img/wish-list-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/wish-list-title.png -------------------------------------------------------------------------------- /src/assets/img/404/page-not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/404/page-not-found.png -------------------------------------------------------------------------------- /src/assets/img/background-series.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/background-series.png -------------------------------------------------------------------------------- /src/assets/img/bottom-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/bottom-background.jpg -------------------------------------------------------------------------------- /src/assets/img/products/dark-mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/products/dark-mask.png -------------------------------------------------------------------------------- /src/assets/img/products/geosyrup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/products/geosyrup.png -------------------------------------------------------------------------------- /src/assets/img/products/guard-doll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/products/guard-doll.png -------------------------------------------------------------------------------- /src/assets/img/products/guard-mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/img/products/guard-mask.png -------------------------------------------------------------------------------- /src/assets/fonts/SharpGrotesk-Book15.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejandroaperez1994g/squid-game/HEAD/src/assets/fonts/SharpGrotesk-Book15.otf -------------------------------------------------------------------------------- /src/hooks/index.js: -------------------------------------------------------------------------------- 1 | import useAuth from './useAuth'; 2 | import useToggle from './useToggle'; 3 | import useFirestore from './useFirestore'; 4 | 5 | export { useAuth, useToggle, useFirestore }; 6 | -------------------------------------------------------------------------------- /src/components/ShoppingCartTable/ShoppingCartTable.css: -------------------------------------------------------------------------------- 1 | .nextui-c-grJsex-ikUaMfZ-css { 2 | max-width: 500px; 3 | } 4 | .nextui-c-PJLV-gulvcB-isFocusVisible-false { 5 | align-items: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/ModalMenu/ModalMenu.css: -------------------------------------------------------------------------------- 1 | p, 2 | b { 3 | font-family: "Sharp"; 4 | } 5 | 6 | .empty__title { 7 | padding: 0.5rem; 8 | box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; 9 | border-radius: 10px; 10 | } 11 | -------------------------------------------------------------------------------- /src/hooks/useToggle.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | const useToggle = () => { 4 | const [state, setState] = useState(false); 5 | 6 | const onToggle = () => { 7 | setState((prevState) => !prevState); 8 | }; 9 | return { state, onToggle }; 10 | }; 11 | 12 | export default useToggle; 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client"; 2 | import { NextUIProvider } from "@nextui-org/react"; 3 | import Router from "./Router"; 4 | 5 | import "./index.css"; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById("root")); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /src/contexts/index.js: -------------------------------------------------------------------------------- 1 | import { UserContextProvider, UserContext } from './UserContext'; 2 | import { WishContextProvider, WishContext } from './WishContext'; 3 | import { CartContextProvider, CartContext } from './CartContext'; 4 | 5 | export { 6 | CartContextProvider, 7 | UserContextProvider, 8 | WishContextProvider, 9 | CartContext, 10 | UserContext, 11 | WishContext, 12 | }; 13 | -------------------------------------------------------------------------------- /.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 | .env 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Squid Store 2 | 3 | ## Demo link: 4 | 5 | Access my site at [squid-store](https://squid-game-sigma.vercel.app/) 6 | 7 | ## Table of Content: 8 | 9 | - [About](#about) 10 | - [Screenshots](#screenshots) 11 | - [Technologies](#technologies) 12 | - [Setup](#setup) 13 | 14 | ## About 15 | 16 | This is a landing page inspired by the Squid Games, with an e-commerce section, where we can add products to the cart and proceed to payment using Stripe (test-mode) 17 | 18 | ## Screenshots 19 | 20 | ## Technologies 21 | 22 | ## Setup 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/SkeletonProduct/SkeletonProduct.css: -------------------------------------------------------------------------------- 1 | .skeleton { 2 | height: 23rem; 3 | width: 18.5rem; 4 | border: 2px solid white; 5 | border-radius: 10px; 6 | border: 1px solid rgba(255, 255, 255, 0.18); 7 | padding: 0.5rem; 8 | margin: 0 0.5rem; 9 | background: rgba(255, 255, 255, 0.1); 10 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; 11 | } 12 | .skeleton__bottom { 13 | display: flex; 14 | width: 100%; 15 | justify-content: space-around; 16 | height: 40%; 17 | } 18 | 19 | @media screen and (max-width: 768px) { 20 | .skeleton { 21 | height: 20rem; 22 | width: 15rem; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Features/Features.jsx: -------------------------------------------------------------------------------- 1 | import { memo } from "react"; 2 | import FeatureItem from "../FeatureItem/FeatureItem"; 3 | import { info } from "../../assets/js/info"; 4 | import "./Features.css"; 5 | 6 | const Features = () => { 7 | return ( 8 |
9 | {info[0].features.map((feat, index) => { 10 | return ( 11 | 17 | ); 18 | })} 19 |
20 | ); 21 | }; 22 | 23 | export default memo(Features); 24 | -------------------------------------------------------------------------------- /src/components/Features/Features.css: -------------------------------------------------------------------------------- 1 | .features__section { 2 | display: flex; 3 | justify-content: space-around; 4 | align-items: center; 5 | height: 35vh; 6 | width: 100%; 7 | padding: 0 1rem; 8 | gap: 1rem; 9 | margin-top: 5rem; 10 | background: linear-gradient( 11 | 83.88deg, 12 | #1b1834 25%, 13 | rgba(255, 255, 255, 0) 100% 14 | ); 15 | } 16 | 17 | @media screen and (max-width: 768px) { 18 | .features__section { 19 | padding: 1.5rem; 20 | flex-direction: column; 21 | height: fit-content; 22 | } 23 | } 24 | 25 | @media screen and (min-width: 768px) and (max-width: 1024px) { 26 | .features__section { 27 | justify-content: space-between; 28 | margin-top: 10rem; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/SignInButton/SignInButton.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@nextui-org/react'; 2 | 3 | const SignInButton = ({ text, action, icon }) => { 4 | return ( 5 | 28 | ); 29 | }; 30 | 31 | export default SignInButton; 32 | -------------------------------------------------------------------------------- /src/contexts/UserContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useEffect, useState } from 'react'; 2 | import { CartContextProvider } from '.'; 3 | import { WishContextProvider } from '.'; 4 | 5 | export const UserContext = createContext(); 6 | let user = JSON.parse(localStorage.getItem('userData')) || null; 7 | 8 | export const UserContextProvider = ({ children }) => { 9 | const [userData, setUserData] = useState(user); 10 | 11 | useEffect(() => { 12 | localStorage.setItem('userData', JSON.stringify(userData)); 13 | }, [userData]); 14 | 15 | return ( 16 | 17 | 18 | {children} 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/services/api.js: -------------------------------------------------------------------------------- 1 | export const fetchProducts = async () => { 2 | try { 3 | const response = await fetch(process.env.REACT_APP_API_SERVER_URL); 4 | const data = await response.json(); 5 | return data; 6 | } catch (error) { 7 | return { error }; 8 | } 9 | }; 10 | 11 | 12 | export const getStripeCheckout = async (shoppingCart) => { 13 | try { 14 | const response = await fetch(process.env.REACT_APP_STRIPE_SERVER_URL, { 15 | method: 'POST', 16 | headers: { 17 | 'Content-Type': 'application/json', 18 | }, 19 | body: JSON.stringify({ 20 | shoppingCart, 21 | }), 22 | }); 23 | const data = await response.json(); 24 | return data; 25 | } catch (error) { 26 | console.error(error); 27 | return error; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/pages/Home/Home.css: -------------------------------------------------------------------------------- 1 | @import url("../../assets/css/reset.css"); 2 | @font-face { 3 | font-family: "Squid"; 4 | src: local("Squid"), 5 | url("../../assets/fonts/Game\ Of\ Squids.ttf") format("truetype"); 6 | font-weight: bold; 7 | } 8 | @font-face { 9 | font-family: "Sharp"; 10 | src: local("Sharp"), 11 | url("../../assets/fonts/SharpGrotesk-Book15.otf") format("truetype"); 12 | font-weight: bold; 13 | } 14 | 15 | .Home { 16 | display: flex; 17 | flex-direction: column; 18 | width: 100vw; 19 | align-items: center; 20 | background: linear-gradient(146.2deg, #ff0055 0%, #000066 100%), 21 | linear-gradient(316.52deg, #ff0055 0%, #000066 100%); 22 | } 23 | 24 | .squid_font { 25 | font-family: "Squid"; 26 | } 27 | 28 | .sharp_font { 29 | font-family: "Sharp"; 30 | font-size: 1.3rem; 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/PageNotFound/PageNotFound.css: -------------------------------------------------------------------------------- 1 | .main__container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | min-height: 100vh; 6 | background: linear-gradient(146.2deg, #ff0055 0%, #000066 100%), 7 | linear-gradient(316.52deg, #ff0055 0%, #000066 100%); 8 | font-family: 'Sharp'; 9 | } 10 | 11 | .section__404 { 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: center; 15 | border: 2px solid red; 16 | width: 400px; 17 | background: rgba(255, 255, 255, 0.7); 18 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); 19 | backdrop-filter: blur(20px); 20 | -webkit-backdrop-filter: blur(20px); 21 | border: 1px solid rgba(255, 255, 255, 0.18); 22 | border-radius: 10px; 23 | padding: 2rem; 24 | } 25 | 26 | .section__page_img { 27 | margin: 1rem 0; 28 | } 29 | -------------------------------------------------------------------------------- /src/Router.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 2 | 3 | import Login from './pages/Login/Login'; 4 | import Home from './pages/Home/Home'; 5 | import PageNotFound from './pages/PageNotFound/PageNotFound'; 6 | 7 | import { UserContextProvider } from './contexts'; 8 | 9 | const Router = () => { 10 | return ( 11 | 12 | 13 | 14 | } /> 15 | 16 | } /> 17 | } /> 18 | } /> 19 | } /> 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default Router; 27 | -------------------------------------------------------------------------------- /src/components/SkeletonProduct/SkeletonProduct.jsx: -------------------------------------------------------------------------------- 1 | import Skeleton from "@mui/material/Skeleton"; 2 | import "./SkeletonProduct.css"; 3 | 4 | const SkeletonProduct = () => { 5 | return ( 6 |
7 | 14 | 15 | 16 | 17 |
18 | 19 | 20 |
21 |
22 | ); 23 | }; 24 | 25 | export default SkeletonProduct; 26 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.css: -------------------------------------------------------------------------------- 1 | .footer__section { 2 | display: flex; 3 | justify-content: space-around; 4 | align-items: center; 5 | width: 90%; 6 | height: 10vh; 7 | color: white; 8 | } 9 | 10 | .footer__section_text { 11 | font-size: 1.2rem; 12 | font-weight: 600; 13 | } 14 | 15 | .link:hover { 16 | color: #df3978; 17 | cursor: pointer; 18 | } 19 | 20 | .footer__section_button { 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | width: -moz-fit-content; 25 | width: fit-content; 26 | color: white; 27 | font-size: 0.9rem; 28 | padding: 0.5em 1.5em; 29 | border-radius: 10px; 30 | background: linear-gradient(145deg, #d13571, #f83f86); 31 | border: none; 32 | } 33 | 34 | .footer__section_button:hover { 35 | cursor: pointer; 36 | } 37 | 38 | @media screen and (max-width: 768px) { 39 | .link { 40 | display: none; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/SignInForm/SignInForm.jsx: -------------------------------------------------------------------------------- 1 | import { useAuth } from '../../hooks'; 2 | import { Input, Button } from '@nextui-org/react'; 3 | 4 | const SignInForm = () => { 5 | const { handleSignInWithEmail, email, setEmail, password, setPassword } = 6 | useAuth(); 7 | return ( 8 |
9 | setEmail(e.target.value)} 13 | /> 14 | setPassword(e.target.value)} 18 | /> 19 | 20 |

Forgot password?

21 | 24 | 25 | ); 26 | }; 27 | 28 | export default SignInForm; 29 | -------------------------------------------------------------------------------- /src/reducers/wishReducer.js: -------------------------------------------------------------------------------- 1 | export const ACTIONS = { 2 | ADD_WISH: 'ADD_WISH', 3 | REMOVE_WISH: 'REMOVE_WISH', 4 | EMPTY_WISH: 'EMPTY_WISH', 5 | SET_WISH_LIST: 'SET_WISH_LIST', 6 | }; 7 | export const wishReducer = (wishList, action) => { 8 | switch (action.type) { 9 | case ACTIONS.ADD_WISH: 10 | return [ 11 | ...wishList, 12 | { 13 | id: action.payload.id, 14 | name: action.payload.title, 15 | img: action.payload.image, 16 | price: action.payload.discount, 17 | }, 18 | ]; 19 | case ACTIONS.REMOVE_WISH: 20 | return wishList.filter((item) => item.id !== action.payload.id); 21 | case ACTIONS.EMPTY_WISH: 22 | return []; 23 | case ACTIONS.SET_WISH_LIST: 24 | if (action.payload) { 25 | return [...action.payload]; 26 | } 27 | break; 28 | default: 29 | return wishList; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import BottomSection from './BottomSection/BottomSection'; 2 | import Catalog from './Catalog/Catalog'; 3 | import FeatureItem from './FeatureItem/FeatureItem'; 4 | import Features from './Features/Features'; 5 | import Footer from './Footer/Footer'; 6 | import MainSection from './MainSection/MainSection'; 7 | import NavBar from './NavBar/NavBar'; 8 | import Product from './Product/Product'; 9 | import Series from './Series/Series'; 10 | import ShoppingCartTable from './ShoppingCartTable/ShoppingCartTable'; 11 | import SignInForm from './SignInForm/SignInForm'; 12 | import SignUpForm from './SignUpForm/SignUpForm'; 13 | import SignInButton from './SignInButton/SignInButton'; 14 | 15 | export { 16 | SignInButton, 17 | SignInForm, 18 | SignUpForm, 19 | BottomSection, 20 | Catalog, 21 | FeatureItem, 22 | Features, 23 | Footer, 24 | MainSection, 25 | NavBar, 26 | Product, 27 | Series, 28 | ShoppingCartTable, 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/Series/Series.css: -------------------------------------------------------------------------------- 1 | .series__container { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | height: 75vh; 7 | width: 90%; 8 | margin-top: -5rem; 9 | padding: 2rem; 10 | background-image: url('../../assets/img/background-series.png'); 11 | background-position: center; 12 | background-repeat: no-repeat; 13 | } 14 | .series__image { 15 | height: 25rem; 16 | } 17 | 18 | .series__title { 19 | margin-bottom: 2rem; 20 | margin-top: 15rem; 21 | } 22 | 23 | .alice-carousel__wrapper { 24 | width: 100%; 25 | } 26 | 27 | @media screen and (max-width: 768px) { 28 | .series__title { 29 | height: 2.5rem; 30 | } 31 | .series__container { 32 | height: 90vh; 33 | background-position: initial; 34 | margin-top: -15rem; 35 | } 36 | .series__image { 37 | height: 20rem !important; 38 | } 39 | } 40 | 41 | @media screen and (min-width: 768px) and (max-width: 1024px) { 42 | .series__title { 43 | height: 3.5rem; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/FeatureItem/FeatureItem.jsx: -------------------------------------------------------------------------------- 1 | import { memo } from "react"; 2 | import * as icons from "@fortawesome/free-solid-svg-icons"; 3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 4 | import PropTypes from "prop-types"; 5 | import "./FeatureItem.css"; 6 | 7 | const FeatureItem = ({ id, title, text, iconShape }) => { 8 | return ( 9 |
10 | 13 |

{title}

14 |

{text}

15 |
16 | ); 17 | }; 18 | 19 | FeatureItem.propTypes = { 20 | id: PropTypes.number.isRequired, 21 | title: PropTypes.string.isRequired, 22 | text: PropTypes.string.isRequired, 23 | iconShape: PropTypes.string.isRequired, 24 | }; 25 | 26 | FeatureItem.defaultProps = { 27 | id: 0, 28 | title: "", 29 | text: "", 30 | iconShape: "", 31 | }; 32 | 33 | export default memo(FeatureItem); 34 | -------------------------------------------------------------------------------- /src/firebase.js: -------------------------------------------------------------------------------- 1 | import { initializeApp } from 'firebase/app'; 2 | import { 3 | getAuth, 4 | GoogleAuthProvider, 5 | GithubAuthProvider, 6 | onAuthStateChanged, 7 | } from 'firebase/auth'; 8 | import { getFirestore } from 'firebase/firestore'; 9 | 10 | const app = initializeApp({ 11 | apiKey: process.env.REACT_APP_FIREBASE_API_KEY, 12 | authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, 13 | projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, 14 | storageBucket: process.env.REACT_APP_FIREABSE_STORAGE_BUCKET, 15 | messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, 16 | appId: process.env.REACT_APP_FIREBASE_APP_ID, 17 | }); 18 | 19 | export default app; 20 | export const auth = getAuth(app); 21 | export const db = getFirestore(app); 22 | 23 | onAuthStateChanged(auth, (user) => { 24 | if (user) { 25 | console.log('user logged in'); 26 | } else { 27 | console.log('user logged out'); 28 | } 29 | }); 30 | 31 | export const provider = new GoogleAuthProvider(); 32 | export const githubProvider = new GithubAuthProvider(); 33 | -------------------------------------------------------------------------------- /src/components/SignUpForm/SignUpForm.jsx: -------------------------------------------------------------------------------- 1 | import { useAuth } from '../../hooks'; 2 | import { Input, Button } from '@nextui-org/react'; 3 | 4 | const SignUpForm = () => { 5 | const { 6 | handleSignUpWithEmailAndPassword, 7 | email, 8 | setEmail, 9 | password, 10 | setPassword, 11 | repPassword, 12 | setRepPassword, 13 | } = useAuth(); 14 | return ( 15 |
16 | setEmail(e.target.value)} 20 | /> 21 | setPassword(e.target.value)} 25 | /> 26 | setRepPassword(e.target.value)} 30 | /> 31 |

Forgot password?

32 | 35 | 36 | ); 37 | }; 38 | 39 | export default SignUpForm; 40 | -------------------------------------------------------------------------------- /src/components/Series/Series.jsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react'; 2 | import AliceCarousel from 'react-alice-carousel'; 3 | import 'react-alice-carousel/lib/alice-carousel.css'; 4 | import { info } from '../../assets/js/info'; 5 | import './Series.css'; 6 | 7 | const handleDragStart = (e) => e.preventDefault(); 8 | 9 | const items = []; 10 | 11 | info[0].series.forEach((item, index) => { 12 | items.push( 13 | 20 | ); 21 | }); 22 | 23 | const Series = () => { 24 | return ( 25 |
26 | 31 | 39 |
40 | ); 41 | }; 42 | 43 | export default memo(Series); 44 | -------------------------------------------------------------------------------- /src/pages/Home/Home.jsx: -------------------------------------------------------------------------------- 1 | import { Toaster } from 'react-hot-toast'; 2 | import { useEffect, useContext } from 'react'; 3 | import { useLocation, useNavigate } from 'react-router-dom'; 4 | import { CartContext } from '../../contexts/CartContext'; 5 | import './Home.css'; 6 | 7 | import { 8 | NavBar, 9 | MainSection, 10 | Features, 11 | Catalog, 12 | Series, 13 | BottomSection, 14 | Footer, 15 | } from '../../components'; 16 | 17 | function Home() { 18 | const { setShoppingCart } = useContext(CartContext); 19 | const { pathname } = useLocation(); 20 | const navigator = useNavigate(); 21 | 22 | useEffect(() => { 23 | if (pathname === '/success') { 24 | setShoppingCart([]); 25 | navigator('/'); 26 | } else { 27 | navigator('/'); 28 | } 29 | // eslint-disable-next-line react-hooks/exhaustive-deps 30 | }, [pathname]); 31 | 32 | return ( 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | ); 44 | } 45 | 46 | export default Home; 47 | -------------------------------------------------------------------------------- /src/components/Catalog/Catalog.css: -------------------------------------------------------------------------------- 1 | .catalog__container { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: flex-start; 5 | justify-content: center; 6 | height: 50vh; 7 | width: 90vw; 8 | margin: 1.5rem 0; 9 | overflow: hidden; 10 | } 11 | 12 | .catalog__title { 13 | height: 2.5rem; 14 | } 15 | 16 | .catalog__products { 17 | display: grid; 18 | grid-auto-flow: column; 19 | justify-content: space-between; 20 | align-items: center; 21 | gap: 1rem; 22 | width: 90vw; 23 | margin-top: 1rem; 24 | overflow-x: auto; 25 | overscroll-behavior-inline: contain; 26 | } 27 | 28 | .catalog__products { 29 | -ms-overflow-style: none; /* for Internet Explorer, Edge */ 30 | scrollbar-width: none; /* for Firefox */ 31 | overflow-x: scroll; 32 | } 33 | .catalog__products::-webkit-scrollbar { 34 | display: none; /* for Chrome, Safari, and Opera */ 35 | } 36 | 37 | @media screen and (max-width: 768px) { 38 | .catalog__container { 39 | height: 50vh; 40 | } 41 | .catalog__title { 42 | height: 1.5rem; 43 | } 44 | .catalog__products { 45 | overflow-x: scroll; 46 | } 47 | } 48 | 49 | @media screen and (min-width: 768px) and (max-width: 1424px) { 50 | .catalog__products { 51 | overflow-x: scroll; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/BottomSection/BottomSection.jsx: -------------------------------------------------------------------------------- 1 | import { memo } from "react"; 2 | import "./BottomSection.css"; 3 | 4 | const BottomSection = () => { 5 | return ( 6 |
7 |
8 | 13 |

14 | Ready For The Next Game! 15 |

16 |

17 | Stay tuned we are live soon. Very soon you will find us on Play Store. 18 |

19 |
20 | 25 | 26 |
27 |

28 | * We will let you know when game is available on Play Store.{" "} 29 |

30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | export default memo(BottomSection); 37 | -------------------------------------------------------------------------------- /src/assets/api/server.js: -------------------------------------------------------------------------------- 1 | // This is your test secret API key. 2 | require("dotenv").config(); 3 | const stripe = require("stripe")(process.env.STRIPE_PRIVATE_KEY); 4 | const express = require("express"); 5 | const app = express(); 6 | const cors = require("cors"); 7 | 8 | app.use(express.json()); 9 | app.use( 10 | cors({ 11 | // origin: "http://localhost:3000", 12 | origin: process.env.CLIENT_URL, 13 | }) 14 | ); 15 | 16 | app.post("/create-checkout-session", async (req, res) => { 17 | try { 18 | const session = await stripe.checkout.sessions.create({ 19 | payment_method_types: ["card"], 20 | mode: "payment", 21 | line_items: req.body.shoppingCart.map((item) => { 22 | return { 23 | price_data: { 24 | currency: "eur", 25 | product_data: { 26 | name: item.name, 27 | }, 28 | unit_amount: item.price * 100, 29 | }, 30 | quantity: item.amount, 31 | }; 32 | }), 33 | success_url: `${process.env.CLIENT_URL}/success`, 34 | cancel_url: `${process.env.CLIENT_URL}/cancel`, 35 | }); 36 | res.json({ url: session.url }); 37 | } catch (e) { 38 | res.status(500).json({ error: e.message }); 39 | } 40 | }); 41 | const PORT = 4242; 42 | app.listen(PORT, () => console.log(`Running on port ${PORT}`)); 43 | -------------------------------------------------------------------------------- /src/assets/data/products.json: -------------------------------------------------------------------------------- 1 | { 2 | "catalog": [ 3 | { 4 | "title": "Front Man Mask Squid Game", 5 | "image": "dark-mask.png", 6 | "price": 70, 7 | "discount": 64, 8 | "category": "Mask" 9 | }, 10 | { 11 | "title": "The Squid Game Guard Mask", 12 | "image": "guard-mask.png", 13 | "price": 60, 14 | "discount": 44, 15 | "category": "Mask" 16 | }, 17 | { 18 | "title": "The Squid Game 성기훈 Doll", 19 | "image": "geosyrup.png", 20 | "price": 65, 21 | "discount": 45, 22 | "category": "Toys" 23 | }, 24 | { 25 | "title": "The Squid Game Guard Doll", 26 | "image": "guard-doll.png", 27 | "price": 85, 28 | "discount": 55, 29 | "category": "Funko" 30 | }, 31 | { 32 | "title": "The Squid Game 티셔츠 PLAYER T-Shirt", 33 | "image": "t-shirt.png", 34 | "price": 90, 35 | "discount": 75, 36 | "category": "Clothing" 37 | }, 38 | { 39 | "title": "Squid Game Player 067 Kang Sae", 40 | "image": "saepost.png", 41 | "price": 70, 42 | "discount": 64, 43 | "category": "Toys" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /src/contexts/WishContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useEffect, useReducer, useContext } from 'react'; 2 | import { updateDoc, doc } from 'firebase/firestore'; 3 | import { db } from '../firebase'; 4 | import { wishReducer } from '../reducers/wishReducer'; 5 | import { UserContext } from './UserContext'; 6 | 7 | export const WishContext = createContext(); 8 | let wishListStorage = JSON.parse(localStorage.getItem('wishList')) || []; 9 | 10 | export const WishContextProvider = ({ children }) => { 11 | const [wishList, dispatch] = useReducer(wishReducer, wishListStorage); 12 | const { userData } = useContext(UserContext); 13 | 14 | const uploadWishList = async (user, wishlist) => { 15 | try { 16 | const userDoc = doc(db, 'squid-store-users', user); 17 | const newField = { wishlist: wishlist }; 18 | await updateDoc(userDoc, newField); 19 | } catch (error) { 20 | console.log(error); 21 | } 22 | }; 23 | 24 | useEffect(() => { 25 | localStorage.setItem('wishList', JSON.stringify(wishList)); 26 | if (userData) { 27 | uploadWishList(userData.email, wishList); 28 | } 29 | // eslint-disable-next-line react-hooks/exhaustive-deps 30 | }, [userData?.email, wishList]); 31 | 32 | return ( 33 | 34 | {children} 35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/ModalMenu/ModalMenu.jsx: -------------------------------------------------------------------------------- 1 | import { Modal } from "@nextui-org/react"; 2 | import { useContext } from "react"; 3 | import ShoppingCartTable from "../ShoppingCartTable/ShoppingCartTable"; 4 | import { WishContext } from "../../contexts/WishContext"; 5 | import "./ModalMenu.css"; 6 | 7 | const ModalMenu = ({ visible, closeHandler }) => { 8 | const { wishList } = useContext(WishContext); 9 | 10 | return ( 11 | <> 12 | 18 | 22 | title 26 |
27 |
28 | 29 | {wishList.length === 0 ? ( 30 | 35 | ) : ( 36 | 37 | )} 38 | 39 |
40 | 41 | ); 42 | }; 43 | 44 | export default ModalMenu; 45 | -------------------------------------------------------------------------------- /src/contexts/CartContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useState, useEffect, useContext } from 'react'; 2 | 3 | import toast from 'react-hot-toast'; 4 | import { UserContext } from './UserContext'; 5 | import { db } from '../firebase'; 6 | import { updateDoc, doc } from 'firebase/firestore'; 7 | 8 | export const CartContext = createContext(); 9 | let cart = JSON.parse(localStorage.getItem('cart')) || []; 10 | 11 | export const CartContextProvider = ({ children }) => { 12 | const [shoppingCart, setShoppingCart] = useState(cart); 13 | const { userData } = useContext(UserContext); 14 | 15 | const uploadShoppingCart = async (user, shoppingCart) => { 16 | try { 17 | const userDoc = doc(db, 'squid-store-users', user); 18 | const newField = { shopping_cart: shoppingCart }; 19 | await updateDoc(userDoc, newField); 20 | } catch (error) { 21 | console.log(error); 22 | } 23 | }; 24 | 25 | useEffect(() => { 26 | localStorage.setItem('cart', JSON.stringify(shoppingCart)); 27 | if (userData) { 28 | uploadShoppingCart(userData.email, shoppingCart); 29 | } 30 | }, [shoppingCart, userData]); 31 | 32 | const notify = (message, type = 'success') => { 33 | if (type === 'error') { 34 | toast.error(message); 35 | return; 36 | } 37 | toast.success(message); 38 | }; 39 | 40 | return ( 41 | 42 | {children} 43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/pages/PageNotFound/PageNotFound.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@nextui-org/react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { useState } from 'react'; 4 | import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrowLeft'; 5 | import HomeIcon from '@mui/icons-material/Home'; 6 | import './PageNotFound.css'; 7 | 8 | const PageNotFound = () => { 9 | const [isShown, setIsShown] = useState(false); 10 | const navigator = useNavigate(); 11 | return ( 12 |
13 |
14 | 404 19 | 404 24 | 404 29 | 40 |
41 |
42 | ); 43 | }; 44 | 45 | export default PageNotFound; 46 | -------------------------------------------------------------------------------- /src/components/FeatureItem/FeatureItem.css: -------------------------------------------------------------------------------- 1 | .feature__item { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | height: 60%; 6 | justify-content: center; 7 | gap: 1rem; 8 | padding: 1rem; 9 | border-radius: 1rem; 10 | color: white; 11 | font-size: 1rem; 12 | background: linear-gradient(40.42deg, #3d4c7d 0%, #19233b 31%); 13 | border: 3px solid #1e2b49; 14 | } 15 | 16 | .feature__item_button { 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | width: 3rem; 21 | height: 3rem; 22 | color: white; 23 | padding: 0.7em 1.7em; 24 | font-size: 15px; 25 | border-radius: 10px; 26 | background: linear-gradient(145deg, #d13571, #f83f86); 27 | border: none; 28 | } 29 | 30 | .feature__item_button:hover { 31 | transform: scale(1.1); 32 | } 33 | 34 | .feature__item_title { 35 | color: #ca2866; 36 | } 37 | 38 | .feature__item_text { 39 | text-align: center; 40 | } 41 | 42 | @media screen and (max-width: 768px) { 43 | .feature__item { 44 | height: 70%; 45 | justify-content: center; 46 | gap: 1rem; 47 | } 48 | } 49 | 50 | @media screen and (min-width: 768px) and (max-width: 1024px) { 51 | .feature__item { 52 | height: 70%; 53 | justify-content: center; 54 | gap: 1rem; 55 | } 56 | .feature__item_title { 57 | font-size: 1.5rem; 58 | font-weight: 600; 59 | text-align: center; 60 | } 61 | .feature__item_button { 62 | width: 5rem; 63 | height: 5rem; 64 | font-size: 1.5rem; 65 | } 66 | .feature__item_text { 67 | font-size: 1.2rem; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import { memo } from "react"; 2 | import { BsGithub } from "react-icons/bs"; 3 | import { BsLinkedin } from "react-icons/bs"; 4 | import { SiGmail } from "react-icons/si"; 5 | import "./Footer.css"; 6 | 7 | const Footer = () => { 8 | return ( 9 |
10 |

11 | Privacy Policy 12 |

13 |

14 | Term of Service 15 |

16 |

17 | Language 18 |

19 |

2022 Alejandro

20 | 25 | 28 | 29 | 34 | 37 | 38 | 43 | 46 | 47 |
48 | ); 49 | }; 50 | 51 | export default memo(Footer); 52 | -------------------------------------------------------------------------------- /src/components/MainSection/MainSection.jsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import "./MainSection.css"; 3 | 4 | const MainSection = () => { 5 | return ( 6 | 16 |
17 | mask 22 | 23 |
24 |

All The Squid Game Products You Will Find Here

25 |
26 |
27 |
28 | 29 |
30 | mask 35 | mask 40 | mask 45 |
46 |
47 | ); 48 | }; 49 | 50 | export default MainSection; 51 | -------------------------------------------------------------------------------- /src/assets/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, 7 | body, 8 | div, 9 | span, 10 | applet, 11 | object, 12 | iframe, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | blockquote, 21 | pre, 22 | a, 23 | abbr, 24 | acronym, 25 | address, 26 | big, 27 | cite, 28 | code, 29 | del, 30 | dfn, 31 | em, 32 | img, 33 | ins, 34 | kbd, 35 | q, 36 | s, 37 | samp, 38 | small, 39 | strike, 40 | strong, 41 | sub, 42 | sup, 43 | tt, 44 | var, 45 | b, 46 | u, 47 | i, 48 | center, 49 | dl, 50 | dt, 51 | dd, 52 | ol, 53 | ul, 54 | li, 55 | fieldset, 56 | form, 57 | label, 58 | legend, 59 | table, 60 | caption, 61 | tbody, 62 | tfoot, 63 | thead, 64 | tr, 65 | th, 66 | td, 67 | article, 68 | aside, 69 | canvas, 70 | details, 71 | embed, 72 | figure, 73 | figcaption, 74 | footer, 75 | header, 76 | hgroup, 77 | menu, 78 | nav, 79 | output, 80 | ruby, 81 | section, 82 | summary, 83 | time, 84 | mark, 85 | audio, 86 | video { 87 | margin: 0; 88 | padding: 0; 89 | border: 0; 90 | font-size: 100%; 91 | font: inherit; 92 | vertical-align: baseline; 93 | } 94 | /* HTML5 display-role reset for older browsers */ 95 | article, 96 | aside, 97 | details, 98 | figcaption, 99 | figure, 100 | footer, 101 | header, 102 | hgroup, 103 | menu, 104 | nav, 105 | section { 106 | display: block; 107 | } 108 | body { 109 | line-height: 1; 110 | } 111 | ol, 112 | ul { 113 | list-style: none; 114 | } 115 | blockquote, 116 | q { 117 | quotes: none; 118 | } 119 | blockquote:before, 120 | blockquote:after, 121 | q:before, 122 | q:after { 123 | content: ""; 124 | content: none; 125 | } 126 | table { 127 | border-collapse: collapse; 128 | border-spacing: 0; 129 | } 130 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 26 | Squid Store 27 | 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/NavBar/NavBar.css: -------------------------------------------------------------------------------- 1 | .navbar__container { 2 | display: flex; 3 | justify-content: space-evenly; 4 | padding-top: 1rem; 5 | padding-bottom: 1rem; 6 | align-items: center; 7 | gap: 3rem; 8 | width: 90%; 9 | } 10 | 11 | .navbar__item { 12 | color: white; 13 | text-decoration: none; 14 | } 15 | .navbar__item:hover { 16 | color: #00a0c6; 17 | text-decoration: none; 18 | cursor: pointer; 19 | } 20 | a { 21 | color: white; 22 | } 23 | a:hover { 24 | color: #00a0c6; 25 | text-decoration: none; 26 | cursor: pointer; 27 | } 28 | .navbar__logo { 29 | height: 2rem; 30 | } 31 | 32 | .navbar__loginButton { 33 | padding: 0.6rem 1rem; 34 | font-family: "Sharp"; 35 | border: 2px solid white; 36 | color: white; 37 | background-color: transparent; 38 | font-size: 1.3rem; 39 | } 40 | 41 | .navbar__loginButton:hover { 42 | cursor: pointer; 43 | } 44 | .total__wrapper { 45 | align-self: flex-end; 46 | } 47 | 48 | .MuiList-root { 49 | width: auto; 50 | padding: 0.5rem !important; 51 | -webkit-font-smoothing: auto; 52 | -moz-font-smoothing: auto; 53 | -moz-osx-font-smoothing: grayscale; 54 | -font-smoothing: auto; 55 | text-rendering: optimizeLegibility; 56 | font-smooth: always; 57 | -webkit-tap-highlight-color: transparent; 58 | -webkit-touch-callout: none; 59 | } 60 | .MuiPaper-root { 61 | display: flex; 62 | justify-content: center; 63 | } 64 | .cart__logo { 65 | height: 2.5rem; 66 | } 67 | 68 | @media screen and (max-width: 768px) { 69 | .navbar__container { 70 | justify-content: space-between; 71 | } 72 | .navbar__item { 73 | display: none; 74 | } 75 | .navbar__item_img { 76 | display: block; 77 | } 78 | .MuiList-root { 79 | width: 20rem; 80 | } 81 | } 82 | 83 | @media screen and (min-width: 768px) and (max-width: 1024px) { 84 | .navbar__container { 85 | justify-content: center; 86 | gap: 2rem; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "squid", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.9.3", 7 | "@emotion/styled": "^11.9.3", 8 | "@fortawesome/fontawesome-svg-core": "^6.1.1", 9 | "@fortawesome/free-solid-svg-icons": "^6.1.1", 10 | "@fortawesome/react-fontawesome": "^0.2.0", 11 | "@mui/icons-material": "^5.8.4", 12 | "@mui/material": "^5.8.6", 13 | "@nextui-org/react": "^1.0.0-beta.9", 14 | "@stripe/react-stripe-js": "^1.9.0", 15 | "@stripe/stripe-js": "^1.32.0", 16 | "@testing-library/jest-dom": "^5.16.4", 17 | "@testing-library/react": "^13.3.0", 18 | "@testing-library/user-event": "^13.5.0", 19 | "cors": "^2.8.5", 20 | "dotenv": "^16.0.1", 21 | "express": "^4.18.1", 22 | "firebase": "^9.9.0", 23 | "framer-motion": "^6.5.1", 24 | "nodemon": "^2.0.19", 25 | "react": "^18.2.0", 26 | "react-alice-carousel": "^2.6.1", 27 | "react-dom": "^18.2.0", 28 | "react-hot-toast": "^2.3.0", 29 | "react-icons": "^4.4.0", 30 | "react-id-swiper": "^4.0.0", 31 | "react-router-dom": "^6.3.0", 32 | "react-scripts": "5.0.1", 33 | "stripe": "^9.13.0", 34 | "swiper": "^8.2.6", 35 | "web-vitals": "^2.1.4" 36 | }, 37 | "scripts": { 38 | "start": "react-scripts start", 39 | "build": "react-scripts build", 40 | "test": "react-scripts test", 41 | "eject": "react-scripts eject", 42 | "server": "json-server --watch ./src/assets/data/products.json --port 3004", 43 | "nodemon": "nodemon ./src/assets/api/server.js" 44 | }, 45 | "eslintConfig": { 46 | "extends": [ 47 | "react-app", 48 | "react-app/jest" 49 | ] 50 | }, 51 | "browserslist": { 52 | "production": [ 53 | ">0.2%", 54 | "not dead", 55 | "not op_mini all" 56 | ], 57 | "development": [ 58 | "last 1 chrome version", 59 | "last 1 firefox version", 60 | "last 1 safari version" 61 | ] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/assets/js/info.js: -------------------------------------------------------------------------------- 1 | export const info = [ 2 | { 3 | features: [ 4 | { 5 | title: "Free Shipping", 6 | text: "Now you can shop for it and enjoy a good deal on Squid game store. Star from €30.", 7 | icon: "faTruck", 8 | }, 9 | { 10 | title: "30 Day Return", 11 | text: "When returning the items, we do a refund of the order. If you wish to exchange for another item or color.", 12 | icon: "faArrowRotateForward", 13 | }, 14 | { 15 | title: "Best Quality", 16 | text: "We use the best quality material in aour product. You can find the best quality product in our shop.", 17 | icon: "faStar", 18 | }, 19 | ], 20 | catalog: [ 21 | { 22 | title: "Front Man Mask Squid Game", 23 | image: "dark-mask.png", 24 | price: 70, 25 | discount: 64, 26 | }, 27 | { 28 | title: "The Squid Game Guard Mask", 29 | image: "guard-mask.png", 30 | price: 60, 31 | discount: 44, 32 | }, 33 | { 34 | title: "The Squid Game 성기훈 Doll", 35 | image: "geosyrup.png", 36 | price: 65, 37 | discount: 45, 38 | }, 39 | { 40 | title: "The Squid Game Guard Doll", 41 | image: "guard-doll.png", 42 | price: 85, 43 | discount: 55, 44 | }, 45 | { 46 | title: "The Squid Game 티셔츠 PLAYER T-Shirt", 47 | image: "t-shirt.png", 48 | price: 90, 49 | discount: 75, 50 | }, 51 | { 52 | title: "Squid Game Player 067 Kang Sae", 53 | image: "saepost.png", 54 | price: 70, 55 | discount: 64, 56 | }, 57 | ], 58 | series: [ 59 | { 60 | url: "ep1.jpg", 61 | }, 62 | { 63 | url: "ep2.jpg", 64 | }, 65 | { 66 | url: "ep3.jpg", 67 | }, 68 | { 69 | url: "ep4.jpg", 70 | }, 71 | { 72 | url: "ep5.jpg", 73 | }, 74 | { 75 | url: "ep6.jpg", 76 | }, 77 | { 78 | url: "ep7.jpg", 79 | }, 80 | { 81 | url: "ep8.jpg", 82 | }, 83 | { 84 | url: "ep9.jpg", 85 | }, 86 | ], 87 | }, 88 | ]; 89 | -------------------------------------------------------------------------------- /src/components/Catalog/Catalog.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useContext } from 'react'; 2 | import { fetchProducts } from '../../services/api'; 3 | import Product from '../Product/Product'; 4 | import { CartContext } from '../../contexts/CartContext'; 5 | import SkeletonProduct from '../SkeletonProduct/SkeletonProduct'; 6 | 7 | import './Catalog.css'; 8 | 9 | const Catalog = ({ dispatch }) => { 10 | const [catalog, setCatalog] = useState([]); 11 | const [isLoading, setIsLoading] = useState(true); 12 | const { notify } = useContext(CartContext); 13 | 14 | const handleRequest = async (retries) => { 15 | const response = await fetchProducts(); 16 | 17 | if (response.catalog.length > 1) { 18 | setCatalog(response.catalog); 19 | setIsLoading(false); 20 | } else { 21 | if (retries > 0) { 22 | setTimeout(() => { 23 | handleRequest(retries - 1); 24 | }, 10000); 25 | } else { 26 | console.error(response.error.message); 27 | notify(`API Error: ${response.error.message}.`, 'error'); 28 | notify(`API Error: please try again later.`, 'error'); 29 | } 30 | } 31 | }; 32 | 33 | useEffect(() => { 34 | handleRequest(5); 35 | // eslint-disable-next-line react-hooks/exhaustive-deps 36 | }, []); 37 | 38 | return ( 39 |
40 | title 45 |
46 | {isLoading ? ( 47 | <> 48 | 49 | 50 | 51 | 52 | 53 | 54 | ) : ( 55 | catalog.map((item, index) => { 56 | return ( 57 | 66 | ); 67 | }) 68 | )} 69 |
70 |
71 | ); 72 | }; 73 | 74 | export default Catalog; 75 | -------------------------------------------------------------------------------- /src/components/MainSection/MainSection.css: -------------------------------------------------------------------------------- 1 | .main__section { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | height: 90vh; 6 | width: 90%; 7 | 8 | background-image: url("../../assets/img/squid-store.png"); 9 | background-position: center; 10 | background-repeat: no-repeat; 11 | background-size: 40rem; 12 | } 13 | 14 | .main__section_container { 15 | display: flex; 16 | justify-content: space-between; 17 | align-items: center; 18 | padding: 1.5rem; 19 | } 20 | 21 | .main__section_top_img { 22 | height: 19rem; 23 | } 24 | 25 | .main__section_top_text { 26 | color: white; 27 | width: 20%; 28 | } 29 | 30 | hr { 31 | height: 3px; 32 | background-color: #e83b7d; 33 | border: none; 34 | } 35 | 36 | .main__section_top_img_4 { 37 | display: none; 38 | } 39 | 40 | @media screen and (max-width: 768px) { 41 | .main__section { 42 | background-size: 0rem; 43 | height: 120vh; 44 | justify-content: start; 45 | } 46 | .main__section_container { 47 | flex-direction: column; 48 | } 49 | .main__section_top_img_4 { 50 | display: block; 51 | } 52 | .main__section_top_text { 53 | margin-top: 4rem; 54 | font-size: 2rem; 55 | width: 60%; 56 | text-align: center; 57 | } 58 | .main__section_top_img_1 { 59 | content: url("../../assets/img/squid-store.png"); 60 | } 61 | .main__section_top_img_2 { 62 | margin-left: -10rem; 63 | } 64 | .main__section_top_img_3 { 65 | margin-right: -10rem; 66 | } 67 | .main__section_top_img_4 { 68 | display: block; 69 | margin-right: -10rem; 70 | } 71 | .main__section_top_img { 72 | height: 10rem; 73 | } 74 | } 75 | 76 | @media screen and (min-width: 768px) and (max-width: 1024px) { 77 | .main__section { 78 | background-size: 0rem; 79 | height: 110vh; 80 | } 81 | .main__section_container { 82 | flex-direction: column; 83 | } 84 | .main__section_top_text { 85 | margin-top: 4rem; 86 | font-size: 2rem; 87 | width: 60%; 88 | text-align: center; 89 | } 90 | .main__section_top_img_1 { 91 | content: url("../../assets/img/squid-store.png"); 92 | } 93 | .main__section_top_img_2 { 94 | margin-left: -10rem; 95 | } 96 | .main__section_top_img_3 { 97 | margin-right: -10rem; 98 | } 99 | .main__section_top_img_4 { 100 | display: block; 101 | margin-right: -10rem; 102 | } 103 | .main__section_top_img { 104 | height: 17rem; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/hooks/useFirestore.js: -------------------------------------------------------------------------------- 1 | import { db } from '../firebase'; 2 | import { 3 | collection, 4 | doc, 5 | setDoc, 6 | getDocs, 7 | getDoc, 8 | updateDoc, 9 | } from 'firebase/firestore'; 10 | import { useContext } from 'react'; 11 | import { CartContext, UserContext, WishContext } from '../contexts'; 12 | import { ACTIONS } from '../reducers/wishReducer'; 13 | 14 | const useFirestore = () => { 15 | const { setShoppingCart } = useContext(CartContext); 16 | const { setUserData } = useContext(UserContext); 17 | const { dispatch } = useContext(WishContext); 18 | 19 | const userCollectionRef = collection(db, 'squid-store-users'); 20 | 21 | const checkIfUserExist = async (email) => { 22 | const data = await getDocs(userCollectionRef); 23 | const userExist = data.docs.find((doc) => doc.id === email); 24 | return userExist ? true : false; 25 | }; 26 | 27 | const createUser = async (user) => { 28 | const userDoc = doc(userCollectionRef, user.email); 29 | await setDoc(userDoc, { 30 | shopping_cart: [], 31 | shopping_history: [], 32 | wishlist: [], 33 | user_data: { 34 | email: user.email, 35 | name: user.displayName || '', 36 | img_url: user.photoURL || '', 37 | }, 38 | }); 39 | }; 40 | 41 | const uploadWishList = async (user, wishlist) => { 42 | try { 43 | const userDoc = doc(db, 'squid-store-users', user); 44 | const newField = { wishlist: wishlist }; 45 | await updateDoc(userDoc, newField); 46 | } catch (error) { 47 | console.log(error); 48 | } 49 | }; 50 | 51 | const getUserData = async (user) => { 52 | const userDoc = doc(userCollectionRef, user.email); 53 | const data = await getDoc(userDoc); 54 | return data.data(); 55 | }; 56 | 57 | const initializeUserSession = async (user) => { 58 | const userData = await getUserData(user); 59 | setUserData({ 60 | name: user.displayName, 61 | email: user.email, 62 | photo: user.photoURL, 63 | }); 64 | setShoppingCart(userData.shopping_cart); 65 | dispatch({ type: ACTIONS.SET_WISH_LIST, payload: userData.wishlist }); 66 | console.log('ejecutado el dispatch'); 67 | }; 68 | 69 | const settingUpData = async (user) => { 70 | console.log(user); 71 | if (await checkIfUserExist(user.email)) { 72 | initializeUserSession(user); 73 | } else { 74 | await createUser(user); 75 | initializeUserSession(user); 76 | } 77 | }; 78 | 79 | return { 80 | settingUpData, 81 | uploadWishList, 82 | }; 83 | }; 84 | 85 | export default useFirestore; 86 | -------------------------------------------------------------------------------- /src/pages/Login/Login.jsx: -------------------------------------------------------------------------------- 1 | import { Toaster } from 'react-hot-toast'; 2 | import { Button, Text } from '@nextui-org/react'; 3 | 4 | import { useToggle, useAuth } from '../../hooks'; 5 | import { SignInForm, SignUpForm, SignInButton } from '../../components'; 6 | import GoogleLogo from '../../assets/img/google.png'; 7 | import GitHubLogo from '../../assets/img/github-logo.png'; 8 | import './Login.css'; 9 | 10 | const Login = () => { 11 | const siRegistering = useToggle(); 12 | const { handleSignInWithPopup, handleSignUpWithGithub } = useAuth(); 13 | 14 | return ( 15 |
16 |
17 |
18 |
19 |
20 | gaurd 25 |

Welcome back

26 |
27 |

28 | Welcome back! Please enter your details. 29 |

30 | {siRegistering.state ? : } 31 | 35 | -- Or Sign In with -- 36 | 37 |
38 | 43 | 48 |
49 | 50 |

51 | Don't have an account? 52 | 61 |

62 |
63 |
64 |
65 |
66 | 67 |
68 | ); 69 | }; 70 | 71 | export default Login; 72 | -------------------------------------------------------------------------------- /src/components/DropdownMenu/DropdownMenu.jsx: -------------------------------------------------------------------------------- 1 | import { Dropdown, Text, Avatar } from '@nextui-org/react'; 2 | import { CartContext, UserContext, WishContext } from '../../contexts'; 3 | import { useContext, useState } from 'react'; 4 | import { ACTIONS } from '../../reducers/wishReducer'; 5 | import ModalMenu from '../ModalMenu/ModalMenu'; 6 | import { auth } from '../../firebase'; 7 | import { signOut } from 'firebase/auth'; 8 | 9 | const DropdownMenu = ({ email, photo }) => { 10 | const { userData, setUserData } = useContext(UserContext); 11 | const { setShoppingCart } = useContext(CartContext); 12 | const { dispatch } = useContext(WishContext); 13 | const [visible, setVisible] = useState(false); 14 | const handler = () => setVisible(true); 15 | 16 | const closeHandler = () => { 17 | setVisible(false); 18 | }; 19 | 20 | const onSignOut = () => { 21 | signOut(auth).then(() => { 22 | setUserData(null); 23 | setShoppingCart([]); 24 | dispatch({ type: ACTIONS.EMPTY_WISH }); 25 | }); 26 | }; 27 | 28 | return ( 29 | <> 30 | 31 | 32 | {userData.photo ? ( 33 | 42 | ) : ( 43 | 52 | )} 53 | 54 | 55 | 56 | 57 | Signed in as 58 | 59 | 60 | {email} 61 | 62 | 63 | 64 | My Wish List 65 | 66 | 67 | 68 | Log Out 69 | 70 | 71 | 72 | 73 | 74 | 75 | ); 76 | }; 77 | 78 | export default DropdownMenu; 79 | -------------------------------------------------------------------------------- /src/components/Product/Product.css: -------------------------------------------------------------------------------- 1 | .product__wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | height: 23rem; 6 | width: 18.5rem; 7 | border-radius: 0.5rem; 8 | margin: 0 0.5rem; 9 | color: white; 10 | font-weight: 600; 11 | border: 2px solid white; 12 | background: rgba(255, 255, 255, 0.1); 13 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); 14 | backdrop-filter: blur(18.5px); 15 | -webkit-backdrop-filter: blur(18.5px); 16 | border-radius: 10px; 17 | border: 1px solid rgba(255, 255, 255, 0.18); 18 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 19 | } 20 | 21 | .product__image { 22 | display: flex; 23 | flex-direction: column; 24 | height: 60%; 25 | width: 100%; 26 | background-size: cover; 27 | background-position: center; 28 | background-repeat: no-repeat; 29 | padding: 0.5rem; 30 | } 31 | 32 | .product__image_pict { 33 | height: 14rem; 34 | align-self: center; 35 | max-width: 100%; 36 | } 37 | 38 | .product__image_info { 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | width: fit-content; 43 | color: white; 44 | font-size: 0.9rem; 45 | padding: 0.2em 1.2em; 46 | border-radius: 10px; 47 | background: linear-gradient(145deg, #d13571, #f83f86); 48 | border: none; 49 | margin-left: 0.5rem; 50 | cursor: pointer; 51 | } 52 | 53 | .product__title { 54 | display: flex; 55 | justify-content: center; 56 | align-items: center; 57 | width: 90%; 58 | flex-wrap: wrap; 59 | height: 20%; 60 | border-bottom: 1px solid white; 61 | margin: 0; 62 | text-align: center; 63 | font-size: 1.1rem; 64 | } 65 | 66 | .product__bottom { 67 | display: flex; 68 | justify-content: space-around; 69 | align-items: center; 70 | margin: 0 0.5rem; 71 | height: 20%; 72 | width: max-content; 73 | } 74 | .prices { 75 | display: flex; 76 | justify-content: space-between; 77 | gap: 0.2rem; 78 | font-size: 1.5rem; 79 | } 80 | .prices__old { 81 | text-decoration: line-through; 82 | color: rgb(106, 102, 99); 83 | margin-right: 0.5rem; 84 | } 85 | 86 | .prices__button { 87 | background-color: transparent; 88 | border: none; 89 | color: #a63e6e; 90 | font-size: 1.5rem; 91 | font-weight: 600; 92 | padding: 1rem; 93 | } 94 | 95 | .prices__button:hover { 96 | cursor: pointer; 97 | color: white; 98 | } 99 | 100 | .hidden { 101 | display: none; 102 | } 103 | 104 | .span { 105 | padding: 0.5rem; 106 | } 107 | .product__added { 108 | border: 2px solid white; 109 | padding: 0.6rem; 110 | border-radius: 10px; 111 | margin-top: 0.2rem; 112 | } 113 | 114 | @media screen and (max-width: 768px) { 115 | .product__wrapper { 116 | height: 98%; 117 | width: 15rem; 118 | } 119 | .prices__old { 120 | display: none; 121 | } 122 | 123 | .product__added { 124 | width: max-content; 125 | } 126 | .product__image_pict { 127 | height: 10rem; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/hooks/useAuth.js: -------------------------------------------------------------------------------- 1 | import { useState, useContext } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { 4 | signInWithEmailAndPassword, 5 | signInWithPopup, 6 | createUserWithEmailAndPassword, 7 | } from 'firebase/auth'; 8 | import { auth, githubProvider, provider } from '../firebase'; 9 | import { CartContext } from '../contexts'; 10 | import useFirestore from './useFirestore'; 11 | 12 | const useAuth = () => { 13 | const navigator = useNavigate(); 14 | 15 | const { notify } = useContext(CartContext); 16 | const { settingUpData } = useFirestore(); 17 | 18 | const [email, setEmail] = useState(''); 19 | const [password, setPassword] = useState(''); 20 | const [repPassword, setRepPassword] = useState(''); 21 | 22 | const resetSignInForm = () => { 23 | setPassword(''); 24 | setEmail(''); 25 | }; 26 | const resetSignUpForm = () => { 27 | resetSignInForm(); 28 | setRepPassword(''); 29 | }; 30 | 31 | const setUserInformation = (user) => { 32 | settingUpData(user.user); 33 | navigator('/'); 34 | }; 35 | 36 | const errorHandler = (error) => { 37 | console.error(error.code); 38 | console.error(error.message); 39 | notify(`${error.message}`, 'error'); 40 | }; 41 | 42 | const handleSignUpWithGithub = () => { 43 | signInWithPopup(auth, githubProvider) 44 | .then((result) => { 45 | setUserInformation(result); 46 | }) 47 | .catch((error) => { 48 | errorHandler(error); 49 | }); 50 | }; 51 | 52 | const handleSignUpWithEmailAndPassword = () => { 53 | if (password !== repPassword) { 54 | notify('Passwords do not match', 'error'); 55 | return; 56 | } else { 57 | createUserWithEmailAndPassword(auth, email, password) 58 | .then((result) => { 59 | setUserInformation(result); 60 | }) 61 | .catch((error) => { 62 | resetSignUpForm(); 63 | errorHandler(error); 64 | }); 65 | } 66 | }; 67 | 68 | const handleSignInWithPopup = () => { 69 | signInWithPopup(auth, provider) 70 | .then((result) => { 71 | setUserInformation(result); 72 | }) 73 | .catch((error) => { 74 | errorHandler(error); 75 | }); 76 | }; 77 | 78 | const handleSignInWithEmail = () => { 79 | signInWithEmailAndPassword(auth, email, password) 80 | .then((result) => { 81 | setUserInformation(result); 82 | }) 83 | .catch((error) => { 84 | errorHandler(error); 85 | resetSignInForm(); 86 | }); 87 | }; 88 | return { 89 | handleSignInWithEmail, 90 | handleSignInWithPopup, 91 | handleSignUpWithEmailAndPassword, 92 | resetSignInForm, 93 | handleSignUpWithGithub, 94 | email, 95 | setEmail, 96 | password, 97 | setPassword, 98 | repPassword, 99 | setRepPassword, 100 | resetSignUpForm, 101 | }; 102 | }; 103 | 104 | export default useAuth; 105 | -------------------------------------------------------------------------------- /src/components/BottomSection/BottomSection.css: -------------------------------------------------------------------------------- 1 | .bottom__section_container { 2 | display: flex; 3 | flex-direction: row; 4 | width: 90%; 5 | height: 60vh; 6 | color: white; 7 | background: linear-gradient(143.36deg, #c1a8e4 0%, #8552c9 75%); 8 | margin-top: 7rem; 9 | border-radius: 10px; 10 | } 11 | 12 | .bottom__section_left { 13 | display: flex; 14 | flex-direction: column; 15 | flex: 1; 16 | margin-left: 1.5rem; 17 | margin-top: 1.5rem; 18 | gap: 2rem; 19 | } 20 | 21 | .bottom__section_right { 22 | background-image: url('../../assets/img/bottom-background.jpg'); 23 | background-size: cover; 24 | background-position: center; 25 | background-repeat: no-repeat; 26 | flex: 1; 27 | margin: 1.5rem; 28 | } 29 | 30 | .bottom__section_image { 31 | height: 6rem; 32 | width: 18rem; 33 | } 34 | 35 | .bottom__section_title { 36 | font-size: 4.5rem; 37 | } 38 | 39 | .bottom__section_text { 40 | font-size: 1.5rem; 41 | } 42 | 43 | .bottom__section_wrapper { 44 | display: flex; 45 | } 46 | 47 | .bottom__section_input { 48 | padding: 1rem; 49 | font-size: 1rem; 50 | width: 18rem; 51 | border: 2px solid #ea3e7d; 52 | border-radius: 0.2rem; 53 | } 54 | 55 | .bottom__section_button { 56 | display: flex; 57 | align-items: center; 58 | justify-content: center; 59 | width: 5.5rem; 60 | color: white; 61 | padding: 0.7em 1.7em; 62 | font-size: 15px; 63 | border-radius: 5px; 64 | background: linear-gradient(145deg, #d13571, #f83f86); 65 | border: none; 66 | } 67 | 68 | .bottom__section_button:hover { 69 | cursor: pointer; 70 | } 71 | 72 | @media screen and (max-width: 768px) { 73 | .bottom__section_container { 74 | flex-direction: column; 75 | justify-content: center; 76 | height: 75vh; 77 | margin-top: 1rem; 78 | } 79 | .bottom__section_image { 80 | height: 3rem; 81 | width: 8rem; 82 | } 83 | .bottom__section_title { 84 | font-size: 2.5rem; 85 | font-weight: 600; 86 | margin: 1rem 0; 87 | } 88 | .bottom__section_text { 89 | font-size: 1.5rem; 90 | } 91 | .bottom__section_left { 92 | margin: 0; 93 | padding: 1.2rem; 94 | gap: 1.2rem; 95 | } 96 | .extra { 97 | font-size: 1rem; 98 | } 99 | } 100 | 101 | @media screen and (min-width: 768px) and (max-width: 1024px) { 102 | .bottom__section_container { 103 | flex-direction: column; 104 | justify-content: center; 105 | height: 60vh; 106 | } 107 | .bottom__section_image { 108 | height: 3rem; 109 | width: 8rem; 110 | } 111 | .bottom__section_title { 112 | font-size: 2.5rem; 113 | font-weight: 600; 114 | margin: 1rem 0; 115 | } 116 | .bottom__section_text { 117 | font-size: 1.5rem; 118 | } 119 | .bottom__section_left { 120 | margin: 0; 121 | padding: 1.2rem; 122 | gap: 1.2rem; 123 | flex: 0; 124 | } 125 | .extra { 126 | font-size: 1rem; 127 | } 128 | .bottom__section_right { 129 | background-position: center; 130 | background-size: cover; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/pages/Login/Login.css: -------------------------------------------------------------------------------- 1 | .login__container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | min-height: 100vh; 6 | background: linear-gradient(146.2deg, #ff0055 0%, #000066 100%), 7 | linear-gradient(316.52deg, #ff0055 0%, #000066 100%); 8 | font-family: 'Sharp'; 9 | } 10 | .login__container_section { 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | height: 90%; 15 | width: 90%; 16 | border-radius: 10px; 17 | } 18 | 19 | .left__section { 20 | border: 2px solid red; 21 | height: 90%; 22 | width: 90%; 23 | flex: 1; 24 | background: rgba(255, 255, 255, 0.7); 25 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); 26 | backdrop-filter: blur(20px); 27 | -webkit-backdrop-filter: blur(20px); 28 | border: 1px solid rgba(255, 255, 255, 0.18); 29 | border-radius: 10px; 30 | padding: 2rem; 31 | } 32 | 33 | .left__section_wrapper { 34 | display: flex; 35 | flex-direction: column; 36 | align-items: flex-start; 37 | gap: 1rem; 38 | height: 100%; 39 | } 40 | 41 | .section__form { 42 | display: flex; 43 | flex-direction: column; 44 | width: 100%; 45 | gap: 2rem; 46 | margin-top: 1rem; 47 | } 48 | .google_icon { 49 | height: 1.9rem; 50 | } 51 | 52 | .top__section { 53 | display: flex; 54 | align-items: flex-end; 55 | } 56 | .top__section_title { 57 | font-size: 2.8rem; 58 | } 59 | .top__section_description { 60 | font-size: 0.8rem; 61 | color: rgb(98, 97, 97); 62 | } 63 | .top__section_img { 64 | height: 4rem; 65 | } 66 | .signUp_text { 67 | display: flex; 68 | width: 100%; 69 | align-items: center; 70 | justify-content: center; 71 | } 72 | .forgotPassword { 73 | align-self: flex-end; 74 | margin: -1rem 0; 75 | font-size: small; 76 | } 77 | .right_section { 78 | display: none; 79 | height: 90%; 80 | width: 90%; 81 | flex: 1; 82 | background: rgba(255, 255, 255, 0.7); 83 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); 84 | backdrop-filter: blur(20px); 85 | -webkit-backdrop-filter: blur(20px); 86 | border: 1px solid rgba(255, 255, 255, 0.18); 87 | border-radius: 10px; 88 | padding: 2rem; 89 | background-image: url('../../assets/img/animated-logo.gif'); 90 | background-position: center; 91 | background-size: cover; 92 | } 93 | .buttons__container { 94 | display: flex; 95 | width: 100%; 96 | gap: 0.5rem; 97 | } 98 | 99 | @media screen and (min-width: 568px) { 100 | .login__container_section { 101 | max-width: 500px; 102 | } 103 | } 104 | 105 | @media screen and (min-width: 868px) { 106 | .login__container_section { 107 | max-width: 1200px; 108 | max-height: 640px; 109 | } 110 | .left__section { 111 | display: flex; 112 | border-radius: 10px 0 0 10px; 113 | height: 640px; 114 | justify-content: center; 115 | align-items: center; 116 | } 117 | .left__section_wrapper { 118 | justify-content: center; 119 | max-width: 80%; 120 | } 121 | .right_section { 122 | border-radius: 0 10px 10px 0; 123 | display: block; 124 | height: 640px; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/components/Product/Product.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useContext } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { CartContext } from '../../contexts/CartContext'; 4 | import { WishContext } from '../../contexts/WishContext'; 5 | import FavoriteIcon from '@mui/icons-material/Favorite'; 6 | import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; 7 | import { ACTIONS } from '../../reducers/wishReducer'; 8 | 9 | import './Product.css'; 10 | 11 | const Product = ({ id, title, image, price, discount }) => { 12 | const [added, setAdded] = useState(false); 13 | const [addedAsFavorite, setAddedAsFavorite] = useState(false); 14 | const { shoppingCart, setShoppingCart, notify } = useContext(CartContext); 15 | const { wishList, dispatch } = useContext(WishContext); 16 | 17 | const handleAddToCart = (e) => { 18 | setAdded(true); 19 | setShoppingCart([ 20 | ...shoppingCart, 21 | { id: id, name: title, img: image, price: discount, amount: 1 }, 22 | ]); 23 | notify(`${title} added to Shopping Cart`); 24 | }; 25 | 26 | const toggleWishList = () => { 27 | if (addedAsFavorite) { 28 | dispatch({ 29 | type: ACTIONS.REMOVE_WISH, 30 | payload: { id }, 31 | }); 32 | notify(`${title} removed from the Wish List`, 'error'); 33 | } else { 34 | dispatch({ 35 | type: ACTIONS.ADD_WISH, 36 | payload: { id, title, image, discount }, 37 | }); 38 | notify(`${title} added to Wish List`); 39 | } 40 | }; 41 | 42 | useEffect(() => { 43 | const check = wishList.some((item) => { 44 | if (item.id === id) { 45 | return true; 46 | } 47 | return false; 48 | }); 49 | check && setAddedAsFavorite(true); 50 | !check && setAddedAsFavorite(false); 51 | }, [wishList, id]); 52 | 53 | useEffect(() => { 54 | const check = shoppingCart.some((item) => { 55 | if (item.id === id) { 56 | return true; 57 | } 58 | return false; 59 | }); 60 | check && setAdded(true); 61 | !check && setAdded(false); 62 | }, [shoppingCart, id]); 63 | 64 | return ( 65 |
66 |
67 | 70 | 71 |
72 |

{title}

73 | {added ? ( 74 |
75 |

Added to cart

76 |
77 | ) : ( 78 |
79 |
80 | € {price} 81 | €{discount} 82 |
83 | | 84 | 90 |
91 | )} 92 |
93 | ); 94 | }; 95 | 96 | Product.propTypes = { 97 | title: PropTypes.string.isRequired, 98 | image: PropTypes.string.isRequired, 99 | price: PropTypes.number.isRequired, 100 | discount: PropTypes.number.isRequired, 101 | }; 102 | 103 | Product.defaultProps = { 104 | title: '', 105 | image: '', 106 | price: 0, 107 | discount: 0, 108 | }; 109 | 110 | export default Product; 111 | -------------------------------------------------------------------------------- /src/components/NavBar/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useContext } from 'react'; 2 | import Badge from '@mui/material/Badge'; 3 | import { styled } from '@mui/material/styles'; 4 | import IconButton from '@mui/material/IconButton'; 5 | import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; 6 | import { Button, Loading } from '@nextui-org/react'; 7 | import { useNavigate, Link } from 'react-router-dom'; 8 | 9 | import { CartContext } from '../../contexts/CartContext'; 10 | import { UserContext } from '../../contexts/UserContext'; 11 | import DropdownMenu from '../DropdownMenu/DropdownMenu'; 12 | import { ShoppingCartTable } from '../index'; 13 | import { getStripeCheckout } from '../../services/api'; 14 | 15 | import Menu from '@mui/material/Menu'; 16 | import './NavBar.css'; 17 | 18 | const StyledBadge = styled(Badge)(({ theme }) => ({ 19 | '& .MuiBadge-badge': { 20 | right: -2, 21 | top: 3, 22 | border: `2px solid ${theme.palette.background.paper}`, 23 | padding: '0 4px', 24 | }, 25 | })); 26 | 27 | const Navbar = () => { 28 | const [anchorEl, setAnchorEl] = useState(null); 29 | const [total, setTotal] = useState(0); 30 | const { shoppingCart, notify } = useContext(CartContext); 31 | const { userData } = useContext(UserContext); 32 | const navigator = useNavigate(); 33 | 34 | const [enableCheckout, setEnableCheckout] = useState(false); 35 | const [isLoading, setIsLoading] = useState(false); 36 | 37 | useEffect(() => { 38 | shoppingCart.length === 0 39 | ? setEnableCheckout(true) 40 | : setEnableCheckout(false); 41 | }, [shoppingCart]); 42 | 43 | const handleMenu = (event) => { 44 | setAnchorEl(event.currentTarget); 45 | }; 46 | 47 | const handleClose = () => { 48 | setAnchorEl(null); 49 | }; 50 | useEffect(() => { 51 | let total = 0; 52 | shoppingCart.map((item) => { 53 | return (total += item.amount * item.price); 54 | }); 55 | setTotal(total); 56 | }, [shoppingCart]); 57 | 58 | const handleCheckout = async () => { 59 | if (!userData) { 60 | notify('You must be authenticated to checkout.', 'error'); 61 | notify('You will be redirected to Login.', 'error'); 62 | setTimeout(() => { 63 | navigator('/login'); 64 | }, 4000); 65 | return; 66 | } 67 | 68 | setIsLoading(true); 69 | const stripeResponse = await getStripeCheckout(shoppingCart); 70 | if (stripeResponse.url) { 71 | setIsLoading(false); 72 | window.location = stripeResponse.url; 73 | } else { 74 | setIsLoading(false); 75 | notify(`Stripe Error: ${stripeResponse.error}.`, 'error'); 76 | notify(`Stripe Error: please try again later.`, 'error'); 77 | } 78 | }; 79 | 80 | const Total = () => { 81 | return ( 82 |
83 |

Total

84 |

€{total}

85 | 94 |
95 | ); 96 | }; 97 | 98 | return ( 99 |
100 | {userData ? ( 101 | 102 | ) : ( 103 | 106 | )} 107 | 108 | 109 | Contact 110 | 111 | 112 | About Us 113 | 114 | 115 | Blog 116 | 117 | 118 | 123 | 124 | 125 | Home 126 | 127 | 128 | Store 129 | 130 | 131 | Series 132 | 133 |
134 | 142 | 143 | 144 | 145 | 146 | 162 | shooping cart 167 | {shoppingCart.length > 0 ? ( 168 | 169 | ) : ( 170 |

The Shopping Cart is Empty

171 | )} 172 | 173 |
174 |
175 |
176 | ); 177 | }; 178 | 179 | export default Navbar; 180 | -------------------------------------------------------------------------------- /src/components/ShoppingCartTable/ShoppingCartTable.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { CartContext } from '../../contexts/CartContext'; 3 | import { WishContext } from '../../contexts/WishContext'; 4 | import { faPlus, faMinus, faTrash } from '@fortawesome/free-solid-svg-icons'; 5 | import { Table, Button, User } from '@nextui-org/react'; 6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 7 | import ShoppingCartCheckoutIcon from '@mui/icons-material/ShoppingCartCheckout'; 8 | import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; 9 | 10 | import './ShoppingCartTable.css'; 11 | import { ACTIONS } from '../../reducers/wishReducer'; 12 | 13 | const ShoppingCartTable = ({ wish }) => { 14 | const { shoppingCart, setShoppingCart, notify } = useContext(CartContext); 15 | const { wishList, dispatch } = useContext(WishContext); 16 | 17 | const handleAddAmount = (id) => { 18 | shoppingCart.forEach((item) => { 19 | if (item.id === id) { 20 | item.amount = item.amount + 1; 21 | } 22 | }); 23 | setShoppingCart([...shoppingCart]); 24 | }; 25 | const handleRestAmount = (id) => { 26 | shoppingCart.forEach((item) => { 27 | if (item.id === id && item.amount > 1) { 28 | item.amount = item.amount - 1; 29 | } 30 | }); 31 | setShoppingCart([...shoppingCart]); 32 | }; 33 | 34 | const handleRemove = (id, name) => { 35 | if (wish) { 36 | dispatch({ 37 | type: ACTIONS.REMOVE_WISH, 38 | payload: { id }, 39 | }); 40 | notify(`${name} removed from the Wish List`); 41 | } else { 42 | let newArray = shoppingCart.filter((item) => item.id !== id); 43 | setShoppingCart(newArray); 44 | notify(`${name} removed from the Shopping Cart`); 45 | } 46 | }; 47 | 48 | const handleAddToCart = (data) => { 49 | setShoppingCart([...shoppingCart, data]); 50 | notify(`${data.name} added to the Shopping Cart`); 51 | }; 52 | 53 | return ( 54 | 63 | {wish ? ( 64 | 65 | PRODUCT 66 | PRICE 67 | 68 | 69 | 70 | ) : ( 71 | 72 | PRODUCT 73 | PRICE 74 | AMOUNT 75 | 76 | 77 | 78 | 79 | )} 80 | 81 | {wish ? ( 82 | 83 | {wishList.map((item, index) => { 84 | return ( 85 | 86 | 87 | 93 | 94 | 95 | € {item?.price} 96 | 97 | 98 | 106 | 107 | 108 | 117 | 118 | 119 | ); 120 | })} 121 | 122 | ) : ( 123 | 124 | {shoppingCart.map((item, index) => { 125 | return ( 126 | 127 | 128 | 135 | 136 | 137 | € {item.price} 138 | 139 | 140 | {item.amount} 141 | 142 | 143 | 146 | 147 | 148 | 151 | 152 | 153 | 161 | 162 | 163 | ); 164 | })} 165 | 166 | )} 167 |
168 | ); 169 | }; 170 | 171 | export default ShoppingCartTable; 172 | --------------------------------------------------------------------------------