Repository: mtmehdi2/Ecommerce-Frontend
Pull request #5: Initial setup of capstone project.
Author: login: mtmehdi2
Created: 2025-03-21T13:42:21Z | Merged: 2025-03-27T13:49:26Z



Diff:
diff --git a/capstone/.prettierignore b/capstone/.prettierignore
new file mode 100644
index 0000000..0b84df0
--- /dev/null
+++ b/capstone/.prettierignore
@@ -0,0 +1 @@
+*.html
\ No newline at end of file
diff --git a/capstone/cart.html b/capstone/cart.html
new file mode 100644
index 0000000..98f985d
--- /dev/null
+++ b/capstone/cart.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Shopping Cart</title>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,initial-scale=1" />
+    <link rel="stylesheet" href="css/cart.css" />
+    <link rel="shortcut icon" href="images/favico.png">
+  </head>
+  <body>
+    <header>
+      <div><img src="images/logo.png" alt="logo"></div>
+      <div class="lnks"><a href="products.html">Products</a> | <a href="categories.html">Categories</a></div><a class="cart-icon" href="cart.html" data-number="0"></a>
+    </header>
+    <h1>Shopping Cart</h1>
+    <section id="product-container">
+        <div class="c-rw hdr">
+            <div class="c-product">Product</div>
+            <div class="c-variety">Variety</div>
+            <div class="c-quantity">Quantity</div>
+            <div class="c-price">Price</div>
+            <div class="c-total">Total</div>
+            <div class="c-action">Action</div>
+        </div>
+    </section>
+    <section id="checkout">
+    <div class="grand-total-ctnr"><div>Grand Total: </div> <div class='grand-total'>25.99</div></div>
+    <div class="checkout-ctnr"><a href="checkout.html">Proceed to Checkout</a></div>
+    </section>
+    <footer>
+      <div class="copyright">2025&copy; All Rights Reserved.</div>
+    </footer>
+    <template id="cart-template">
+        <div class="c-rw">
+            <div class="c-product" data-title="Product:">Product 1</div>
+            <div class="c-variety" data-title="Variety:">1 Pound</div>
+            <div class="c-quantity" data-title="Quantity:">3</div>
+            <div class="c-price" data-title="Price: "="">25.99</div>
+            <div class="c-total" data-title="Total: ">77.97</div>
+            <div class="c-action"><button class="btn-remove">Remove</div>
+        </div>
+    </template>
+    <script src="js/e.js"></script>
+    <script src="js/cart.js"></script>
+  </body>
+</html>
diff --git a/capstone/categories.html b/capstone/categories.html
new file mode 100644
index 0000000..f72e7fe
--- /dev/null
+++ b/capstone/categories.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Categories</title>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,initial-scale=1" />
+    <link
+      rel="stylesheet"
+      href="css/categories.css"/>
+    <link rel="shortcut icon" href="images/favico.png" />
+  </head>
+  <body>
+    <header>
+      <div><img src="images/logo.png" alt="logo"></div>
+      <div class="lnks"><a href="products.html">Products</a> | <a href="#">Categories</a></div><a class="cart-icon" href="cart.html" data-number="0"></a>
+    </header>
+    <h1>Categories</h1>
+ 
+    <section id="cat-container">
+      <div class="cat-hdr">Main Categories</div>
+      <div class="main-cat-ctnr">
+        <div class="main-cat-rw hdr">
+          <div class="main-cat-fc">ID</div>
+          <div class="main-cat-lc">Cat Name</div>
+        </div>
+      </div>
+      <div class="cat-hdr">Sub Categories</div>
+      <div class="child-cat-ctnr">
+        <div class="child-cat-rw hdr">
+          <div class="child-cat-fc">ID</div>
+          <div class="child-cat-sc">Cat Name</div>
+          <div class="child-cat-lc">Parent Cat</div>
+        </div>
+      </div>
+    </section>
+    <footer>
+      <div class="copyright">2025&copy; All Rights Reserved.</div>
+    </footer>
+    <script src="js/e.js"></script>
+    <script src="js/categories.js"></script>
+  </body>
+</html>
diff --git a/capstone/checkout.html b/capstone/checkout.html
new file mode 100644
index 0000000..0017afa
--- /dev/null
+++ b/capstone/checkout.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Checkout</title>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,initial-scale=1">
+    <link rel="stylesheet" href="css/checkout.css">
+    <link rel="shortcut-icon" href="images/favicon.jpg">
+  </head>
+  <body>
+    <header>
+    <div><img src="images/logo.png" alt="Logo"></div>
+    <div class="lnks"><a href="products.html">Products</a> | <a href="categories.html">Categories</a></div> <a class="cart-icon" href="cart.html" data-number="0"></a>
+    </header>
+    <h1>Checkout</h1>
+    <section id="product-container">
+        <form>
+            <div>
+                <div>Customer ID: <input type="text" id="customer_id" name="customer_id" value="" readonly></div>
+                <div>Order Total: <input type="text" id="total_amount" name="total_amount" value="10.99"></div>
+                <div>Payment Methods</div>
+                <div class="payment-methods">
+                    <div><input type="radio" class="rdo" name="payment_method" value="CREDIT_CARD" checked>Credit Card</div>
+                    <div><input type="radio" class="rdo" name="payment_method" value="DEBIT_CARD">Debit Card</div>
+                    <div><input type="radio" name="payment_method" value="PAYPAL">PayPal</div>
+                    <div><input type="radio" name="payment_method" value="CASH_ON_DELIVERY">Cash on Delivery</div>
+                </div>
+                <div>Delivery Address: <textarea rows="5" id="shipping_address" name="shipping_address"></textarea></div>
+                <div><button type="button" onclick="placeOrder()" id="submit-button">Submit</button></div>
+            </div>
+        </form>         
+    </section>
+    <div id="result"></div>
+    <footer>
+      <div class="copyright">2025&copy; All Rights Reserved.</div>
+    </footer>
+    <template id="result-template">
+        <h2 class="a-title">Order Successful!</h2>
+        <div><span>Order ID: </span> <span class="a-order-id"></span></div>
+        <div><span>Customer ID: </span><span class="a-customer-id"></span></div>
+        <div><span>Order Date: </span><span class="a-order-date"></span></div>
+        <div><span>Total Amount: </span><span class="a-total-amount"></span></div>
+        <div><span>Order Status: </span><span class="a-order-status"></span></div>
+        <div><span>Payment Method: </span><span class="a-payment-method"></span></div>
+        <div><span>Shipping Address: </span><span class="a-shipping-address"></span></div>
+        <div><textarea class="a-code" rows="8"></textarea></div>
+    </template>
+    <script src="js/checkout.js"></script>
+  </body>
+</html>
diff --git a/capstone/css/cart.css b/capstone/css/cart.css
new file mode 100644
index 0000000..c7c814b
--- /dev/null
+++ b/capstone/css/cart.css
@@ -0,0 +1,150 @@
+*,
+::after,
+::before {
+  box-sizing: border-box;
+}
+body {
+  font-family: Arial, Helvetica, sans-serif;
+}
+header,
+footer {
+  background-color: #1d5997;
+  color: #ffffff;
+  padding: 10px;
+}
+footer {
+  text-align: center;
+}
+header {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+}
+header > div:first-child {
+  width: 20%;
+  text-align: center;
+}
+header > div:last-child {
+  width: 80%;
+}
+header img {
+  width: 50px;
+  height: auto;
+}
+h1 {
+  text-align: center;
+}
+a {
+  text-decoration: none;
+  color: inherit;
+}
+
+a:active {
+  outline: none;
+}
+
+.hdr {
+    background-color: #1d5997!important;
+    color: white!important;
+    font-weight: bold;
+}
+
+.c-rw:nth-child(odd){
+    background-color: #d3d3d3;
+}
+.c-rw:nth-child(odd) button {
+    border-color: white;
+}
+.c-rw {
+    display: flex;
+    justify-content: flex-start;
+}
+.c-product,.c-variety,.c-quantity,.c-price,.c-total,.c-action {
+    padding: 8px 12px;
+}
+.c-product {
+    width: 40%;
+}
+.c-variety {
+    width: 20%;
+}
+.c-quantity, .c-price, .c-total, .c-action {
+    width: 10%;
+}
+
+.cart-empty-msg {
+    padding: 50px;
+}
+.btn-remove {
+    padding: 5px 10px;
+    background-color: transparent;
+    border: 1px solid #cccccc;
+    border-radius: 5px;
+    cursor: pointer;
+}
+.grand-total-ctnr {
+    background-color: #1d5997;
+    color: white;
+    font-weight: bold;
+    display: flex;
+    justify-content: flex-end;
+    padding-right: 10%;
+    margin-bottom: 50px;
+    display: none;
+}
+.grand-total-ctnr>div:first-child {
+    padding: 8px 12px;
+}
+.grand-total {
+    width: 10%;
+    padding: 8px 12px;
+}
+
+
+.cart-icon {
+    position: absolute;
+    right: 100px;
+    padding: 10px;
+    display: inline-block;
+    width: 50px;
+    height: 50px;
+    background-image: url('../images/cart-icon2.png');
+    background-size: 80% 80%;
+    background-repeat: no-repeat;
+  }
+  .cart-icon img {
+    width: 30px;
+  }
+  .cart-icon::after {
+    content: attr(data-number);
+    padding: 0 10px;
+  }
+  
+  .checkout-ctnr {
+    text-align: center;
+    margin-bottom: 50px;
+  }
+  .checkout-ctnr a {
+    border: 1px solid #cccccc;
+    border-radius: 5px;
+    padding: 8px 12px;
+  }
+
+  @media screen and (max-width: 900px) {
+    .c-rw {
+      flex-direction: column;
+    }
+    .c-rw>div {
+      width: 100%;
+    }
+    .c-rw>div::before {
+      content: attr(data-title);
+      font-weight: bold;
+      width: 20%;
+      display: inline-block;
+      margin-right: 20px;
+    }
+    .hdr {
+      display: none;
+    }
+  }
\ No newline at end of file
diff --git a/capstone/css/categories.css b/capstone/css/categories.css
new file mode 100644
index 0000000..105568f
--- /dev/null
+++ b/capstone/css/categories.css
@@ -0,0 +1,108 @@
+*,
+::after,
+::before {
+  box-sizing: border-box;
+}
+body {
+  font-family: Arial, Helvetica, sans-serif;
+}
+header,
+footer {
+  background-color: #1d5997;
+  color: #ffffff;
+  padding: 10px;
+}
+footer {
+  text-align: center;
+}
+header {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+}
+header > div:first-child {
+  width: 20%;
+  text-align: center;
+}
+header > div:last-child {
+  width: 80%;
+}
+header img {
+  width: 50px;
+  height: auto;
+}
+a {
+  text-decoration: none;
+  color: inherit;
+}
+h1 {
+  text-align: center;
+}
+.copyright {
+  padding: 10px;
+}
+
+.cat-hdr {
+  text-align: center;
+  font-weight: bold;
+  margin: 20px auto;
+}
+
+.main-cat-ctnr,
+.child-cat-ctnr {
+  width: 400px;
+  margin: auto;
+}
+.child-cat-ctnr {
+  margin-bottom: 50px;
+}
+.main-cat-rw,
+.child-cat-rw {
+  display: flex;
+  justify-content: flex-start;
+}
+.main-cat-rw.hdr,
+.child-cat-rw.hdr {
+  font-weight: bold;
+  background-color: #1d5997;
+  color: white;
+}
+.main-cat-rw:nth-child(even),
+.child-cat-rw:nth-child(even) {
+  background-color: #d3d3d3;
+}
+.main-cat-fc,
+.child-cat-fc {
+  width: 20%;
+}
+.child-cat-sc,
+.child-cat-lc {
+  width: 40%;
+}
+.main-cat-fc,
+.main-cat-lc,
+.child-cat-fc,
+.child-cat-sc,
+.child-cat-lc {
+  padding: 8px 12px;
+}
+
+
+.cart-icon {
+  position: absolute;
+  right: 100px;
+  padding: 10px;
+  display: inline-block;
+  width: 50px;
+  height: 50px;
+  background-image: url('../images/cart-icon2.png');
+  background-size: 80% 80%;
+  background-repeat: no-repeat;
+}
+.cart-icon img {
+  width: 30px;
+}
+.cart-icon::after {
+  content: attr(data-number);
+  padding: 0 10px;
+}
diff --git a/capstone/css/checkout.css b/capstone/css/checkout.css
new file mode 100644
index 0000000..2d575f0
--- /dev/null
+++ b/capstone/css/checkout.css
@@ -0,0 +1,120 @@
+*,
+::after,
+::before {
+  box-sizing: border-box;
+}
+body {
+  font-family: Arial, Helvetica, sans-serif;
+}
+header,
+footer {
+  background-color: #1d5997;
+  color: #ffffff;
+  padding: 10px;
+}
+header {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+}
+header > div:first-child {
+  width: 20%;
+  text-align: center;
+}
+header > div:last-child {
+  width: 80%;
+}
+header img {
+  width: 50px;
+  height: auto;
+}
+footer {
+  text-align: center;
+}
+a {
+  text-decoration: none;
+  color: inherit;
+}
+
+h1 {
+  text-align: center;
+}
+.copyright {
+  padding: 10px;
+}
+
+#product-container form {
+  width: 400px;
+  margin: auto;
+  padding-bottom: 50px;
+}
+#product-container input[type=text],
+#product-container input[type=number],
+#product-container textarea {
+  width: 100%;
+  padding: 8px 10px;
+  border-radius: 3px;
+  max-width: 100%;
+  border: 1px solid #333333;
+  margin-bottom: 20px;
+}
+#product-container .payment-methods {
+  border: 1px solid #333333;
+  padding: 5px 10px;
+  margin-bottom: 20px;
+}
+#submit-button {
+  padding: 10px 20px;
+  border-radius: 5px;
+  border: 1px solid #333;
+}
+
+#result {
+  width: 400px;
+  margin: auto;
+}
+#result textarea {
+  width: 100%;
+  border-radius: 10px;
+  padding: 10px;
+}
+
+#result>div>span {
+  padding: 5px 10px;
+}
+#result>div>span:first-child {
+  font-weight: normal;
+}
+#result>div>span:last-child {
+  font-weight: bold;
+}
+.a-code {
+  width: 100%;
+  margin-bottom: 50px;
+  max-width: 100%;
+}
+
+.cart-icon {
+  position: absolute;
+  right: 100px;
+  padding: 10px;
+  display: inline-block;
+  width: 50px;
+  height: 50px;
+  background-image: url('../images/cart-icon2.png');
+  background-size: 80% 80%;
+  background-repeat: no-repeat;
+}
+.cart-icon img {
+  width: 30px;
+}
+.cart-icon::after {
+  content: attr(data-number);
+  padding: 0 10px;
+}
+
+@media screen and (max-width:900px) {
+  input[type=text],input[type=number],textarea {
+    width: 95%;
+  }
+}
\ No newline at end of file
diff --git a/capstone/css/product.css b/capstone/css/product.css
new file mode 100644
index 0000000..1b1958d
--- /dev/null
+++ b/capstone/css/product.css
@@ -0,0 +1,173 @@
+*,
+::after,
+::before {
+  box-sizing: border-box;
+}
+body {
+  font-family: Arial, Helvetica, sans-serif;
+}
+header,
+footer {
+  background-color: #1d5997;
+  color: #ffffff;
+  padding: 10px;
+}
+header {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+}
+footer {
+  text-align: center;
+}
+header > div:first-child {
+  width: 20%;
+  text-align: center;
+}
+header > div:last-child {
+  width: 80%;
+}
+header img {
+  width: 50px;
+  height: auto;
+}
+
+.breadcrumb {
+  display: flex;
+  justify-content: flex-start;
+}
+
+.breadcrumb > div {
+  padding: 5px;
+  position: relative;
+  margin-right: 20px;
+}
+
+.breadcrumb > div::after {
+  content: "";
+  position: absolute;
+  right: -20px;
+  top: 6px;
+  border: 8px solid;
+  border-color: transparent transparent transparent white; 
+}
+
+a {
+  text-decoration: none;
+  color: inherit;
+}
+.breadcrumb > div:nth-child(3)::after,
+.breadcrumb > div:last-child::after {
+  border: 7px solid transparent;
+}
+
+h1 {
+  text-align: center;
+}
+.copyright {
+  padding: 10px;
+}
+
+.p-container {
+  padding: 10px 50px 50px;
+  text-align: center;
+}
+.p-img {
+  width: 400px;
+  height: auto;
+  border-radius: 10px;
+}
+
+select {
+  padding: 10px;
+}
+#quantity {
+  padding: 5px 10px;
+  width: 100px;
+}
+.p-price {
+  font-weight: bold;
+  font-size: 1.5rem;
+  margin-top: 10px;
+}
+.p-price sup {
+  font-size: 1rem;
+}
+
+.submit-links {
+  padding: 10px;
+  margin-top: 20px;
+}
+
+.submit-links button {
+  font-size: 1rem;
+  background-color: transparent;
+}
+.submit-links a {
+  text-decoration: none;
+  color: inherit;
+}
+
+.submit-links a,
+.submit-links button {
+  border: 1px solid #cccccc;
+  border-radius: 5px;
+  padding: 5px 10px;
+  display: inline-block;
+  margin: auto 5px;
+  cursor: pointer;
+}
+.submit-links input[type="text"] {
+  padding: 5px 10px;
+  border: 1px solid #cccccc;
+  border-radius: 5px;
+  width: 100px;
+}
+.info {
+  background-color: green;
+  color: white;
+  text-align: center;
+  padding: 20px;
+  position: sticky;
+  top: 30%;
+  width: 60%;
+  left: 20%;
+  border-radius: 10px;
+  transform: scale(0);
+  display: none;
+}
+.animate {
+  animation: animate-info 1s ease;
+  display: block;
+}
+@keyframes animate-info {
+  0% {transform: scale(0);}
+  40% {transform: scale(1);}
+  100% {transform: scale(1)}
+}
+
+
+.cart-icon {
+  position: absolute;
+  right: 100px;
+  padding: 10px;
+  display: inline-block;
+  width: 50px;
+  height: 50px;
+  background-image: url('../images/cart-icon2.png');
+  background-size: 80% 80%;
+  background-repeat: no-repeat;
+}
+.cart-icon img {
+  width: 30px;
+}
+.cart-icon::after {
+  content: attr(data-number);
+  padding: 0 10px;
+}
+
+@media screen and (max-width: 900px) {
+  .p-img {
+    width: 80%;
+  } 
+}
\ No newline at end of file
diff --git a/capstone/css/products.css b/capstone/css/products.css
new file mode 100644
index 0000000..c5c4119
--- /dev/null
+++ b/capstone/css/products.css
@@ -0,0 +1,141 @@
+*,
+::after,
+::before {
+  box-sizing: border-box;
+}
+body {
+  font-family: Arial, Helvetica, sans-serif;
+}
+header,
+footer {
+  background-color: #1d5997;
+  color: #ffffff;
+  padding: 10px;
+}
+header {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  position: relative;
+}
+header > div:first-child {
+  width: 20%;
+  text-align: center;
+}
+header > div:last-child {
+  width: 80%;
+}
+header img {
+  width: 50px;
+  height: auto;
+}
+h1 {
+  text-align: center;
+}
+a {
+  text-decoration: none;
+  color: inherit;
+}
+
+a:active {
+  outline: none;
+}
+
+#product-container {
+  padding: 10px 100px;
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+.prod-lnk {
+  width: 23%;
+  aspect-ratio: 1/1.2;
+  display: block;
+  border: 1px solid #cccccc;
+  border-radius: 5px;
+  margin-bottom: 50px;
+  box-shadow: 3px 3px 3px #cccccc;
+  transition: all 500ms;
+}
+.prod-lnk:hover {
+  box-shadow: 10px 10px 10px #cccccc, -5px -5px 5px #cccccc;
+  transform: scale(1.03);
+  transition: all 500ms;
+}
+
+.prod-container {
+  padding: 5px 10px;
+  text-align: center;
+}
+
+.prod-img {
+  width: 200px;
+  max-width: 98%;
+  height: 200px;
+  border-radius: 10px;
+}
+
+.prod-category,
+.prod-quantity {
+  font-size: 1.2rem;
+  padding: 10px;
+  margin: none;
+}
+.prod-price {
+  padding: 10%;
+  font-size: 2rem;
+  font-weight: bold;
+}
+.prod-price sup {
+  font-size: 1rem;
+}
+
+.prod-quantity {
+  margin-bottom: 20px;
+}
+footer {text-align: center;}
+.copyright {
+  padding: 10px;
+}
+
+#error-container {
+  padding: 50px;
+}
+
+.cart-icon {
+  position: absolute;
+  right: 100px;
+  padding: 10px;
+  display: inline-block;
+  width: 50px;
+  height: 50px;
+  background-image: url('../images/cart-icon2.png');
+  background-size: 80% 80%;
+  background-repeat: no-repeat;
+}
+.cart-icon img {
+  width: 30px;
+}
+.cart-icon::after {
+  content: attr(data-number);
+  padding: 0 10px;
+}
+
+@media screen and (max-width: 900px) and (min-width: 601px){
+ #product-container {
+    padding: 10px 50px;
+  }
+  .prod-lnk {
+    width: 48%;
+  }
+}
+
+@media screen and (max-width: 600px){
+  #product-container {
+     padding: 10px 20px;
+   }
+   .prod-lnk {
+     width: 95%;
+   }
+ }
+ 
\ No newline at end of file
diff --git a/capstone/images/cart-icon.png b/capstone/images/cart-icon.png
new file mode 100644
index 0000000..7896c3e
Binary files /dev/null and b/capstone/images/cart-icon.png differ
diff --git a/capstone/images/cart-icon2.png b/capstone/images/cart-icon2.png
new file mode 100644
index 0000000..4ee858b
Binary files /dev/null and b/capstone/images/cart-icon2.png differ
diff --git a/capstone/images/favico.png b/capstone/images/favico.png
new file mode 100644
index 0000000..be35b1e
Binary files /dev/null and b/capstone/images/favico.png differ
diff --git a/capstone/images/logo.png b/capstone/images/logo.png
new file mode 100644
index 0000000..d38ed5d
Binary files /dev/null and b/capstone/images/logo.png differ
diff --git a/capstone/js/cart.js b/capstone/js/cart.js
new file mode 100644
index 0000000..807bad3
--- /dev/null
+++ b/capstone/js/cart.js
@@ -0,0 +1,81 @@
+const d=document;
+const showEmptyCart =()=>{
+    const cartEmpty = d.createDocumentFragment();
+        const div=d.createElement('div');
+        const h2= d.createElement('h2');
+        const p=d.createElement('p');
+        div.setAttribute('class','cart-empty-msg');
+        h2.textContent='Empty!';
+        p.textContent='The shopping cart is empty. Please add some product to the cart. Click on the Products link on the menu to browse the products.';
+        div.appendChild(h2);
+        div.appendChild(p);
+        cartEmpty.appendChild(div);
+        d.querySelector('#product-container').appendChild(cartEmpty);
+        d.querySelector('#checkout').style.display='none';
+        updateCartIconLabel();
+}
+
+
+const updateCartIconLabel =()=>{
+    const shoppingCart = localStorage.getItem("capstoneShoppingCart");
+    if(shoppingCart!=null && shoppingCart !=undefined){
+      const theCart = JSON.parse(shoppingCart);
+      document.querySelector('.cart-icon').setAttribute('data-number',theCart.length);
+    }
+  };
+
+const updateTotal = ()=>{
+    const shoppingCart=JSON.parse(localStorage.getItem('capstoneShoppingCart'));
+    let cartTotal=0;
+    shoppingCart.forEach(item=>{
+        cartTotal+=Number(item.total);
+    });
+    d.querySelector('.grand-total').textContent=cartTotal.toFixed(2);
+    d.querySelector('.grand-total-ctnr').style.display='flex';
+    updateCartIconLabel();
+}
+
+
+const removeItem=(pid,pv)=>{
+    if(confirm('Do you really want to remove this item from the cart?')){
+        const shoppingCart = JSON.parse(localStorage.getItem("capstoneShoppingCart"));
+        const newCart = [];
+        for(const x of shoppingCart){
+            if(x.product_id != pid || x.variety!=pv){
+                newCart.push(x);
+            }
+        }
+        localStorage.setItem("capstoneShoppingCart",JSON.stringify(newCart));
+        d.querySelector('#rw_'+pid).remove();
+        if(newCart.length===0){
+            showEmptyCart();
+        }else{
+            updateTotal();
+        }
+    }
+}
+
+ (()=>{
+    const currCart = localStorage.getItem("capstoneShoppingCart");
+    if(currCart===undefined || currCart===null){
+        showEmptyCart();
+    }else{
+        const theCart = JSON.parse(currCart);
+        if(theCart.length==0){
+            showEmptyCart();
+        }else{
+            theCart.forEach(item => {
+                const cln = d.querySelector('#cart-template').content.cloneNode(true);
+                cln.querySelector('.c-rw').setAttribute('id','rw_'+item.product_id);
+                cln.querySelector('.c-product').textContent=item.product_name;
+                cln.querySelector('.c-variety').textContent=item.variety;
+                cln.querySelector('.c-quantity').textContent=item.quantity;
+                cln.querySelector('.c-price').textContent=Number(item.price).toFixed(2);
+                cln.querySelector('.c-total').textContent=Number(item.total).toFixed(2);
+                cln.querySelector('.btn-remove').setAttribute('onclick','removeItem('+item.product_id+',"'+item.variety+'");');
+                d.querySelector('#product-container').appendChild(cln);
+            });
+            updateTotal();
+        }
+    }
+})();
diff --git a/capstone/js/categories.js b/capstone/js/categories.js
new file mode 100644
index 0000000..0fe4847
--- /dev/null
+++ b/capstone/js/categories.js
@@ -0,0 +1,64 @@
+const mainCat = _(".main-cat-ctnr")[0];
+const childCat = _(".child-cat-ctnr")[0];
+const apiUrl = "http://3.136.18.203:8000/categories/";
+
+fetch(apiUrl)
+  .then((response) => response.json())
+  .then((data) => {
+    let parent_cat = [];
+    let child_cat = [];
+    data.forEach((cat) => {
+      if (cat.parent_category === null) {
+        parent_cat.push(cat);
+      } else {
+        child_cat.push(cat);
+      }
+    });
+    parent_cat.forEach((ct) => {
+      const r = document.createElement("div");
+      const c1 = document.createElement("div");
+      const c2 = document.createElement("div");
+      r.setAttribute("class", "main-cat-rw");
+      c1.setAttribute("class", "main-cat-fc");
+      c2.setAttribute("class", "main-cat-lc");
+      c1.textContent = ct.category_id;
+      c2.textContent = ct.name;
+      r.appendChild(c1);
+      r.appendChild(c2);
+      mainCat.appendChild(r);
+    });
+    const frag = document.createDocumentFragment();
+    child_cat.forEach((ct) => {
+      const pcc = parent_cat.find(
+        (itm) => itm.category_id === ct.parent_category
+      );
+      const r = document.createElement("div");
+      const c1 = document.createElement("div");
+      const c2 = document.createElement("div");
+      const c3 = document.createElement("div");
+      r.setAttribute("class", "child-cat-rw");
+      c1.setAttribute("class", "child-cat-fc");
+      c2.setAttribute("class", "child-cat-sc");
+      c3.setAttribute("class", "child-cat-lc");
+      c1.textContent = ct.category_id;
+      c2.textContent = ct.name;
+      c3.textContent = pcc.name;
+      r.appendChild(c1);
+      r.appendChild(c2);
+      r.appendChild(c3);
+      frag.appendChild(r);
+      childCat.appendChild(frag);
+    });
+  })
+  .catch((err) => {
+    console.error(err.message);
+  });
+
+  (()=>{
+    const shoppingCart = localStorage.getItem("capstoneShoppingCart");
+    if(shoppingCart!=null && shoppingCart !=undefined){
+      const theCart = JSON.parse(shoppingCart);
+      console.log(theCart.length);
+      document.querySelector('.cart-icon').setAttribute('data-number',theCart.length);
+    }
+  })();
\ No newline at end of file
diff --git a/capstone/js/checkout.js b/capstone/js/checkout.js
new file mode 100644
index 0000000..63ff3d5
--- /dev/null
+++ b/capstone/js/checkout.js
@@ -0,0 +1,62 @@
+const placeOrder = ()=>{
+    const d=document;
+    const apiUrl = "http://3.136.18.203:8000/orders/";
+    const c_id=d.querySelector('#customer_id').value;
+    const t_amount = d.querySelector('#total_amount').value;
+    const p_method=d.querySelector('input[name=payment_method]:checked').value;
+    const sh_address=d.querySelector('#shipping_address').value;
+    const data = {
+        customer_id: c_id,
+        total_amount: t_amount,
+        payment_method: p_method,
+        shipping_address: sh_address
+    }
+    fetch(apiUrl, {
+        method: 'POST',
+        headers: {
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify(data)
+    })
+    .then(response => response.json())
+    .then(data =>{
+        const template=document.getElementById('result-template');
+        const cln = template.content.cloneNode(true);
+        cln.querySelector('.a-order-id').textContent=data.order_id;
+        cln.querySelector('.a-customer-id').textContent=data.customer_id;
+        cln.querySelector('.a-order-date').textContent=data.order_date;
+        cln.querySelector('.a-total-amount').textContent=data.total_amount;
+        cln.querySelector('.a-order-status').textContent=data.status;
+        cln.querySelector('.a-payment-method').textContent=data.payment_method;
+        cln.querySelector('.a-shipping-address').textContent=data.shipping_address;
+        cln.querySelector('.a-code').value = "Data Received: "+ JSON.stringify(data,null,4);
+        document.querySelector("#result").appendChild(cln);
+    })
+    .catch(error => console.error('Error:', error));
+
+    document.querySelector('#submit-button').disabled=true;
+    document.querySelector('.cart-icon').setAttribute('data-number',0);
+    localStorage.setItem('capstoneShoppingCart','[]');
+}
+
+(()=>{
+    const c_id = Math.floor(Math.random()* 90000)+10000;
+    const shoppingCart = localStorage.getItem('capstoneShoppingCart');
+    if(shoppingCart!=null && shoppingCart!=undefined){
+        const theCart = JSON.parse(shoppingCart);
+        let cartTotal = 0;
+        theCart.forEach(item => {
+            cartTotal+=Number(item.total);
+        });
+        document.querySelector('#total_amount').value=cartTotal.toFixed(2);
+    }
+    document.querySelector('#customer_id').value=c_id;
+})();
+
+(()=>{
+    const shoppingCart = localStorage.getItem("capstoneShoppingCart");
+    if(shoppingCart!=null && shoppingCart !=undefined){
+      const theCart = JSON.parse(shoppingCart);
+      document.querySelector('.cart-icon').setAttribute('data-number',theCart.length);
+    }
+  })();
\ No newline at end of file
diff --git a/capstone/js/e.js b/capstone/js/e.js
new file mode 100644
index 0000000..377d1df
--- /dev/null
+++ b/capstone/js/e.js
@@ -0,0 +1 @@
+function _(c){return c.substr(0,1)=="#"?document.getElementById(c.substr(1,c.length-1)):c.substring(0,1)=="."?document.getElementsByClassName(c.substr(1,c.length-1)):c.substring(0,1)=="$"?document.getElementsByName(c.substr(1,c.length-1)):document.getElementsByTagName(c);}
diff --git a/capstone/js/error.js b/capstone/js/error.js
new file mode 100644
index 0000000..45cedd4
--- /dev/null
+++ b/capstone/js/error.js
@@ -0,0 +1,30 @@
+let myErr = false;
+const showError = (data) => {
+  if (!myErr) {
+    const ctnr = _("#product-container");
+    const h1 = document.createElement("h1");
+    const p = document.createElement("p");
+    h1.textContent = "Oops!";
+    p.textContent = data;
+    ctnr.setAttribute("id", "error-container");
+    ctnr.appendChild(h1);
+    ctnr.appendChild(p);
+    myErr = true;
+  }
+};
+
+const handleErrors = (response) => {
+  if (!response.ok) {
+    switch (response.status) {
+      case 404:
+        showError("404 - Resource not found.");
+      case 500:
+        showError("500 - Server error.");
+      case 403:
+        showError("403 - Access forbidden.");
+      default:
+        showError(`${response.status} - Unexpected error.`);
+    }
+  }
+  return response.json();
+};
diff --git a/capstone/js/product.js b/capstone/js/product.js
new file mode 100644
index 0000000..dac6d39
--- /dev/null
+++ b/capstone/js/product.js
@@ -0,0 +1,125 @@
+const qryStr = location.search;
+const urlParams = new URLSearchParams(qryStr);
+const prodId = urlParams.get("product_id");
+const apiUrl = "http://3.136.18.203:8000/products/" + prodId + "/";
+const catUrl = "http://3.136.18.203:8000/categories/";
+const template = _("#prod-template");
+const ctnr = _("#product-container");
+const d = document;
+
+const updatePrice = () => {
+  const varieties = d.querySelector('#varieties');
+  const cost = varieties.options[varieties.selectedIndex].value;
+  const qty=d.querySelector('#quantity').value;
+  const total = (Number(qty)*Number(cost)).toFixed(2);
+  d.querySelector('#variety').value = varieties.options[varieties.selectedIndex].innerText.split("|")[0].trim();
+  d.querySelector('#price').value=cost;
+  d.querySelector('#total_price').value=total;
+  cost_str = total.toString().split(".");
+  cost_html = "$" + cost_str[0] + "<sup>" + cost_str[1] + "</sup>";
+  _(".p-price")[0].innerHTML = cost_html;
+};
+
+const addToCart = ()=>{
+  const prod_id = d.querySelector('#product_id').value;
+  const prod_name = d.querySelector('#prod-title').innerText.trim();
+  const qty = d.querySelector('#quantity').value;
+  const variety = d.querySelector('#variety').value;
+  const unit_price = d.querySelector('#price').value;
+  const total_price= d.querySelector('#total_price').value;
+  const cartItem = {
+    'product_id': prod_id,
+    'product_name': prod_name,
+    'variety': variety,
+    'quantity': qty,
+    'price':unit_price,
+    'total':total_price
+  };
+  
+  const currCartItems = localStorage.getItem("capstoneShoppingCart");
+  if (currCartItems===null || currCartItems===undefined){
+    const nCart = [];
+    nCart.push(cartItem);
+    localStorage.setItem("capstoneShoppingCart",JSON.stringify(nCart));
+    showAlert();
+  }else{
+    const cart=JSON.parse(currCartItems);
+    let currQty=0;
+    for(x of cart){
+      if(x.product_id == prod_id && x.variety == variety){
+        currQty = x.quantity;
+      }
+    }
+    let newCart = [];
+    for (i = 0; i< cart.length; i++){
+      if(cart[i].product_id != prod_id || cart[i].variety!=variety){
+        newCart.push(cart[i]);
+      }
+    }
+    const newQty = Number(qty)+Number(currQty);
+    cartItem.quantity=newQty;
+    cartItem.total = Number(cartItem.quantity)*Number(cartItem.price);
+    newCart.push(cartItem);
+    localStorage.setItem('capstoneShoppingCart',JSON.stringify(newCart));
+    updateCartInfo();
+    showAlert();
+  }
+}
+
+const updateCartInfo=()=>{
+  const shoppingCart = localStorage.getItem("capstoneShoppingCart");
+  if(shoppingCart!=null && shoppingCart !=undefined){
+    const theCart = JSON.parse(shoppingCart);
+    document.querySelector('.cart-icon').setAttribute('data-number',theCart.length);
+  }
+};
+
+const showAlert=()=>{
+  const nfo = document.querySelector('.info');
+    nfo.textContent=_("#prod-title").innerText+' has been added to the cart successfully';
+    nfo.classList.remove('animate');
+    void nfo.offsetWidth;
+    nfo.classList.add('animate');
+}
+
+(()=>{  
+  Promise.all([
+    fetch(apiUrl).then(handleErrors),
+    fetch(catUrl).then(handleErrors),
+  ])
+    .then(([product, categories]) => {
+      const category = categories.find(
+        (cat) => cat.category_id === product.category
+      );
+      parent_cat = categories.find(
+        (c) => c.category_id === category.parent_category
+      );
+      const price_str = product.starting_at_price.toString().split(".");
+      const price_html = price_str[0] + "<sup>" + price_str[1] + "</sup>";
+      let variety_options = "";
+      product.varieties.map((item) => {
+        variety_options += `<option value='${item.price}'>${item.name} | ${item.price}</option>`;
+      });
+      _(".breadcrumb")[0].children[0].textContent = parent_cat.name;
+      _(".breadcrumb")[0].children[1].textContent = category.name;
+      _(".breadcrumb")[0].children[2].textContent = product.name;
+
+      _("#prod-title").textContent = product.name;
+      const myT = template.content.cloneNode(true);
+      myT.querySelector(".p-img").src = product.picture_url;
+      myT.querySelector(".p-title").textContent = product.name;
+      myT.querySelector(".p-description").textContent = product.description;
+      myT.querySelector(".p-category").textContent += category.name;
+      myT.querySelector(".p-quantity").textContent += product.stock_quantity;
+      myT.querySelector(".p-price").innerHTML += price_html;
+      myT.querySelector("select").innerHTML = variety_options;
+      _("#product-container").appendChild(myT);
+      _("#product_id").value=prodId;
+      updatePrice();
+    })
+    .catch((err) => {
+      showError(err.message);
+    });
+})();
+
+(()=>updateCartInfo())();
\ No newline at end of file
diff --git a/capstone/js/products-tmp.js b/capstone/js/products-tmp.js
new file mode 100644
index 0000000..1a99698
--- /dev/null
+++ b/capstone/js/products-tmp.js
@@ -0,0 +1,42 @@
+const template = _("#prod-template");
+const ctnr = _("#product-container");
+const productsApi = "http://3.136.18.203:8000/products/";
+const categoriesApi = "http://3.136.18.203:8000/categories/";
+const fragment = document.createDocumentFragment();
+
+(()=>{
+  Promise.all([
+    fetch(productsApi).then(handleErrors),
+    fetch(categoriesApi).then(handleErrors),
+  ])
+    .then(([products, categories]) => {
+      products.map(item => {
+        const category = categories.find(cat => cat.category_id === item.category);
+        const price_str = item.starting_at_price.toString().split(".");
+        const html_block =`
+          <a class='prod-lnk' href='product.html?product_id=${item.product_id}'>
+            <div class='prod-container'>
+              <h2 class='prod-title'>${item.name}</h2>
+              <div class='prod-img-ctnr'><img class='prod-img' src='${item.picture_url}' alt='' width='500' height='500'></div>
+              <div class='prod-desc'>${item.description}</div>
+              <div class='prod-category'>${category.name}</div>
+              <div class='prod-price'><span>${item.starting_at_price}</div>
+              <div class='prod-quantity'>Stock Qty: ${item.stock_quantity}</div>
+          </div>
+        </a>`;
+        _("#product-container").innerHTML+=html_block;
+      });
+    })
+    .catch((err) => {
+      showError(err.message);
+    });
+  })();
+
+  (()=>{
+    const shoppingCart = localStorage.getItem("capstoneShoppingCart");
+    if(shoppingCart!=null && shoppingCart !=undefined){
+      const theCart = JSON.parse(shoppingCart);
+      console.log(theCart.length);
+      document.querySelector('.cart-icon').setAttribute('data-number',theCart.length);
+    }
+  })();
\ No newline at end of file
diff --git a/capstone/js/products.js b/capstone/js/products.js
new file mode 100644
index 0000000..22f1e43
--- /dev/null
+++ b/capstone/js/products.js
@@ -0,0 +1,40 @@
+const template = _("#prod-template");
+const ctnr = _("#product-container");
+const productsApi = "http://3.136.18.203:8000/products/";
+const categoriesApi = "http://3.136.18.203:8000/categories/";
+const fragment = document.createDocumentFragment();
+
+(()=>{
+  Promise.all([
+    fetch(productsApi).then(handleErrors),
+    fetch(categoriesApi).then(handleErrors),
+  ])
+    .then(([products, categories]) => {
+      products.map(item => {
+        const category = categories.find(cat => cat.category_id === item.category);
+        const price_str = item.starting_at_price.toString().split(".");
+        const myElem = template.content.cloneNode(true);
+        myElem.querySelector(".prod-lnk").href += item.product_id;
+        myElem.querySelector(".prod-img").src = item.picture_url;
+        myElem.querySelector(".prod-title").textContent = item.name;
+        myElem.querySelector(".prod-desc").textContent = item.description;
+        myElem.querySelector(".prod-quantity").textContent += item.stock_quantity;
+        myElem.querySelector(".prod-price").children[0].textContent += price_str[0];
+        myElem.querySelector(".prod-price").children[1].textContent += price_str[1];
+        fragment.appendChild(myElem);
+      });
+      _("#product-container").appendChild(fragment);
+    })
+    .catch((err) => {
+      showError(err.message);
+    });
+  })();
+
+  (()=>{
+    const shoppingCart = localStorage.getItem("capstoneShoppingCart");
+    if(shoppingCart!=null && shoppingCart !=undefined){
+      const theCart = JSON.parse(shoppingCart);
+      console.log(theCart.length);
+      document.querySelector('.cart-icon').setAttribute('data-number',theCart.length);
+    }
+  })();
\ No newline at end of file
diff --git a/capstone/product.html b/capstone/product.html
new file mode 100644
index 0000000..31bada6
--- /dev/null
+++ b/capstone/product.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Details</title>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1">
+    <link rel="stylesheet" href="css/product.css">
+    <link rel="shortcut icon" href="images/favico.png">
+  </head>
+  <body>
+    <header>
+    <div><img src="images/logo.png" alt="Logo"></div>
+    <div class="breadcrumb">
+      <div>Food</div>
+      <div>Dog Food</div>
+      <div>Product Name</div>
+      <div> | <a href="products.html">Products</a> | <a href="categories.html">Categories</a></div>
+    </div>
+    <a class="cart-icon" href="cart.html" data-number="0"></a>
+    </header>
+    <div class="info">Product was addde successfully to the cart.</div>
+    <h1 id="prod-title">Product Details</h1>
+    <section id="product-container"></section>
+    <footer>
+      <div class="copyright">2025&copy; All Rights Reserved.</div>
+    </footer>
+    <template id="prod-template">
+      <div class='p-container'>
+        <div class='p-img-ctnr'><img class='p-img' src='' alt='' width='400' height='400'></div>
+        <h2 class='p-title'></h2>
+        <p class='p-description'></p>
+        <p class='p-category'>Category: </p>
+        <p class='p-quantity'>Stock Quantity: </p>
+        <p class='p-price'>$</p>
+        <h3 class='variety-title'>Varieties</h3>
+        <div><select onchange='updatePrice()' id="varieties"></select></div>
+        <div class='submit-links'>Quantity: <input type='number' value='1' min="1" id="quantity" onchange="updatePrice()"><input type="hidden" id="variety"><input type="hidden" id="price"><input type="hidden" id="total_price"><input type="hidden" id="product_id">
+          <button onclick="addToCart()">Add to Cart</button><a href='cart.html'>View Shopping Cart</a></div>
+      </div>
+    </template>
+    <script src="js/e.js"></script>
+    <script src="js/error.js"></script>
+    <script src="js/product.js"></script>
+  </body>
+</html>
diff --git a/capstone/products-tmp.html b/capstone/products-tmp.html
new file mode 100644
index 0000000..72d205f
--- /dev/null
+++ b/capstone/products-tmp.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Products</title>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,initial-scale=1">
+    <link rel="stylesheet" href="css/products.css">
+    <link rel="shortcut icon" href="images/favico.png">
+  </head>
+  <body>
+    <header>
+      <div><img src="images/logo.png" alt="logo"></div>
+      <div class="lnks"><a href="#">Products</a> | <a href="categories.html">Categories</a></div>
+      <a class="cart-icon" href="cart.html" data-number="0"></a>
+    </header>
+    <h1>All Products</h1>
+    <section id="product-container"></section>
+    <footer>
+      <div class="copyright">2025&copy; All Rights Reserved.</div>
+    </footer>
+    <script src="js/e.js"></script>
+    <script src="js/error.js"></script>
+    <script src="js/products-tmp.js"></script>
+  </body>
+</html>
diff --git a/capstone/products.html b/capstone/products.html
new file mode 100644
index 0000000..6867c99
--- /dev/null
+++ b/capstone/products.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Products</title>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,initial-scale=1">
+    <link rel="stylesheet" href="css/products.css">
+    <link rel="shortcut icon" href="images/favico.png">
+  </head>
+  <body>
+    <header>
+      <div><img src="images/logo.png" alt="logo"></div>
+      <div class="lnks"><a href="#">Products</a> | <a href="categories.html">Categories</a></div>
+      <a class="cart-icon" href="cart.html" data-number="0"></a>
+    </header>
+    <h1>All Products</h1>
+    <section id="product-container"></section>
+    <footer>
+      <div class="copyright">2025&copy; All Rights Reserved.</div>
+    </footer>
+    <template id="prod-template">
+      <a class='prod-lnk' href='product.html?product_id='>
+        <div class='prod-container'>
+            <h2 class='prod-title'></h2>
+            <div class='prod-img-ctnr'><img class='prod-img' src='' alt='' width='500' height='500'></div>
+            <div class='prod-desc'></div>
+            <div class='prod-category'></div>
+            <div class='prod-price'><span>$</span><sup></sup></div>
+            <div class='prod-quantity'>Stock Qty: </div>
+        </div></a>
+    </template>
+    <script src="js/e.js"></script>
+    <script src="js/error.js"></script>
+    <script src="js/products.js"></script>
+  </body>
+</html>
diff --git a/frontend/css/styles.css b/frontend/css/styles.css
index c6c0dc5..08300f0 100644
Binary files a/frontend/css/styles.css and b/frontend/css/styles.css differ
diff --git a/frontend/js/functions.js b/frontend/js/functions.js
index b0ba666..43b5187 100644
Binary files a/frontend/js/functions.js and b/frontend/js/functions.js differ
diff --git a/frontend/js/products.js b/frontend/js/products.js
index d78e03b..49fefc8 100644
Binary files a/frontend/js/products.js and b/frontend/js/products.js differ


Reviews:
user: login: lanceparlier
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T20:02:41Z
user: login: lanceparlier
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T20:03:29Z
user: login: lanceparlier
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T20:04:17Z
user: login: lanceparlier
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T20:08:13Z
user: login: mtmehdi2
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-22T03:36:00Z
user: login: mtmehdi2
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-22T03:37:34Z
user: login: mtmehdi2
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-22T03:38:36Z
user: login: mtmehdi2
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-22T03:42:46Z

Review comments:
diff_hunk: @@ -0,0 +1,23 @@
+Promise.all([
+  fetch("http://3.136.18.203:8000/products/").then((res) => res.json()),
+  fetch("http://3.136.18.203:8000/categories/").then((res) => res.json()),
+])
+  .then(([products, categories]) => {
+    products.map((item) => {
+      const category = categories.find(
+        (cat) => cat.category_id === item.category
+      );
+      _("#products-container").innerHTML += `
+        <a class='prod-lnk' href='product.html?product_id=${item.product_id}'>
+        <div class='prod-container'>
+            <div class='prod-img-ctnr'><img class='prod-img' src='${item.picture_url}' alt='' width='500' height='500'></div>
+            <h2 class='prod-titlle'>${item.name}</h2>
+            <div class='prod-category'>${category.name}</div>
+            <div class='prod-quantity'>Stock: ${item.stock_quantity}</div>
+            <div class='prod-price'>$${item.starting_at_price}</div>
+        </div></a>`;
+    });
+  })
path: capstone/js/products.js
user: login: lanceparlier
body: This works, but can you change it to use `<template>` to clean it up? This way would separate the html and js a little more and have less js in the html. https://www.w3schools.com/tags/tag_template.asp
created_at: 2025-03-21T20:02:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,23 @@
+Promise.all([
+  fetch("http://3.136.18.203:8000/products/").then((res) => res.json()),
+  fetch("http://3.136.18.203:8000/categories/").then((res) => res.json()),
+])
+  .then(([products, categories]) => {
+    products.map((item) => {
+      const category = categories.find(
+        (cat) => cat.category_id === item.category
+      );
+      _("#products-container").innerHTML += `
+        <a class='prod-lnk' href='product.html?product_id=${item.product_id}'>
+        <div class='prod-container'>
+            <div class='prod-img-ctnr'><img class='prod-img' src='${item.picture_url}' alt='' width='500' height='500'></div>
+            <h2 class='prod-titlle'>${item.name}</h2>
+            <div class='prod-category'>${category.name}</div>
+            <div class='prod-quantity'>Stock: ${item.stock_quantity}</div>
+            <div class='prod-price'>$${item.starting_at_price}</div>
+        </div></a>`;
+    });
+  })
+  .catch((err) => {
+    console.error(err.message);
+  });
path: capstone/js/products.js
user: login: lanceparlier
body: Can you handle a little more of the errors? For example, if no products are loaded then display "No products found" to the user, and if it's a server error display something different?
created_at: 2025-03-21T20:03:29Z
author_association: NONE
diff_hunk: @@ -0,0 +1,43 @@
+const qryStr = location.search;
+const urlParams = new URLSearchParams(qryStr);
+const prodId = urlParams.get("product_id");
+const apiUrl = "http://3.136.18.203:8000/products/" + prodId + "/";
+Promise.all([
+  fetch(apiUrl).then((res) => res.json()),
+  fetch("http://3.136.18.203:8000/categories/").then((res) => res.json()),
+])
+  .then(([product, categories]) => {
+    const category = categories.find(
+      (cat) => cat.category_id === product.category
+    );
+    price_str = product.starting_at_price.toString().split(".");
+    price_html = price_str[0] + "<sup>" + price_str[1] + "</sup>";
+    let variety_options = "";
+    product.varieties.map((item) => {
+      variety_options += `<option value='${item.price}'>${item.name} | ${item.price}</option>`;
+    });
+    _("#prod-title").innerHTML = product.name;
+    _("#details-container").innerHTML += `
+        <div class='p-container'>
+            <div class='p-img-ctnr'><img class='p-img' src='${product.picture_url}' alt='' width='400' height='400'></div>
+            <h2 class='p-titlle'>${product.name}</h2>
+            <p class='p-description'>${product.description}</p>
+            <p class='p-category'>Category: ${category.name}</p>
+            <p class='p-quantity'>Stock: ${product.stock_quantity}</p>
+            <p class='p-price'>$${price_html}</p>
+            <h3 class='variety-title'>Varieties</h3>
+            <div><select onchange='updatePrice(this.value)'>${variety_options}</select></div>
+            <div class='submit-links'><button>Add to Cart</button><a href='products.html'>Back to Products page</a></div>
+        </div>`;
+  })
+  .catch((err) => {
+    _("#details-container").innerHTML = `<h1>Oops!</h1>
+    <p>I was not able to find this product.</p>
+    <p>Please browse <strong><a href='products.html'>products</a></strong> and select the product to view.</p>`;
path: capstone/js/product.js
user: login: lanceparlier
body: I like this error, would like to see more error handling for any other cases you can think of
created_at: 2025-03-21T20:04:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,43 @@
+const qryStr = location.search;
+const urlParams = new URLSearchParams(qryStr);
+const prodId = urlParams.get("product_id");
+const apiUrl = "http://3.136.18.203:8000/products/" + prodId + "/";
+Promise.all([
+  fetch(apiUrl).then((res) => res.json()),
+  fetch("http://3.136.18.203:8000/categories/").then((res) => res.json()),
+])
+  .then(([product, categories]) => {
+    const category = categories.find(
+      (cat) => cat.category_id === product.category
+    );
+    price_str = product.starting_at_price.toString().split(".");
+    price_html = price_str[0] + "<sup>" + price_str[1] + "</sup>";
path: capstone/js/product.js
user: login: lanceparlier
body: I notice a couple variables are used without being declared first. use const or let 
created_at: 2025-03-21T20:08:13Z
author_association: NONE
diff_hunk: @@ -0,0 +1,23 @@
+Promise.all([
+  fetch("http://3.136.18.203:8000/products/").then((res) => res.json()),
+  fetch("http://3.136.18.203:8000/categories/").then((res) => res.json()),
+])
+  .then(([products, categories]) => {
+    products.map((item) => {
+      const category = categories.find(
+        (cat) => cat.category_id === item.category
+      );
+      _("#products-container").innerHTML += `
+        <a class='prod-lnk' href='product.html?product_id=${item.product_id}'>
+        <div class='prod-container'>
+            <div class='prod-img-ctnr'><img class='prod-img' src='${item.picture_url}' alt='' width='500' height='500'></div>
+            <h2 class='prod-titlle'>${item.name}</h2>
+            <div class='prod-category'>${category.name}</div>
+            <div class='prod-quantity'>Stock: ${item.stock_quantity}</div>
+            <div class='prod-price'>$${item.starting_at_price}</div>
+        </div></a>`;
+    });
+  })
path: capstone/js/products.js
user: login: mtmehdi2
body: Thank you for your feedback, sir!

I thought of the following approaches for it and thought, this one might need less code as compared to other two approaches. But I didn't consider the performance. I'll change it to the template as you proposed.

1. Create those elements through JavaScript, set their classes and other properties, fill them with the data and append them as children elements of the container.
2. Use template and populate the data and append it to the container similar to the alert box template that I had used in previous project. (used div instead of template tag but for the same purpose)
3. Use current approach (This can further be improved to boost the performance).

created_at: 2025-03-22T03:36:00Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,23 @@
+Promise.all([
+  fetch("http://3.136.18.203:8000/products/").then((res) => res.json()),
+  fetch("http://3.136.18.203:8000/categories/").then((res) => res.json()),
+])
+  .then(([products, categories]) => {
+    products.map((item) => {
+      const category = categories.find(
+        (cat) => cat.category_id === item.category
+      );
+      _("#products-container").innerHTML += `
+        <a class='prod-lnk' href='product.html?product_id=${item.product_id}'>
+        <div class='prod-container'>
+            <div class='prod-img-ctnr'><img class='prod-img' src='${item.picture_url}' alt='' width='500' height='500'></div>
+            <h2 class='prod-titlle'>${item.name}</h2>
+            <div class='prod-category'>${category.name}</div>
+            <div class='prod-quantity'>Stock: ${item.stock_quantity}</div>
+            <div class='prod-price'>$${item.starting_at_price}</div>
+        </div></a>`;
+    });
+  })
+  .catch((err) => {
+    console.error(err.message);
+  });
path: capstone/js/products.js
user: login: mtmehdi2
body: OK sir, I'll work on this. Let me see how I can improve this.
created_at: 2025-03-22T03:37:33Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,43 @@
+const qryStr = location.search;
+const urlParams = new URLSearchParams(qryStr);
+const prodId = urlParams.get("product_id");
+const apiUrl = "http://3.136.18.203:8000/products/" + prodId + "/";
+Promise.all([
+  fetch(apiUrl).then((res) => res.json()),
+  fetch("http://3.136.18.203:8000/categories/").then((res) => res.json()),
+])
+  .then(([product, categories]) => {
+    const category = categories.find(
+      (cat) => cat.category_id === product.category
+    );
+    price_str = product.starting_at_price.toString().split(".");
+    price_html = price_str[0] + "<sup>" + price_str[1] + "</sup>";
+    let variety_options = "";
+    product.varieties.map((item) => {
+      variety_options += `<option value='${item.price}'>${item.name} | ${item.price}</option>`;
+    });
+    _("#prod-title").innerHTML = product.name;
+    _("#details-container").innerHTML += `
+        <div class='p-container'>
+            <div class='p-img-ctnr'><img class='p-img' src='${product.picture_url}' alt='' width='400' height='400'></div>
+            <h2 class='p-titlle'>${product.name}</h2>
+            <p class='p-description'>${product.description}</p>
+            <p class='p-category'>Category: ${category.name}</p>
+            <p class='p-quantity'>Stock: ${product.stock_quantity}</p>
+            <p class='p-price'>$${price_html}</p>
+            <h3 class='variety-title'>Varieties</h3>
+            <div><select onchange='updatePrice(this.value)'>${variety_options}</select></div>
+            <div class='submit-links'><button>Add to Cart</button><a href='products.html'>Back to Products page</a></div>
+        </div>`;
+  })
+  .catch((err) => {
+    _("#details-container").innerHTML = `<h1>Oops!</h1>
+    <p>I was not able to find this product.</p>
+    <p>Please browse <strong><a href='products.html'>products</a></strong> and select the product to view.</p>`;
path: capstone/js/product.js
user: login: mtmehdi2
body: OK sir, let me see what I can do.
created_at: 2025-03-22T03:38:36Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,43 @@
+const qryStr = location.search;
+const urlParams = new URLSearchParams(qryStr);
+const prodId = urlParams.get("product_id");
+const apiUrl = "http://3.136.18.203:8000/products/" + prodId + "/";
+Promise.all([
+  fetch(apiUrl).then((res) => res.json()),
+  fetch("http://3.136.18.203:8000/categories/").then((res) => res.json()),
+])
+  .then(([product, categories]) => {
+    const category = categories.find(
+      (cat) => cat.category_id === product.category
+    );
+    price_str = product.starting_at_price.toString().split(".");
+    price_html = price_str[0] + "<sup>" + price_str[1] + "</sup>";
path: capstone/js/product.js
user: login: mtmehdi2
body: OK sure, I'll fix it. Thanks for the feedback.
created_at: 2025-03-22T03:42:46Z
author_association: OWNER

PR comments:
user: login: lanceparlier
created_at: 2025-03-21T20:06:32Z
body: One more thing I would like to see on these pages since you are moving along quickly: can you have the product page show breadcrumbs for the category? For example, if something has a "Cat Food" category and a parent category of "Food", it would show at the top of the product **Food -> Cat Food -> [Product Name]**
author_association: NONE
user: login: mtmehdi2
created_at: 2025-03-22T03:41:51Z
body: > One more thing I would like to see on these pages since you are moving along quickly: can you have the product page show breadcrumbs for the category? For example, if something has a "Cat Food" category and a parent category of "Food", it would show at the top of the product **Food -> Cat Food -> [Product Name]**

OK sir, I'll add breadcrumbs as well to the pages.
author_association: OWNER