Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.1",
"typescript": "^5.0.2",
"vite": "^4.4.0"
"vite": "^4.4.0",
"vitest": "^0.33.0"
}
}
20 changes: 17 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
import './App.css'
import React from 'react';

import Box from '@mui/material/Box';
import CssBaseline from '@mui/material/CssBaseline';

import Header from "./components/Layout/Header/Header";
import AvailableProducts from './components/Products/AvailableProducts';
import Cart from './components/Cart/Cart';
import CartProvider from './store/CartProvider';

function App() {

const [showCart, setShowCart] = React.useState(false);

const handleShowCart = () => {
setShowCart(true);
};

const handleHideCart = () => {
setShowCart(false);
};

return (
<>
<CartProvider>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<CssBaseline />
<Header/>
<Header onShowCart={handleShowCart} />
<Cart showCart={showCart} onClose={handleHideCart}/>
<AvailableProducts/>
</Box>
</>
</CartProvider>
)
}

Expand Down
53 changes: 53 additions & 0 deletions src/components/Cart/Cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useContext } from 'react';

import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';

import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';

import CartContext from '../../store/cart-context';

const Cart = props => {
const cartCtx = useContext(CartContext);
const totalAmount = `$${cartCtx.totalAmount.toFixed(2)}`;
const hasItems = cartCtx.items.length > 0;
const cartItems = (
<List>
{cartCtx.items.map(item => (
<ListItem disablePadding>
<ListItemText primary={item.name} />
</ListItem>
))}
{/* <ListItem disablePadding>
<ListItemText primary="Hello" />
</ListItem>
<ListItem disablePadding>
<ListItemText primary="World" />
</ListItem> */}
Comment on lines +27 to +32
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, on the other hand, works as expected, so I'm not sure what's wrong with cartCtx.items.map

</List>
);
return (
<Dialog open={props.showCart} onClose={props.onClose}>
<DialogTitle>Shopping cart</DialogTitle>
<DialogContent>
{cartItems}
{/* {cartCtx.items[0].name} */}
Comment on lines +39 to +40
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I comment the first line and uncomment the second one, {cartCtx.items[0].name} works fine.

<DialogContentText>
Total: {totalAmount}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button color="error" onClick={props.onClose}>Cancel</Button>
{hasItems && <Button onClick={props.onClose}>Checkout</Button>}
</DialogActions>
</Dialog>
);
}

export default Cart;
10 changes: 3 additions & 7 deletions src/components/Layout/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import Badge from '@mui/material/Badge';
import MailIcon from '@mui/icons-material/Mail';
import NotificationsIcon from '@mui/icons-material/Notifications';

import HeaderSearch from './HeaderSearch';
import HeaderCartButton from './HeaderCartButton';

