├── src
├── images
│ ├── food.jpg
│ ├── hike.jpg
│ └── lake.jpg
├── components
│ ├── Events
│ │ ├── Events.module.css
│ │ ├── Events.jsx
│ │ ├── EventItem.module.css
│ │ └── EventItem.jsx
│ ├── MainHeader
│ │ ├── MainHeader.module.css
│ │ └── MainHeader.jsx
│ └── Cart
│ │ ├── Cart.module.css
│ │ └── Cart.jsx
├── main.jsx
├── index.css
├── App.jsx
├── data
│ └── dummy-events.js
└── contexts
│ └── cart-context.jsx
├── vite.config.js
├── .gitignore
├── index.html
├── README.md
├── .eslintrc.cjs
├── package.json
└── public
└── vite.svg
/src/images/food.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohnMwendwa/context-excercise-1/HEAD/src/images/food.jpg
--------------------------------------------------------------------------------
/src/images/hike.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohnMwendwa/context-excercise-1/HEAD/src/images/hike.jpg
--------------------------------------------------------------------------------
/src/images/lake.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JohnMwendwa/context-excercise-1/HEAD/src/images/lake.jpg
--------------------------------------------------------------------------------
/src/components/Events/Events.module.css:
--------------------------------------------------------------------------------
1 | .events {
2 | list-style: none;
3 | margin: 3rem auto;
4 | width: 40rem;
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 | import "./index.css";
5 |
6 | ReactDOM.createRoot(document.getElementById("root")).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
9 | sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | color: #2f2d2b;
13 | background-color: #eeeded;
14 | }
15 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { CartContextProvider } from "./contexts/cart-context";
2 | import Events from "./components/Events/Events";
3 | import MainHeader from "./components/MainHeader/MainHeader";
4 |
5 | function App() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/src/components/Events/Events.jsx:
--------------------------------------------------------------------------------
1 | import dummyEvents from "../../data/dummy-events";
2 | import EventItem from "./EventItem";
3 | import classes from "./Events.module.css";
4 |
5 | function Events() {
6 | return (
7 |
8 | {dummyEvents.map((event) => (
9 |
10 | ))}
11 |
12 | );
13 | }
14 |
15 | export default Events;
16 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/src/components/MainHeader/MainHeader.module.css:
--------------------------------------------------------------------------------
1 | .header {
2 | width: 100%;
3 | height: 7rem;
4 | display: flex;
5 | justify-content: space-around;
6 | align-items: center;
7 | padding: 0 10%;
8 | border-bottom: 2px solid #e0e0e0;
9 | }
10 |
11 | .header button {
12 | cursor: pointer;
13 | font: inherit;
14 | background: #fdd4ab;
15 | border: none;
16 | color: #704815;
17 | border-radius: 4px;
18 | font-weight: bold;
19 | padding: 0.5rem 1.5rem;
20 | }
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | "eslint:recommended",
6 | "plugin:react/recommended",
7 | "plugin:react/jsx-runtime",
8 | "plugin:react-hooks/recommended",
9 | ],
10 | ignorePatterns: ["dist", ".eslintrc.cjs"],
11 | parserOptions: { ecmaVersion: "latest", sourceType: "module" },
12 | settings: { react: { version: "18.2" } },
13 | plugins: ["react-refresh"],
14 | rules: {
15 | "react-refresh/only-export-components": [
16 | "warn",
17 | { allowConstantExport: true },
18 | ],
19 | "react/prop-types": -1,
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/src/data/dummy-events.js:
--------------------------------------------------------------------------------
1 | import lakeImage from '../images/lake.jpg';
2 | import hikeImage from '../images/hike.jpg';
3 | import foodImage from '../images/food.jpg';
4 |
5 | const dummyEvents = [
6 | {
7 | id: 'e1',
8 | title: 'Lake Festival',
9 | description: 'Join us for four days of fun and relaxed people.',
10 | image: lakeImage,
11 | price: 99
12 | },
13 | {
14 | id: 'e2',
15 | title: 'Guided Hiking',
16 | description: 'Explore nature on one of the most beautiful hiking tracks.',
17 | image: hikeImage,
18 | price: 119
19 | },
20 | {
21 | id: 'e3',
22 | title: 'Food & Nature',
23 | description: 'Enjoy the best of alpine cuisine.',
24 | image: foodImage,
25 | price: 219
26 | },
27 | ];
28 |
29 | export default dummyEvents;
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-context-excercise-1",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite --port 3000",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^18.2.15",
18 | "@types/react-dom": "^18.2.7",
19 | "@vitejs/plugin-react": "^4.0.3",
20 | "eslint": "^8.45.0",
21 | "eslint-plugin-react": "^7.32.2",
22 | "eslint-plugin-react-hooks": "^4.6.0",
23 | "eslint-plugin-react-refresh": "^0.4.3",
24 | "vite": "^4.4.5"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Events/EventItem.module.css:
--------------------------------------------------------------------------------
1 | .event {
2 | margin: 2rem 0;
3 | display: flex;
4 | align-items: flex-start;
5 | gap: 2rem;
6 | }
7 |
8 | .event img {
9 | object-fit: cover;
10 | border-radius: 14px;
11 | width: 7.5rem;
12 | height: 7.5rem;
13 | }
14 |
15 | .content {
16 | flex: 1;
17 | }
18 |
19 | .content h2 {
20 | margin: 0;
21 | }
22 |
23 | .price {
24 | font-weight: bold;
25 | background: #63615e;
26 | color: white;
27 | display: inline-block;
28 | padding: 0.25rem 0.5rem;
29 | border-radius: 4px;
30 | margin: 0.5rem 0;
31 | }
32 |
33 | .actions {
34 | display: flex;
35 | justify-content: flex-end;
36 | }
37 |
38 | .actions button {
39 | font: inherit;
40 | cursor: pointer;
41 | background-color: #fdd4ab;
42 | color: #2f2d2b;
43 | border: none;
44 | padding: 0.5rem 1.5rem;
45 | border-radius: 4px;
46 | }
47 |
48 | .actions button:hover {
49 | background-color: #fbbf82;
50 | }
--------------------------------------------------------------------------------
/src/components/MainHeader/MainHeader.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | import Cart from "../Cart/Cart";
4 | import classes from "./MainHeader.module.css";
5 | import { useCartContext } from "../../contexts/cart-context";
6 |
7 | function MainHeader() {
8 | const { cartItems } = useCartContext();
9 | const [modalIsOpen, setModalIsOpen] = useState();
10 |
11 | function openCartModalHandler() {
12 | setModalIsOpen(true);
13 | }
14 |
15 | function closeCartModalHandler() {
16 | setModalIsOpen(false);
17 | }
18 |
19 | const numCartItems = cartItems.length;
20 |
21 | return (
22 | <>
23 |
24 | StateEvents Shop
25 |
26 |
27 | {modalIsOpen && }
28 | >
29 | );
30 | }
31 |
32 | export default MainHeader;
33 |
--------------------------------------------------------------------------------
/src/contexts/cart-context.jsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useState } from "react";
2 |
3 | //create cart context
4 | const CartContext = createContext({
5 | cartItems: [],
6 | addItemHandler: () => {},
7 | removeItemHandler: () => {},
8 | });
9 |
10 | // function to consume the cart context object
11 | export const useCartContext = () => {
12 | return useContext(CartContext);
13 | };
14 |
15 | // cart context provider
16 | export const CartContextProvider = ({ children }) => {
17 | const [cartItems, setCartItems] = useState([]);
18 |
19 | const addItemHandler = (item) => {
20 | setCartItems((prev) => [...prev, item]);
21 | };
22 |
23 | const removeItemHandler = (itemId) => {
24 | setCartItems((prev) => prev.filter((item) => item.id !== itemId));
25 | };
26 |
27 | const cartItemsCtx = {
28 | cartItems,
29 | addItemHandler,
30 | removeItemHandler,
31 | };
32 | return (
33 | {children}
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/Cart/Cart.module.css:
--------------------------------------------------------------------------------
1 | .backdrop {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100vh;
7 | background-color: rgba(0, 0, 0, 0.7);
8 | }
9 |
10 | .cart {
11 | position: fixed;
12 | top: 20vh;
13 | left: calc(50% - 20rem);
14 | width: 40rem;
15 | padding: 1.5rem;
16 | background-color: #fdf1e6;
17 | border-radius: 8px;
18 | }
19 |
20 | .cart h2 {
21 | margin: 0;
22 | }
23 |
24 | .total {
25 | font-weight: bold;
26 | font-size: 1.25rem;
27 | text-align: right;
28 | }
29 |
30 | .actions {
31 | display: flex;
32 | justify-content: flex-end;
33 | gap: 1rem;
34 | }
35 |
36 | .actions button {
37 | font: inherit;
38 | cursor: pointer;
39 | background-color: #fdd4ab;
40 | color: #2f2d2b;
41 | border: none;
42 | padding: 0.5rem 1.5rem;
43 | border-radius: 4px;
44 | }
45 |
46 | .actions button:hover {
47 | font: inherit;
48 | cursor: pointer;
49 | background-color: #f8c897;
50 | color: #2f2d2b;
51 | border: none;
52 | padding: 0.5rem 1.5rem;
53 | border-radius: 4px;
54 | }
--------------------------------------------------------------------------------
/src/components/Cart/Cart.jsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from "react-dom";
2 |
3 | import classes from "./Cart.module.css";
4 | import { useCartContext } from "../../contexts/cart-context";
5 |
6 | function Cart({ onClose }) {
7 | const { cartItems: items } = useCartContext();
8 | const total = items.reduce((prevVal, item) => prevVal + item.price, 0);
9 |
10 | return ReactDOM.createPortal(
11 | <>
12 |
13 |
28 | >,
29 | document.getElementById("modal")
30 | );
31 | }
32 |
33 | export default Cart;
34 |
--------------------------------------------------------------------------------
/src/components/Events/EventItem.jsx:
--------------------------------------------------------------------------------
1 | import { useCartContext } from "../../contexts/cart-context";
2 | import classes from "./EventItem.module.css";
3 |
4 | function EventItem({ event }) {
5 | const { cartItems, addItemHandler, removeItemHandler } = useCartContext();
6 |
7 | const isInCart = cartItems.some((item) => item.id === event.id);
8 |
9 | let buttonCaption = "Add to Cart";
10 | let buttonAction = () => addItemHandler(event);
11 |
12 | if (isInCart) {
13 | buttonCaption = "Remove from Cart";
14 | buttonAction = () => removeItemHandler(event.id);
15 | }
16 |
17 | return (
18 |
19 |
20 |
21 |
{event.title}
22 |
${event.price}
23 |
{event.description}
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | export default EventItem;
33 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------