+
+
+ @if (IsProcessing)
+ {
+
+
+
+
+
+ Securing Your Order
+
+
+ Please stand by. We are preparing your payment portal and transferring you securely to PayFast.
+
+
+
+
+
Bank-Grade 256-Bit SSL Connection
+
+
+
+ }
+
+
+
+
+ @if (ShoppingCart.Items.Any() == false)
+ {
+
+
+
+
+
Your cart is empty
+
You cannot proceed to payment without selected titles.
+
+
+ Browse Book Catalogue
+
+
+ }
+ else
+ {
+
+
+
+
+
+
+
+
+
+ @foreach (var item in ShoppingCart.Items)
+ {
+
+
+
@item.Product!.Name
+
+ By @($"{item.Author!.Name} {item.Author.LastName}")
+
+
+
+
+
+ @item.Quantity
+
+
+
+
-
+ }
+
+
+
+
+
+ Fulfillment Option
+
+
+
+
+
+
ShippingCost = 0">
+
+
+
FREE
+
+
+
+
+
ShippingCost = 60">
+
+
+
R 60.00
- }
+
+
+
+
+
+
Delivery Instructions
+
+
+
+ Add any specific details for our dispatch team (e.g., gate access codes, complex navigation, or safe drop-off preferences).
+
+
+
+
+
+
-
-
Shipping Method
-
- ShippingCost = 0">
-
-
-
-
-
-
-
Order Summary
-
SubtotalR @ShoppingCart.TotalAmount.ToString("F2")
-
VAT (15%)R @ShoppingCart.TotalVat.ToString("F2")
-
ShippingR @($"{ShippingCost:F2}")
-
-
- Total Due
-
R @($"{ShoppingCart.TotalAmount + ShoppingCart.TotalVat + ShippingCost:F2}")
-
-
-
-
-
- @if (IsProcessing == true && CheckoutPayload?.Count > 0)
- {
-
- }
-
+ }
\ No newline at end of file
diff --git a/MidrandBookshop/Components/Pages/Checkout.razor.cs b/MidrandBookshop/Components/Pages/Checkout.razor.cs
index c82d88d..f5e1c46 100644
--- a/MidrandBookshop/Components/Pages/Checkout.razor.cs
+++ b/MidrandBookshop/Components/Pages/Checkout.razor.cs
@@ -29,6 +29,7 @@ public partial class Checkout()
private decimal ShippingCost = 0;
private bool IsSameAddress = true;
+ public string? OrderNotes { get; set; }
private Dictionary
CheckoutPayload { get; set; } = [];
protected override async Task OnInitializedAsync()
@@ -38,6 +39,9 @@ public partial class Checkout()
Navigation.LocationChanged += OnLocationChanged;
CartService.OnCartChanged += CartService_OnCartChanged;
+
+ if (CartService.ShoppingCart.Items.Count == 0)
+ await CartService.LoadCartFromStorageAsync();
}
private async void CartService_OnCartChanged() => await InvokeAsync(StateHasChanged);
@@ -113,7 +117,7 @@ public partial class Checkout()
if (paymentGen.IsSuccess) paymentId = paymentGen.Value;
- if(paymentGen.IsFailed)
+ if (paymentGen.IsFailed)
{
var paymentFetch = await PaymentService.GetOrderPaymentAsync(orderId, CancellationToken);
@@ -161,7 +165,7 @@ public partial class Checkout()
await JSRuntime.InvokeVoidAsync("eval", "document.getElementById('payfastForm').submit();");
}
- catch(Exception ex)
+ catch (Exception ex)
{
ToastService.ShowError($"Failed to perform checkout: {ex.Message}", "Checkout");
diff --git a/MidrandBookshop/Components/Pages/Checkout.razor.css b/MidrandBookshop/Components/Pages/Checkout.razor.css
new file mode 100644
index 0000000..0db0b33
--- /dev/null
+++ b/MidrandBookshop/Components/Pages/Checkout.razor.css
@@ -0,0 +1,347 @@
+/* ==========================================================================
+ Midrand Books — Checkout Layout Polish & Tightening
+ ========================================================================== */
+
+/* --- 🛠️ 1. Global Page Wrapper Boundary Constraints --- */
+.checkout-page-container {
+ max-width: 1140px;
+ margin: 0 auto;
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+}
+
+/* --- 2. Page Typography & Headers --- */
+.checkout-header {
+ margin-bottom: 2.5rem !important;
+}
+
+.checkout-main-title {
+ font-size: 2.25rem;
+ letter-spacing: -0.03em;
+ color: #111111;
+}
+
+.tracking-wider {
+ letter-spacing: 0.12em;
+}
+
+/* --- 3. Custom Structural Content Panels --- */
+.checkout-section-panel {
+ background-color: #FFFFFF;
+ border: 1px solid rgba(0, 0, 0, 0.06);
+ border-radius: 12px;
+ padding: 2rem;
+}
+
+.panel-title {
+ font-size: 1.1rem;
+ letter-spacing: -0.01em;
+ color: #111111;
+}
+
+/* --- 🛠️ 4. Items Manifest Row & Stepper Controls --- */
+.checkout-items-stack .checkout-item-row {
+ display: flex;
+ align-items: center; /* Locks elements on a clean vertical axis line */
+ justify-content: space-between;
+ gap: 2rem;
+ border-bottom: 1px dashed rgba(0, 0, 0, 0.08);
+}
+
+ .checkout-items-stack .checkout-item-row:last-child {
+ border-bottom: none;
+ }
+
+.item-meta-details {
+ max-width: 65%; /* Brounds long item titles elegantly */
+}
+
+.item-product-name {
+ font-size: 0.95rem;
+ line-height: 1.4;
+ color: #1A1A1A;
+}
+
+.premium-quantity-stepper {
+ display: inline-flex;
+ align-items: center;
+ background-color: #F8F9FA;
+ border: 1px solid rgba(0, 0, 0, 0.08);
+ border-radius: 30px;
+ padding: 3px;
+}
+
+ .premium-quantity-stepper .step-btn {
+ background: none;
+ border: none;
+ width: 28px;
+ height: 28px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1rem;
+ color: #555555;
+ border-radius: 50%;
+ transition: background-color 0.2s ease, color 0.2s ease;
+ }
+
+ .premium-quantity-stepper .step-btn:hover {
+ background-color: #FFFFFF;
+ color: #000000;
+ }
+
+ .premium-quantity-stepper .step-value {
+ min-width: 28px;
+ text-align: center;
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: #111111;
+ }
+
+.btn-clean-remove {
+ background: none;
+ border: none;
+ color: #DC3545;
+ font-size: 0.72rem;
+ font-weight: 600;
+ letter-spacing: 0.05em;
+ padding: 4px 8px;
+ border-radius: 4px;
+ transition: background-color 0.2s ease;
+}
+
+ .btn-clean-remove:hover {
+ background-color: rgba(220, 53, 69, 0.06);
+ }
+
+/* --- 5. Interactive Selectable Radio Selection Cards --- */
+.visual-hidden {
+ position: absolute;
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.premium-selectable-card {
+ display: flex;
+ align-items: center;
+ border: 1px solid rgba(0, 0, 0, 0.08);
+ border-radius: 10px;
+ padding: 1.25rem;
+ cursor: pointer;
+ transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
+ background-color: #FFFFFF;
+}
+
+ .premium-selectable-card:hover {
+ border-color: #111111;
+ background-color: #FAFBFB;
+ }
+
+ .premium-selectable-card.active {
+ border-color: #000000;
+ box-shadow: inset 0 0 0 1px #000000;
+ background-color: #FFFFFF;
+ }
+
+.card-indicator-circle {
+ width: 16px;
+ height: 16px;
+ border: 1px solid rgba(0, 0, 0, 0.25);
+ border-radius: 50%;
+ margin-right: 1.25rem;
+ position: relative;
+ transition: all 0.2s ease;
+ flex-shrink: 0;
+}
+
+.premium-selectable-card.active .card-indicator-circle {
+ border-color: #000000;
+ background-color: #000000;
+}
+
+ .premium-selectable-card.active .card-indicator-circle::after {
+ content: '';
+ position: absolute;
+ width: 6px;
+ height: 6px;
+ background-color: #FFFFFF;
+ border-radius: 50%;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+.card-label-title {
+ font-size: 0.9rem;
+ line-height: 1.3;
+}
+
+.card-label-desc {
+ font-size: 0.78rem !important;
+}
+
+.card-price-tag {
+ font-size: 0.85rem;
+ letter-spacing: -0.01em;
+}
+
+/* --- 6. Form Checkbox Options --- */
+.context-clickable {
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
+ .context-clickable:hover {
+ background-color: #FAFBFB;
+ }
+
+.custom-box-tick {
+ cursor: pointer;
+}
+
+/* --- 🛠️ 7. Sticky Right Sidebar Order Ledger --- */
+.sticky-summary-card {
+ background-color: #FFFFFF;
+ border: 1px solid rgba(0, 0, 0, 0.08) !important;
+ border-radius: 12px;
+ position: sticky;
+ top: 120px; /* Safe breathing room underneath the global nav header */
+ box-shadow: 0 12px 34px -10px rgba(0, 0, 0, 0.03);
+}
+
+.small-summary-heading {
+ font-size: 0.78rem;
+ color: #666666;
+}
+
+.price-summary-ledger .ledger-row {
+ padding: 0.25rem 0;
+}
+
+.summary-divider {
+ border-color: rgba(0, 0, 0, 0.06);
+ opacity: 1;
+}
+
+.extra-small {
+ font-size: 0.68rem;
+}
+
+/* --- 8. Core Checkout Action Button --- */
+.btn-premium-action {
+ background-color: #111111;
+ color: #FFFFFF;
+ border: none;
+ border-radius: 50px;
+ font-weight: 600;
+ font-size: 0.95rem;
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+ .btn-premium-action:hover:not(:disabled) {
+ background-color: #222222;
+ transform: translateY(-1px);
+ box-shadow: 0 8px 20px -6px rgba(0, 0, 0, 0.15);
+ }
+
+ .btn-premium-action:disabled {
+ background-color: #CCCCCC;
+ color: #888888;
+ cursor: not-allowed;
+ }
+
+/* ==========================================================================
+ Full-Screen Handover Processing Overlay
+ ========================================================================== */
+.processing-screen-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background-color: #FFFFFF;
+ z-index: 3000;
+}
+
+.processing-card-box {
+ max-width: 420px;
+ padding: 2rem;
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1.5rem;
+}
+
+.payment-vector-loader {
+ width: 48px;
+ height: 48px;
+}
+
+.loader-track {
+ stroke: rgba(0, 0, 0, 0.06);
+ stroke-width: 2.5;
+ fill: none;
+}
+
+.loader-handshake-ring {
+ stroke: #111111;
+ stroke-width: 2.5;
+ stroke-linecap: round;
+ fill: none;
+ transform-origin: center;
+ animation: spinHandoverLoop 1s linear infinite;
+}
+
+.secure-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ color: #666666;
+ opacity: 0.6;
+ border: 1px solid rgba(0, 0, 0, 0.08);
+ padding: 0.35rem 0.85rem;
+ border-radius: 50px;
+ font-size: 0.75rem;
+ font-weight: 500;
+}
+
+@keyframes spinHandoverLoop {
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+/* --- 9. Premium Plaintext Field & Textarea Structure --- */
+.premium-textarea-wrapper {
+ position: relative;
+}
+
+.premium-plaintext-field {
+ background-color: #FFFFFF;
+ border: 1px solid rgba(0, 0, 0, 0.08);
+ border-radius: 8px;
+ padding: 0.85rem 1rem;
+ font-size: 0.9rem;
+ line-height: 1.5;
+ color: #1A1A1A;
+ resize: none; /* Disables ugly drag handles to maintain design proportions */
+ transition: all 0.2s ease;
+}
+
+ .premium-plaintext-field:focus {
+ background-color: #FFFFFF;
+ border-color: #000000;
+ box-shadow: none; /* Strip standard blue Bootstrap glowing halos */
+ outline: none;
+ }
+
+ .premium-plaintext-field::placeholder {
+ color: #A0A0A0;
+ font-size: 0.88rem;
+ }
\ No newline at end of file