const Header = () => {
const Header = props => {
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
Expand All @@ -25,11 +25,7 @@ const Header = () => {
<HeaderSearch/>
<Box sx={{ flexGrow: 1 }} />
<Box sx={{ display: { xs: 'none', md: 'flex' } }}>
<IconButton size="large" aria-label="show 4 new mails" color="inherit">
<Badge badgeContent={4} color="error">
<MailIcon />
</Badge>
</IconButton>
<HeaderCartButton onClick={props.onShowCart} />
<IconButton
size="large"
aria-label="show 17 new notifications"
Expand Down
19 changes: 19 additions & 0 deletions src/components/Layout/Header/HeaderCartButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Badge from '@mui/material/Badge';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import IconButton from '@mui/material/IconButton';
import { useContext } from 'react';
import CartContext from '../../../store/cart-context';

const HeaderCartButton = props => {
const cartCtx = useContext(CartContext);
const itemsQuantity = cartCtx.items.length;
return (
<IconButton size="large" aria-label="show 4 new mails" color="inherit" onClick={props.onClick}>
<Badge badgeContent={itemsQuantity} color="error">
<ShoppingCartIcon />
</Badge>
</IconButton>
);
};

export default HeaderCartButton;
7 changes: 4 additions & 3 deletions src/components/Products/AvailableProducts.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Box from '@mui/material/Box';

import ProductItem from "./ProductItem/ProductItem";
import Content from '../UI/Content';
import ProductContainer from './ProductContainer';

const DUMMY_PRODUCTS = [
{
Expand All @@ -22,16 +22,17 @@ const AvailableProducts = () => {
const productList = DUMMY_PRODUCTS.map((product) => (
<ProductItem
key={product.id}
id={product.id}
name={product.name}
description={product.description}
price={product.price}
/>
));
return (
<Box component="main" sx={{ p: 3 }}>
<Content>
<ProductContainer>
{productList}
</Content>
</ProductContainer>
</Box>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Box from '@mui/material/Box';
import Grid from '@mui/material/Unstable_Grid2';
import { PropsWithChildren } from 'react';

const Content = (props) => {
const ProductContainer = (props: PropsWithChildren) => {

return (
<Box component="main" sx={{ p: 3 }}>
Expand All @@ -12,4 +13,4 @@ const Content = (props) => {
);
};

export default Content;
export default ProductContainer;
29 changes: 23 additions & 6 deletions src/components/Products/ProductItem/ProductItem.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart';
import ProductItemForm from './ProductItemForm';
import { useContext } from 'react';
import CartContext from '../../../store/cart-context';

const ProductItem = (props) => {
export interface ProductItemProps {
id: number;
name: string;
description: string;
price: number;
}


const ProductItem = (props: ProductItemProps) => {
const cartCtx = useContext(CartContext);
const price = `$${props.price.toFixed(2)}`

const addToCartHandler = amount => {
cartCtx.addItem({
id: props.id,
name: props.name,
amount,
price: props.price
})
};

return (
<Grid xs={3}>
<Card sx={{ minWidth: 275 }}>
Expand All @@ -25,9 +44,7 @@ const ProductItem = (props) => {
</Typography>
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to cart">
<AddShoppingCartIcon />
</IconButton>
<ProductItemForm onAddToCart={addToCartHandler}/>
</CardActions>
</Card>
</Grid>
Expand Down
29 changes: 29 additions & 0 deletions src/components/Products/ProductItem/ProductItemForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart';
import IconButton from '@mui/material/IconButton';

const ProductItemForm = props => {
const onSubmit = event => {
event.preventDefault();

props.onAddToCart(1)
}

return (
<form onSubmit={onSubmit}>
{/* <TextField ref={amountInputRef} type="number" variant="standard" label="Quantity" inputProps={{
defaultValue: 1,
step: 1,
min: 1,
max: 5,
}} InputLabelProps={{
shrink: true,
}}/> */}
<IconButton color="secondary" aria-label="add to cart" type='submit'>
<AddShoppingCartIcon />
</IconButton>
</form>

);
};

export default ProductItemForm;
42 changes: 42 additions & 0 deletions src/store/CartProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useReducer } from "react";
import CartContext from "./cart-context";

const defaultCartState = {
items: [],
totalAmount: 0
}

const cartReducer = (state, action) => {
if (action.type === "ADD") {
const updatedItems = state.items.concat(action.item);
const updatedTotal = state.totalAmount + action.item.price * action.item.amount;
return {
items: updatedItems,
totalAmount: updatedTotal
}
}
}

const CartProvider = props => {
const [cartState, dispatchCartAction] = useReducer(cartReducer, defaultCartState);

const addItemToCartHandler = item => {
dispatchCartAction({type: "ADD", item});
};
const removeItemFromCartHandler = id => {
dispatchCartAction({type: "REMOVE", id});
};

const cartContext = {
items: cartState.items,
totalAmount: cartState.totalAmount,
addItem: addItemToCartHandler,
removeItem: removeItemFromCartHandler
};

return <CartContext.Provider value={cartContext}>
{props.children}
</CartContext.Provider>
};

export default CartProvider;
10 changes: 10 additions & 0 deletions src/store/cart-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from "react"

const CartContext = React.createContext({
items: [],
totalAmount: 0,
addItem: item => {},
removeItem: id => {}
});

export default CartContext;
7 changes: 7 additions & 0 deletions vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { configDefaults, defineConfig } from 'vitest/config'

export default defineConfig({
test: {
exclude: [...configDefaults.exclude, 'packages/template/*'],
},
})
Loading