Files
midrandbooks/MidrandBookshop/Components/Pages/Checkout.razor
T
Khwezi Mngoma 5d614d2a94
continuous-integration/drone/pr Build is passing
Checkout page clean up
2026-06-16 21:12:12 +02:00

250 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@page "/checkout"
@inject NavigationManager Navigation
@rendermode InteractiveServer
@attribute [Authorize]
<div class="checkout-page-container py-5">
<!-- HANDOVER MODAL VIEW OVERLAY -->
@if (IsProcessing)
{
<div class="processing-screen-overlay">
<div class="processing-card-box">
<div class="mb-1">
<svg class="payment-vector-loader" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<circle class="loader-track" cx="12" cy="12" r="10" />
<path class="loader-handshake-ring" d="M12 2a10 10 0 0 1 10 10" />
</svg>
</div>
<div>
<h4 class="fw-bold text-dark font-monospace text-uppercase tracking-wider mb-2" style="font-size: 1.1rem;">
Securing Your Order
</h4>
<p class="text-muted small mb-0 px-2" style="line-height: 1.5; font-size: 0.85rem;">
Please stand by. We are preparing your payment portal and transferring you securely to PayFast.
</p>
</div>
<div class="secure-badge">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
<span>Bank-Grade 256-Bit SSL Connection</span>
</div>
</div>
</div>
}
<!-- Header Presentation Details -->
<header class="checkout-header mb-5">
<span class="text-uppercase font-monospace text-muted tracking-wider small d-block mb-1">Secure Checkout</span>
<h1 class="checkout-main-title fw-bold">Review Your Order</h1>
</header>
@if (ShoppingCart.Items.Any() == false)
{
<!-- EMPTY STATE TRAP: Displays when cart hits zero items -->
<div class="checkout-section-panel text-center py-5 my-4 d-flex flex-column align-items-center gap-3">
<div class="text-muted opacity-50">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
</div>
<div>
<h4 class="fw-bold text-dark mb-1">Your cart is empty</h4>
<p class="text-muted small mb-0">You cannot proceed to payment without selected titles.</p>
</div>
<a href="/" class="btn btn-premium-action px-4 py-2 mt-2 text-decoration-none" style="font-size: 0.85rem;">
Browse Book Catalogue
</a>
</div>
}
else
{
<div class="row g-5">
<!-- Left Side Forms Layout Matrix -->
<div class="col-lg-7">
<!-- SECTION A: SHOPPING CART MANIFEST LIST -->
<section class="checkout-section-panel mb-4">
<div class="panel-header-row d-flex justify-content-between align-items-center mb-4">
<h5 class="panel-title fw-bold mb-0">Your Selection</h5>
<span class="badge rounded-pill bg-light text-dark font-monospace px-2.5 py-1.5 border">
@ShoppingCart.Items.Count Items
</span>
</div>
<div class="checkout-items-stack">
@foreach (var item in ShoppingCart.Items)
{
<div class="checkout-item-row py-3 d-flex align-items-center justify-content-between">
<div class="item-meta-details pe-4">
<h6 class="item-product-name fw-bold mb-1 text-dark">@item.Product!.Name</h6>
<span class="item-author-label small text-muted font-monospace">
By @($"{item.Author!.Name} {item.Author.LastName}")
</span>
</div>
<div class="item-interactive-actions d-flex align-items-center gap-3 flex-shrink-0">
<div class="premium-quantity-stepper">
<button class="step-btn" @onclick="() => ChangeQuantity(item, -1)" aria-label="Decrease quantity"></button>
<span class="step-value font-monospace">@item.Quantity</span>
<button class="step-btn" @onclick="() => ChangeQuantity(item, 1)" aria-label="Increase quantity">+</button>
</div>
<button class="btn-clean-remove font-monospace text-uppercase" @onclick="() => RemoveFromCart(item)">
Remove
</button>
</div>
</div>
}
</div>
</section>
<!-- SECTION B: FULFILLMENT SELECTABLE METHODS -->
<section class="checkout-section-panel mb-4">
<h5 class="panel-title fw-bold mb-4">Fulfillment Option</h5>
<div class="premium-radio-group d-flex flex-column gap-3">
<!-- Collect Card Structure -->
<div class="premium-selectable-card @(ShippingCost == 0 ? "active" : "")">
<input class="form-check-input visual-hidden" type="radio" name="shipping" id="pickup"
checked=@(ShippingCost == 0) @onclick="() => ShippingCost = 0">
<div class="card-indicator-circle"></div>
<label class="card-text-block m-0 context-clickable" for="pickup">
<span class="card-label-title fw-bold d-block text-dark">Collect from Midrand Bookshop</span>
<span class="card-label-desc text-muted small">Corner of Church & Third Roads. Ready within 2 hours.</span>
</label>
<span class="card-price-tag font-monospace ms-auto text-success fw-bold">FREE</span>
</div>
<!-- Courier Card Structure -->
<div class="premium-selectable-card @(ShippingCost == 60 ? "active" : "")">
<input class="form-check-input visual-hidden" type="radio" name="shipping" id="delivery"
checked=@(ShippingCost == 60) @onclick="() => ShippingCost = 60">
<div class="card-indicator-circle"></div>
<label class="card-text-block m-0 context-clickable" for="delivery">
<span class="card-label-title fw-bold d-block text-dark">Door-to-Door Home Delivery</span>
<span class="card-label-desc text-muted small">Dispatched via reliable overnight courier straight to your steps.</span>
</label>
<span class="card-price-tag font-monospace ms-auto text-dark fw-bold">R 60.00</span>
</div>
</div>
</section>
<!-- SECTION C: ADDITIONAL COURIER DELIVERY NOTES -->
<section class="checkout-section-panel mb-4">
<div class="d-flex justify-content-between align-items-baseline mb-2">
<h5 class="panel-title fw-bold mb-0">Delivery Instructions</h5>
<span class="text-muted font-monospace extra-small opacity-75">Optional</span>
</div>
<p class="text-muted small mb-3">
Add any specific details for our dispatch team (e.g., gate access codes, complex navigation, or safe drop-off preferences).
</p>
<div class="premium-textarea-wrapper">
<textarea class="form-control premium-plaintext-field"
rows="3"
placeholder="Type your notes or courier instructions here..."
@bind="OrderNotes"
maxlength="500">
</textarea>
<div class="text-end mt-1.5">
<small class="font-monospace text-muted extra-small opacity-50">
@(OrderNotes?.Length ?? 0) / 500 characters
</small>
</div>
</div>
</section>
<!-- SECTION D: BILLING ADDRESS EQUALITY SETTINGS -->
<section class="checkout-section-panel">
<h5 class="panel-title fw-bold mb-3">Billing Settings</h5>
<div class="premium-checkbox-wrapper d-flex align-items-center gap-3 p-3 border rounded-3">
<input class="form-check-input custom-box-tick m-0" type="checkbox" id="sameAsBilling" @bind="IsSameAddress">
<label class="checkbox-text-label small text-dark fw-medium m-0 context-clickable w-100 py-1" for="sameAsBilling">
My billing address is the same as my shipping address
</label>
</div>
</section>
</div>
<!-- Right Side Sidebar Sticky Cost Summary -->
<div class="col-lg-5">
<div class="sticky-summary-card p-4 border">
<h5 class="fw-bold text-dark font-monospace text-uppercase tracking-wider mb-4 small-summary-heading">Summary Breakdown</h5>
<div class="price-summary-ledger d-flex flex-column gap-3">
<div class="ledger-row d-flex justify-content-between">
<span class="text-muted small">Items Subtotal</span>
<span class="font-monospace text-dark fw-medium">R @ShoppingCart.TotalAmount.ToString("F2")</span>
</div>
<!-- VAT DISPLAY LOGIC SWITCH -->
<div class="ledger-row d-flex justify-content-between align-items-center">
@if (ShoppingCart.TotalVat > 0)
{
<span class="text-muted small">Value Added Tax (VAT 15%)</span>
<span class="font-monospace text-dark fw-medium">R @ShoppingCart.TotalVat.ToString("F2")</span>
}
else
{
<span class="text-muted small">Value Added Tax (VAT)</span>
<small class="text-danger fw-medium tracking-wide font-monospace" style="font-size: 0.78rem;">
(Price is VAT inclusive)
</small>
}
</div>
<div class="ledger-row d-flex justify-content-between">
<span class="text-muted small">Fulfillment Courier Fee</span>
<span class="font-monospace text-dark fw-medium">
@if (ShippingCost == 0)
{
<span class="text-success fw-bold">FREE</span>
}
else
{
<span>R @($"{ShippingCost:F2}")</span>
}
</span>
</div>
<hr class="summary-divider my-2" />
<div class="ledger-row d-flex justify-content-between align-items-baseline mb-4">
<span class="fw-bold text-dark">Total Due Amount</span>
<div class="text-end">
<span class="font-monospace text-dark fw-bold h3 mb-0 d-block tracking-tight">
R @($"{ShoppingCart.TotalAmount + ShoppingCart.TotalVat + ShippingCost:F2}")
</span>
<small class="text-muted opacity-60 font-monospace extra-small">ZAR Currency</small>
</div>
</div>
</div>
<button class="btn btn-premium-action w-100 py-3.5 d-flex align-items-center justify-content-center gap-2"
disabled="@IsProcessing"
@onclick="PayNow">
<span>Pay Now</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
</svg>
</button>
</div>
</div>
<!-- Automatic PayFast Form Handover Context -->
@if (IsProcessing == true && CheckoutPayload?.Count > 0)
{
<form id="payfastForm" action="@PayfastOptions.Value.CheckoutUrl" method="POST">
@foreach (var field in CheckoutPayload)
{
<input type="hidden" name="@field.Key" value="@field.Value" />
}
</form>
}
</div>
}
</div>