Skip to content

Commit

Permalink
Merge branch 'creme332:main' into improveuser
Browse files Browse the repository at this point in the history
  • Loading branch information
Divyeshhhh authored May 22, 2024
2 parents 3528ca3 + e8ec61c commit 65c8859
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 133 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ The code for the admin website is found in a separate repository.
## Main features

- MVC pattern
- Dynamic routing
- Semantic URL routing
- Email-based password recovery
- Email notification on order
- Testing with phpUnit
- Mobile-responsive website
- Javascript code bundling with backwards compatibility
- Utilizes Webpack for efficient code bundling and compatibility with older browsers.
- Product review system with nested comments
- Fuzzy searching on shop page
- Pagination
- SEO optimized
- REST API

## Documentation
Expand Down
17 changes: 13 additions & 4 deletions public/js_original/cart-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import Cart from "./models/Cart";
import CartItem from "./models/CartItem";
import ModalManager from "./modal";

function updateCart(e) {
const sectionNode = e.target.parentNode.parentNode;
Expand Down Expand Up @@ -50,6 +49,9 @@ function updateCart(e) {
}

async function checkout() {
// set loading animation on checkout button to prevent multiple form submissions
document.querySelector("#checkout-btn").setAttribute("aria-busy", "true");

const myCart = Cart();
const items = myCart.getItems();

Expand All @@ -63,10 +65,14 @@ async function checkout() {
body: JSON.stringify(data),
});

// stop loading animation
document.querySelector("#checkout-btn").removeAttribute("aria-busy");

if (response.ok) {
// Clear cart items from localStorage if checkout is successful
myCart.clear();
ModalManager("my-modal").openModal();

document.querySelector("#my-modal").setAttribute("open", "");
return;
}
const x = await response.json();
Expand All @@ -85,9 +91,12 @@ function initCartPage() {
...document.querySelectorAll("section input[type='number']"),
];

ModalManager("my-modal").init();
const checkoutBtn = document.querySelector("#checkout-btn");

document.querySelector("#checkout-btn").addEventListener("click", checkout);
// if checkout button is present on page (button is absent when cart is empty)
if (checkoutBtn !== null) {
checkoutBtn.addEventListener("click", checkout);
}

quantityInputs.forEach((input) => {
input.addEventListener("change", updateCart);
Expand Down
30 changes: 30 additions & 0 deletions public/js_original/profile-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
function openTab(evt, tabName) {
console.log("New tab = " + tabName);

// hide all tab contents
const tabcontents = [...document.getElementsByClassName("tabcontent")];
for (let i = 0; i < tabcontents.length; i++) {
tabcontents[i].style.display = "none";
}

// remove active class from the currently active tab link
const tablinks = document.getElementsByClassName("tablink");
for (let i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}

// display content for clicked tab
document.getElementById(tabName).style.display = "block";

// set active class only to the clicked tab link
evt.currentTarget.className += " active";
}

const tabs = ["Account", "Orders", "Settings"];

window.addEventListener("DOMContentLoaded", () => {
[...document.getElementsByClassName("tablink")].forEach((tablink, i) => {
console.log(i, tablink);
tablink.addEventListener("click", (e) => openTab(e, tabs[i]));
});
});
27 changes: 24 additions & 3 deletions public/styles/views/Profile.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,32 @@ button[name="account_delete_submit"] {
background-color: red;
}

table button{
padding:5px;
table button {
padding: 5px;
}

table tr button:first-of-type{
table tr button:first-of-type {
border: 0;
background-color: red;
}

/* Style tab links */
.tablink {
float: left;
border: none;
outline: none;
cursor: pointer;
font-size: 17px;
background-color: var(--secondary);
}

.active {
background-color: var(--contrast);
color: var(--contrast-inverse);
}

/* Style the tab content (and add height:100% for full page content) */
.tabcontent {
display: none;
padding: 20px 0;
}
44 changes: 30 additions & 14 deletions src/controllers/Cart.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
namespace Steamy\Controller;

use Exception;
use PDOException;
use Steamy\Core\Controller;
use Steamy\Core\Utility;
use Steamy\Model\Mailer;
use Steamy\Model\Order;
use Steamy\Model\OrderProduct;
use Steamy\Model\Product;
Expand Down Expand Up @@ -71,8 +71,6 @@ private function handleInvalidURL(): void

private function handleCheckout(): void
{
// TODO: write appropriate errors to Cart view instead of sending response code

// check if user is logged in
$signed_client = $this->getSignedInClient();
if (!$signed_client) {
Expand Down Expand Up @@ -110,25 +108,43 @@ private function handleCheckout(): void
$new_order->addLineItem($line_item);
}

// save order
$success_order = false;
// attempt to save order. An exception will be generated in case of any errors.
try {
$success_order = $new_order->save();
http_response_code($success_order ? 201 : 400);
} catch (PDOException $e) {
error_log($e->getMessage());
http_response_code(503);
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
return;
} catch (Exception $e) {
error_log($e->getMessage());
http_response_code(500);
http_response_code(400);
echo json_encode(['error' => $e->getMessage()]);
return;
}
// if order was unsuccessfully saved without any exceptions generated
if (!$success_order) {
http_response_code(500);
echo json_encode(['error' => "Order could not be saved for an unknown reason."]);
return;
}

// send confirmation email if order was successfully saved
if ($success_order) {
try {
$signed_client->sendOrderConfirmationEmail($new_order);
} catch (Exception $e) {
error_log($e->getMessage());
}
// send confirmation email
try {
$success_mail = $signed_client->sendOrderConfirmationEmail($new_order);
} catch (Exception $e) {
http_response_code(503);
echo json_encode(['error' => "Order was saved but email could not be sent: " . $e->getMessage()]);
return;
}

if (!$success_mail) {
http_response_code(503);
echo json_encode(['error' => "Order was saved but email could not be sent for an unknown reason."]);
}

// if everything is good, tell client to reset the document view
http_response_code(205);
}

public function index(): void
Expand Down
11 changes: 4 additions & 7 deletions src/controllers/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Product

private ?ProductModel $product = null; // product to be displayed
private array $view_data;
private ?User $signed_user = null; // currently logged-in user
private ?User $signed_user; // currently logged-in user

public function __construct()
{
Expand All @@ -38,13 +38,10 @@ public function __construct()
// get product id from URL
$product_id = filter_var(Utility::splitURL()[2], FILTER_VALIDATE_INT);

// check if user is logged in
$reviewer_email = $_SESSION['user'] ?? "";

// get user details
$user_account = Client::getByEmail($reviewer_email);
if (!empty($user_account)) {
$this->signed_user = $user_account;
$this->signed_user = $this->getSignedInClient();

if (!empty($this->signed_user)) {
$this->view_data["signed_in_user"] = true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/controllers/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ private function handleAccountDeletion(): void
*/
private function handleUnsignedUsers(): void
{
if (!array_key_exists('user', $_SESSION) || !isset($_SESSION['user'])) {
if (empty($this->getSignedInClient())) {
Utility::redirect('login');
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/core/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,13 @@ private function getLibrariesTags(array $required_libraries): string
return $script_str;
}

/**
* @return Client|null Client account of currently logged-in user. Null if no one is logged in.
*/
private function getSignedInClient(): ?Client
{
// $_SESSION['user'] was set to the client email on login
// if it is empty, no one is logged in
if (empty($_SESSION['user'])) {
return null;
}
Expand Down
50 changes: 34 additions & 16 deletions src/models/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,8 @@ public function save(): bool
$update_stock_stm = $conn->prepare($query);

foreach ($this->line_items as $line_item) {
if (!empty($line_item->validate())) {
// line item contains invalid attributes
$conn->rollBack();
$conn = null;
throw new Exception("Invalid line item:" . json_encode($line_item));
}
// set order ID of line item
$line_item->setOrderID($new_order_id);

// fetch product corresponding to line item
$product = Product::getByID($line_item->getProductID());
Expand All @@ -139,22 +135,49 @@ public function save(): bool
throw new Exception("Product with ID " . $line_item->getProductID() . " does not exist");
}

// set true unit price of line item
$line_item->setUnitPrice($product->getPrice());

// get stock level for current product
$stock_level = $store->getProductStock($product->getProductID());

if ($line_item->getQuantity() > $stock_level) {
// store does not have enough stock
$conn->rollBack();
$conn = null;

$error_message = <<< EOL
Store with ID $this->store_id has insufficient stock ($stock_level) for the following line item:
Product ID = {$line_item->getProductID()} and quantity = {$line_item->getQuantity()}.
EOL;

throw new Exception($error_message);
}

// validate line item
$line_item_errors = $line_item->validate();
if (!empty($line_item_errors)) {
// line item contains invalid attributes
$conn->rollBack();
$conn = null;

$line_item_info = json_encode($line_item->toArray());
$line_item_errors = json_encode($line_item_errors);

$error_message = <<< EOL
Invalid line item:
$line_item_info
Errors:
$line_item_errors
EOL;

throw new Exception(
"Store with ID " . $this->store_id
. " has insufficient stock for product " . $line_item->getProductID()
$error_message
);
}

// insert into order_product table
$line_item->setOrderID($new_order_id);
$line_item->setUnitPrice($product->getPrice());
// insert line item into order_product table

$success = $insert_line_item_stm->execute($line_item->toArray());
if (!$success) {
Expand Down Expand Up @@ -192,14 +215,9 @@ public function save(): bool
*
* @param OrderProduct $orderProduct
* @return void
* @throws Exception
*/
public function addLineItem(OrderProduct $orderProduct): void
{
$errors = $orderProduct->validate();
if (!empty($errors)) {
throw new Exception("Invalid line item: " . json_encode($errors));
}
$this->line_items[] = $orderProduct;
}

Expand Down
9 changes: 9 additions & 0 deletions src/models/OrderProduct.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ class OrderProduct
private int $quantity;
private float $unit_price;

/**
* Create a new OrderProduct object
* @param int $product_id
* @param string $cup_size
* @param string $milk_type
* @param int $quantity
* @param float|null $unit_price If not set, the default $unit_price is -1.
* @param int|null $order_id If not set, the default $order_id is -1.
*/
public function __construct(
int $product_id,
string $cup_size,
Expand Down
15 changes: 1 addition & 14 deletions src/views/Cart.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,11 @@
?>
<dialog id="my-modal">
<article>
<a href="#"
aria-label="Close"
class="close"
data-target="my-modal"
>
</a>
<h3>Checkout successful! ✨</h3>
<p>Your order has been successfully placed and an email has been sent to you.</p>
<footer>
<a href="#"
role="button"
class="secondary"
data-target="my-modal"
>
Ok
</a>
<a href="/profile"
role="button"
data-target="my-modal"
>
View order
</a>
Expand Down
2 changes: 1 addition & 1 deletion src/views/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ class="close"

<textarea name="comment" placeholder="Your comment" cols="20" rows="5"></textarea>
<small style="color:red"><?= $comment_form_info['error'] ?? "" ?></small>
<button class="secondary" type="submit">Submit</button>
<button class="secondary" type="submit" <?= $signed_in_user ? "" : "disabled" ?>>Submit</button>
</form>
</article>
</dialog>
Expand Down
Loading

0 comments on commit 65c8859

Please sign in to comment.