Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shopit coding challenge #84

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
24 changes: 24 additions & 0 deletions shopit/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
3 changes: 3 additions & 0 deletions shopit/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Ignore artifacts:
build
coverage
1 change: 1 addition & 0 deletions shopit/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Binary file added shopit/.yarn/install-state.gz
Binary file not shown.
1 change: 1 addition & 0 deletions shopit/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
34 changes: 34 additions & 0 deletions shopit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Shopit Coding Challenge 🌙   ![medium](https://img.shields.io/badge/-Medium-yellow) ![time](https://img.shields.io/badge/%E2%8F%B0-30m-blue)

 
# Goals / Outcomes ✨
- Using state and global state
- Manipulation of global states and its side effects

 
# Pre-requisites ✅
- React Context
- [Dark Mode coding challenge](https://github.com/alexgurr/react-coding-challenges/tree/master/dark-mode)

 
# Requirements 📖
- Add functionality to add products to cart
- Add *states* to manage *cart items*
- Reset *cart state* on checkout

 
# Think about 💡
- Where should the state reside
- How to use and manage state

 
# What's Already Been Done 🏁
- Basic app UI (mobile responsive)
- Mock products are provided

 
# Screenshots 🌄
 
![Product_Page](https://github.com/user-attachments/assets/87a72f9d-9d7e-49f6-862b-abcd9d922a86)
 
![Cart_Page](https://github.com/user-attachments/assets/c12e490b-0b2a-4bed-99f2-e9c072828da0)
33 changes: 33 additions & 0 deletions shopit/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";

export default [
{ ignores: ["dist"] },
{
files: ["**/*.{js,jsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: "latest",
ecmaFeatures: { jsx: true },
sourceType: "module",
},
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
"no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
},
];
13 changes: 13 additions & 0 deletions shopit/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Shopit</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
32 changes: 32 additions & 0 deletions shopit/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "shopit",
"private": true,
"version": "0.0.0",
"type": "module",
"author": "pandacover",
"scripts": {
"start": "vite --open --port 3000",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.4.0"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react-swc": "^3.8.0",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"prettier": "3.5.3",
"sass": "^1.86.0",
"vite": "^6.2.0"
},
"packageManager": "[email protected]+sha512.955259c0370ab8c06d013faa7d5e7addb6914251029695675f54e04c917ea6b092c379ba8d3521556d90b3874e5037759467072f01d2295341ead43cc259f14b"
}
1 change: 1 addition & 0 deletions shopit/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions shopit/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Route, Routes } from "react-router-dom";

import Cart from "./pages/cart";
import Products from "./pages/products";

const App = () => {
return (
<div>
<header></header>
<main className="container">
<Routes>
<Route path="/" element={<Products />} />
<Route path="/cart" element={<Cart />} />
</Routes>
</main>
<footer></footer>
</div>
);
};

export default App;
1 change: 1 addition & 0 deletions shopit/src/assets/react.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions shopit/src/components/product/Product.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Quantity from "../quanity";
import styles from "./product.module.scss";

const Product = ({ name, description, price, image_url, rating }) => {
return (
<article className={styles.product}>
<figure>
<img
width="400px"
height="200px"
style={{ objectFit: "cover" }}
loading="lazy"
src={image_url}
/>
</figure>
<section>
<h3>{name}</h3>
<details title={description}>
{description}
<summary>Description</summary>
</details>
<div className={styles.metadata}>
<strong>${price}</strong>
<span>{rating} / 5</span>
</div>
</section>
<footer className="quantityContainer">
<Quantity
btnText="Add to cart"
btnTitle="Adding to cart will replace the item amount"
/>
</footer>
</article>
);
};

export default Product;
3 changes: 3 additions & 0 deletions shopit/src/components/product/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Product from "./Product";

export default Product;
11 changes: 11 additions & 0 deletions shopit/src/components/product/product.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.product {
details {
margin-bottom: 10px;
}

.metadata {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
}
10 changes: 10 additions & 0 deletions shopit/src/components/quanity/Quantity.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const Quantity = ({ quantity, handleChangeQuantity, btnText, btnTitle }) => {
return (
<>
<input type="number" value={quantity} defaultValue={0} min={0} max={5} />
<button className="addToCart" title={btnTitle}>{btnText}</button>
</>
);
};

export default Quantity;
3 changes: 3 additions & 0 deletions shopit/src/components/quanity/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Quantity from "./Quantity";

export default Quantity;
134 changes: 134 additions & 0 deletions shopit/src/constants/mock-products.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const MOCK_PRODUCTS = [
{
id: "wirelessmouse",
name: "Wireless Mouse",
description:
"A comfortable wireless mouse with ergonomic design and long-lasting battery life.",
price: 25.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=Wireless+Mouse",
rating: 4.5,
},
{
id: "bluetoothheadphones",
name: "Bluetooth Headphones",
description:
"High-quality Bluetooth headphones with noise-canceling feature and deep bass.",
price: 59.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=Bluetooth+Headphones",
rating: 4.7,
},
{
id: "smartphonestand",
name: "Smartphone Stand",
description:
"Adjustable smartphone stand suitable for various devices and viewing angles.",
price: 15.49,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=Smartphone+Stand",
rating: 4.2,
},
{
id: "laptopsleeve",
name: "Laptop Sleeve",
description:
"Protective laptop sleeve made of durable material, fits most 15-inch laptops.",
price: 19.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=Laptop+Sleeve",
rating: 4.3,
},
{
id: "usbflashdrive64gb",
name: "USB Flash Drive 64GB",
description:
"High-speed USB flash drive with 64GB capacity for secure data storage.",
price: 12.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=USB+Flash+Drive+64GB",
rating: 4.6,
},
{
id: "leddesklamp",
name: "LED Desk Lamp",
description:
"Energy-efficient LED desk lamp with adjustable brightness and color temperature.",
price: 22.5,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=LED+Desk+Lamp",
rating: 4.0,
},
{
id: "portablecharger10000mah",
name: "Portable Charger 10000mAh",
description:
"Compact portable charger with 10000mAh capacity, capable of charging multiple devices.",
price: 29.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=Portable+Charger+10000mAh",
rating: 4.8,
},
{
id: "wirelessearbuds",
name: "Wireless Earbuds",
description:
"Sweat-resistant wireless earbuds with high-fidelity sound and secure fit.",
price: 49.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=Wireless+Earbuds",
rating: 4.4,
},
{
id: "smartwatch",
name: "Smartwatch",
description:
"Feature-rich smartwatch with fitness tracking, heart rate monitor, and customizable watch faces.",
price: 89.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=Smartwatch",
rating: 4.6,
},
{
id: "bluetoothspeaker",
name: "Bluetooth Speaker",
description:
"Portable Bluetooth speaker with 360-degree sound and waterproof design.",
price: 35.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=Bluetooth+Speaker",
rating: 4.3,
},
{
id: "tabletcase",
name: "Tablet Case",
description:
"Slim and lightweight tablet case with multiple viewing angles and secure closure.",
price: 18.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=Tablet+Case",
rating: 4.1,
},
{
id: "externalharddrive1tb",
name: "External Hard Drive 1TB",
description:
"Reliable external hard drive with 1TB capacity for extensive data storage.",
price: 59.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=External+Hard+Drive+1TB",
rating: 4.7,
},
{
id: "smarthomeplug",
name: "Smart Home Plug",
description:
"Wi-Fi enabled smart plug that allows remote control of your home appliances via smartphone.",
price: 14.99,
image_url:
"https://dummyjson.com/image/400x200/008080/ffffff?text=Smart+Home+Plug",
rating: 4.2,
},
];

export default MOCK_PRODUCTS;
Loading