Skip to content
Open
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: 3 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Code of Conduct

Be respectful to others. This project follows a Contributor Covenant-style code of conduct. Harassment, hate speech, or other abusive behavior will not be tolerated.
9 changes: 9 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Contributing

Thanks for your interest in contributing to this project!

- Fork the repository and create a feature branch
- Make your changes and commit with clear messages
- Open a pull request describing your changes

Please keep changes focused and add tests where helpful.
43 changes: 42 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,42 @@
# coding-project-template
# e-plantShopping

This repository contains the e-plantShopping demo application (React + Vite) used for learning Redux and basic frontend patterns.

Contents:
- `src/` - React source files
- `public/` - static assets
- `LICENSE` - Apache 2.0 license

How to run locally

1. Install dependencies:

```powershell
npm install
```

2. Start dev server:

```powershell
npm run dev
```

3. Open the app in the browser (typically http://localhost:5173)

Simple interest script

There is a helper shell script `simple-interest.sh` which calculates simple interest.

Usage:

```powershell
# on Unix/macOS
./simple-interest.sh <principal> <rate> <time>

# Example
./simple-interest.sh 1000 5 2
```

License

This project is licensed under the Apache 2.0 License - see the `LICENSE` file for details.
17 changes: 17 additions & 0 deletions simple-interest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# simple-interest.sh - calculate simple interest
# Usage: ./simple-interest.sh <principal> <rate> <time>

if [ "$#" -ne 3 ]; then
echo "Usage: $0 <principal> <rate> <time>"
exit 1
fi

P=$1
R=$2
T=$3

# calculate simple interest: (P * R * T) / 100
SI=$(echo "scale=2; ($P * $R * $T) / 100" | bc)

echo "Simple Interest: $SI"
26 changes: 22 additions & 4 deletions src/CartItem.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
/* eslint-disable react/prop-types */
import { useSelector, useDispatch } from 'react-redux';
import { removeItem, updateQuantity } from './CartSlice';
import './CartItem.css';
Expand All @@ -9,27 +9,45 @@ const CartItem = ({ onContinueShopping }) => {

// Calculate total amount for all products in the cart
const calculateTotalAmount = () => {

// Sum over each item's subtotal (quantity * numeric cost)
return cart.reduce((sum, item) => {
const price = parseFloat(String(item.cost).replace(/[^0-9.]/g, '')) || 0;
const qty = item.quantity || 0;
return sum + price * qty;
}, 0).toFixed(2);
};

const handleContinueShopping = (e) => {

e.preventDefault();
if (onContinueShopping) onContinueShopping(e);
};



const handleIncrement = (item) => {
const newQty = (item.quantity || 0) + 1;
dispatch(updateQuantity({ name: item.name, quantity: newQty }));
};

const handleDecrement = (item) => {

const currentQty = item.quantity || 0;
if (currentQty > 1) {
dispatch(updateQuantity({ name: item.name, quantity: currentQty - 1 }));
} else {
// If quantity would drop to 0, remove the item from cart
dispatch(removeItem(item.name));
}
};

const handleRemove = (item) => {
dispatch(removeItem(item.name));
};

// Calculate total cost based on quantity for an item
const calculateTotalCost = (item) => {
const price = parseFloat(String(item.cost).replace(/[^0-9.]/g, '')) || 0;
const qty = item.quantity || 0;
return (price * qty).toFixed(2);
};

return (
Expand Down
40 changes: 40 additions & 0 deletions src/CartSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createSlice } from '@reduxjs/toolkit';

export const CartSlice = createSlice({
name: 'cart',
initialState: {
items: [], // Initialize items as an empty array
},
reducers: {
addItem: (state, action) => {
// action.payload should be a product object
const product = action.payload;
// If the product already exists in cart (by name), increase quantity, else push new
const existingIndex = state.items.findIndex(item => item.name === product.name);
if (existingIndex !== -1) {
state.items[existingIndex].quantity = (state.items[existingIndex].quantity || 1) + 1;
} else {
state.items.push({ ...product, quantity: 1 });
}
},
removeItem: (state, action) => {
const name = action.payload;
state.items = state.items.filter(item => item.name !== name);
},
updateQuantity: (state, action) => {
const { name, quantity } = action.payload;
const idx = state.items.findIndex(item => item.name === name);
if (idx !== -1) {
state.items[idx].quantity = quantity;
if (quantity <= 0) {
state.items.splice(idx, 1);
}
}

},
},
});

export const { addItem, removeItem, updateQuantity } = CartSlice.actions;

export default CartSlice.reducer;
26 changes: 3 additions & 23 deletions src/CartSlice.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,3 @@
import { createSlice } from '@reduxjs/toolkit';

export const CartSlice = createSlice({
name: 'cart',
initialState: {
items: [], // Initialize items as an empty array
},
reducers: {
addItem: (state, action) => {

},
removeItem: (state, action) => {
},
updateQuantity: (state, action) => {


},
},
});

export const { addItem, removeItem, updateQuantity } = CartSlice.actions;

export default CartSlice.reducer;
// Re-export to keep compatibility with existing imports
export { default } from './CartSlice.js';
export { addItem, removeItem, updateQuantity } from './CartSlice.js';
68 changes: 61 additions & 7 deletions src/ProductList.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useState, useEffect } from 'react';
/* eslint-disable react/prop-types */
import { useState } from 'react';
import './ProductList.css'
import CartItem from './CartItem';
import { useDispatch, useSelector } from 'react-redux';
import { addItem } from './CartSlice';
function ProductList({ onHomeClick }) {
const [showCart, setShowCart] = useState(false);
const [showPlants, setShowPlants] = useState(false); // State to control the visibility of the About Us page

const plantsArray = [
{
Expand Down Expand Up @@ -233,6 +235,19 @@ function ProductList({ onHomeClick }) {
textDecoration: 'none',
}

const dispatch = useDispatch();
const cartItems = useSelector(state => state.cart.items);

// total number of items (sum of quantities)
const calculateTotalQuantity = () => {
return cartItems ? cartItems.reduce((total, item) => total + (item.quantity || 0), 0) : 0;
};

const itemsInCartByName = cartItems.reduce((acc, item) => {
acc[item.name] = true;
return acc;
}, {});

const handleHomeClick = (e) => {
e.preventDefault();
onHomeClick();
Expand All @@ -244,8 +259,8 @@ function ProductList({ onHomeClick }) {
};
const handlePlantsClick = (e) => {
e.preventDefault();
setShowPlants(true); // Set showAboutUs to true when "About Us" link is clicked
setShowCart(false); // Hide the cart when navigating to About Us
// Show plants view and ensure cart is hidden
setShowCart(false);
};

const handleContinueShopping = (e) => {
Expand All @@ -269,19 +284,58 @@ function ProductList({ onHomeClick }) {
</div>
<div style={styleObjUl}>
<div> <a href="#" onClick={(e) => handlePlantsClick(e)} style={styleA}>Plants</a></div>
<div> <a href="#" onClick={(e) => handleCartClick(e)} style={styleA}><h1 className='cart'><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" id="IconChangeColor" height="68" width="68"><rect width="156" height="156" fill="none"></rect><circle cx="80" cy="216" r="12"></circle><circle cx="184" cy="216" r="12"></circle><path d="M42.3,72H221.7l-26.4,92.4A15.9,15.9,0,0,1,179.9,176H84.1a15.9,15.9,0,0,1-15.4-11.6L32.5,37.8A8,8,0,0,0,24.8,32H8" fill="none" stroke="#faf9f9" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" id="mainIconPathAttribute"></path></svg></h1></a></div>
<div style={{ position: 'relative' }}>
<a href="#" onClick={(e) => handleCartClick(e)} style={styleA}>
<h1 className='cart'>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" id="IconChangeColor" height="68" width="68">
<rect width="156" height="156" fill="none"></rect>
<circle cx="80" cy="216" r="12"></circle>
<circle cx="184" cy="216" r="12"></circle>
<path d="M42.3,72H221.7l-26.4,92.4A15.9,15.9,0,0,1,179.9,176H84.1a15.9,15.9,0,0,1-15.4-11.6L32.5,37.8A8,8,0,0,0,24.8,32H8" fill="none" stroke="#faf9f9" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" id="mainIconPathAttribute"></path>
</svg>
</h1>
</a>
{calculateTotalQuantity() > 0 && (
<span style={{ position: 'absolute', right: 6, top: 6, background: 'red', color: 'white', borderRadius: '50%', padding: '4px 7px', fontSize: '12px' }}>{calculateTotalQuantity()}</span>
)}
</div>
</div>
</div>
{!showCart ? (
<div className="product-grid">


{plantsArray.map((categoryObj) => (
<div key={categoryObj.category} className="category-block">
<h2>{categoryObj.category}</h2>
<div className="category-plants">
{categoryObj.plants.map((plant) => (
<div className="product-card" key={plant.name}>
<h3 className="product-name">{plant.name}</h3>
<img src={plant.image} alt={plant.name} className="product-image" />
<p className="product-desc">{plant.description}</p>
<p className="product-cost">{plant.cost}</p>
<button
className="add-to-cart"
onClick={() => handleAddToCart(plant)}
disabled={!!itemsInCartByName[plant.name]}
>
{itemsInCartByName[plant.name] ? 'Added' : 'Add to Cart'}
</button>
</div>
))}
</div>
</div>
))}
</div>
) : (
<CartItem onContinueShopping={handleContinueShopping} />
)}
</div>
);

function handleAddToCart(plant) {
// Dispatch the selected plant to the cart slice
dispatch(addItem(plant));
}
}

export default ProductList;