8d2efbeb4a
continuous-integration/drone/pr Build is passing
Redesigned account, checkout Added stock management design elements
240 lines
20 KiB
Plaintext
240 lines
20 KiB
Plaintext
@page "/account"
|
|
@using Microsoft.AspNetCore.Components.Authorization
|
|
@inject NavigationManager Navigation
|
|
@rendermode InteractiveServer
|
|
@attribute [Authorize]
|
|
|
|
<div class="account-page-container py-5">
|
|
<header class="account-header mb-5">
|
|
<span class="text-uppercase font-monospace text-muted tracking-wider small d-block mb-1">Customer Dashboard</span>
|
|
<h1 class="account-main-title fw-bold">My Account</h1>
|
|
</header>
|
|
|
|
<div class="row g-5">
|
|
<div class="col-lg-3">
|
|
<div class="nav flex-column account-nav-stack gap-1" role="tablist">
|
|
<button class="nav-link active text-start d-flex align-items-center gap-2" data-bs-toggle="pill" data-bs-target="#orders" role="tab">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path><line x1="3" y1="6" x2="21" y2="6"></line><path d="M16 10a4 4 0 0 1-8 0"></path></svg>
|
|
<span>Order History</span>
|
|
</button>
|
|
<button class="nav-link text-start d-flex align-items-center gap-2" data-bs-toggle="pill" data-bs-target="#shipping" role="tab">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
|
|
<span>Shipping Address</span>
|
|
</button>
|
|
<button class="nav-link text-start d-flex align-items-center gap-2" data-bs-toggle="pill" data-bs-target="#profile" role="tab">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
|
|
<span>Profile Settings</span>
|
|
</button>
|
|
<hr class="my-3 opacity-10" />
|
|
<button class="nav-link nav-logout text-danger text-start d-flex align-items-center gap-2" @onclick="TriggerLogout">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
|
|
<span>Logout Account</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-9">
|
|
<AuthorizeView>
|
|
<Authorized>
|
|
<div class="tab-content account-panels-deck">
|
|
|
|
<div class="tab-pane fade show active" id="orders" role="tabpanel">
|
|
<div class="panel-card-wrapper mb-4">
|
|
<h5 class="panel-section-title fw-bold text-dark font-monospace text-uppercase tracking-wider mb-4">Order History</h5>
|
|
|
|
@if (orderHistory == null || !orderHistory.Any())
|
|
{
|
|
<div class="text-center py-5 border rounded-3 bg-light bg-opacity-50">
|
|
<p class="text-muted small mb-0">You haven't placed any orders with us yet.</p>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="orders-stack d-flex flex-column gap-4">
|
|
@foreach (var order in orderHistory)
|
|
{
|
|
<div class="premium-order-card p-4 border rounded-3 bg-white shadow-sm">
|
|
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3 pb-3 border-b-dashed mb-3">
|
|
<div>
|
|
<span class="font-monospace text-dark fw-bold d-block h6 mb-1">@order.OrderId</span>
|
|
<small class="text-muted d-block mb-1">Ordered on @order.OrderDate.ToString("dd MMMM yyyy")</small>
|
|
<small class="text-secondary d-flex align-items-center gap-1" style="font-size: 0.8rem;">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
|
|
<span>Shipped to: <span class="fw-semibold text-dark">@order.ShippingAddressName</span></span>
|
|
</small>
|
|
</div>
|
|
<div class="text-md-end d-flex flex-column align-items-md-end gap-2">
|
|
<div class="d-flex align-items-center gap-1.5 flex-wrap justify-content-md-end">
|
|
<span class="badge status-badge-base @GetPaymentStatusClass(order.PaymentStatus)">
|
|
Pay: @order.PaymentStatus
|
|
</span>
|
|
<span class="badge status-badge-base @GetStatusClass(order.Status)">
|
|
Logistics: @order.Status
|
|
</span>
|
|
</div>
|
|
|
|
@if (order.PaymentStatus?.ToLower() == "paid")
|
|
{
|
|
<button class="btn btn-outline-dark btn-premium-sm font-monospace text-uppercase d-inline-flex align-items-center gap-1.5 py-1 px-2.5"
|
|
style="font-size: 0.7rem;"
|
|
@onclick="() => DownloadInvoice(order.OrderId)">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
|
<span>Invoice</span>
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="order-manifest-details d-flex align-items-center justify-content-between gap-4 py-1">
|
|
<div class="item-meta">
|
|
<h6 class="text-dark fw-bold mb-0 small" style="line-height: 1.4;">@order.ProductTitle</h6>
|
|
</div>
|
|
<div class="item-value text-end flex-shrink-0">
|
|
<span class="font-monospace text-dark fw-bold d-block">R @order.Total.ToString("F2")</span>
|
|
<small class="text-muted extra-small font-monospace">VAT Inclusive</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="shipping" role="tabpanel">
|
|
<div class="panel-card-wrapper mb-4">
|
|
<div class="d-flex justify-content-between align-items-baseline mb-4">
|
|
<h5 class="panel-section-title fw-bold text-dark font-monospace text-uppercase tracking-wider mb-0">Saved Addresses</h5>
|
|
@if (!showAddForm && editingAddress == null)
|
|
{
|
|
<button class="btn btn-outline-dark btn-premium-sm font-monospace text-uppercase" @onclick="() => showAddForm = true">
|
|
Add New Address
|
|
</button>
|
|
}
|
|
</div>
|
|
|
|
@if (showAddForm || editingAddress != null)
|
|
{
|
|
<div class="premium-interactive-form p-4 border rounded-3 mb-4 bg-light bg-opacity-20 animate-fade-in">
|
|
<h6 class="fw-bold text-dark mb-3">@(editingAddress != null ? "Modify Curated Address" : "Register Destination Address")</h6>
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label extra-small font-monospace text-uppercase text-muted">Address Name Label</label>
|
|
<input type="text" class="form-control premium-plaintext-field" placeholder="e.g., Home" @bind="newAddressName" />
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label extra-small font-monospace text-uppercase text-muted">Postal Routing Code</label>
|
|
<input type="text" class="form-control premium-plaintext-field" placeholder="e.g., 1685" @bind="newPostalCode" />
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label extra-small font-monospace text-uppercase text-muted">Street Address Lines</label>
|
|
<input type="text" class="form-control premium-plaintext-field" placeholder="e.g., 12 Main Road" @bind="newStreetAddress" />
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label extra-small font-monospace text-uppercase text-muted">City / Region</label>
|
|
<input type="text" class="form-control premium-plaintext-field" placeholder="e.g., Midrand" @bind="newCity" />
|
|
</div>
|
|
<div class="col-12 d-flex flex-wrap gap-4 py-2 border-y my-2 bg-white px-3 rounded border">
|
|
<div class="form-check d-flex align-items-center gap-2 m-0">
|
|
<input type="checkbox" class="form-check-input custom-box-tick m-0" id="isBillingCheck" @bind="isBilling" />
|
|
<label class="form-check-label context-clickable small fw-medium text-dark" for="isBillingCheck">Default Billing Endpoint</label>
|
|
</div>
|
|
<div class="form-check d-flex align-items-center gap-2 m-0">
|
|
<input type="checkbox" class="form-check-input custom-box-tick m-0" id="isShippingCheck" @bind="isShipping" />
|
|
<label class="form-check-label context-clickable small fw-medium text-dark" for="isShippingCheck">Default Fulfillment Endpoint</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 d-flex justify-content-end gap-2 mt-3">
|
|
<button class="btn btn-clean-cancel font-monospace text-uppercase small" @onclick="CancelAddressActions">Cancel</button>
|
|
<button class="btn btn-dark px-4 py-2 text-uppercase font-monospace small" @onclick="SaveAddress">Save Address Details</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="row g-4">
|
|
@foreach (var addr in savedAddresses)
|
|
{
|
|
<div class="col-md-6">
|
|
<div class="address-curated-card p-4 border rounded-3 position-relative d-flex flex-column h-100 bg-white @(addr.IsPrimary ? "border-dark shadow-sm" : "opacity-90")">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<span class="fw-bold text-dark font-monospace tracking-wide text-uppercase" style="font-size: 0.82rem;">@addr.Name</span>
|
|
<div class="form-check d-flex align-items-center gap-1.5 p-0 m-0">
|
|
<input type="radio" class="form-check-input custom-box-tick m-0" name="primaryAddr" id="@($"primary-{addr.Id}")" checked="@addr.IsPrimary" @onchange="(e) => SetPrimary(addr, e)" />
|
|
<label class="form-check-label extra-small text-muted font-monospace text-uppercase context-clickable ms-1" for="@($"primary-{addr.Id}")">Primary</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="address-body-text text-muted mb-4 mt-1 flex-grow-1" style="font-size: 0.88rem; line-height: 1.6;">
|
|
<span class="d-block text-dark fw-medium">@addr.Street</span>
|
|
<span class="d-block">@addr.City</span>
|
|
<span class="font-monospace text-secondary extra-small d-block mt-1">ZA-@addr.PostalCode</span>
|
|
</div>
|
|
|
|
<div class="address-metadata-badges d-flex flex-wrap gap-1 mb-3">
|
|
@if (addr.IsBilling)
|
|
{
|
|
<span class="badge bg-light text-secondary font-monospace tracking-wide border text-uppercase extra-small px-2 py-1">Billing</span>
|
|
}
|
|
@if (addr.IsShipping)
|
|
{
|
|
<span class="badge bg-light text-dark font-monospace tracking-wide border border-secondary text-uppercase extra-small px-2 py-1">Shipping</span>
|
|
}
|
|
</div>
|
|
|
|
<div class="address-actions-row border-top-dashed pt-3 d-flex gap-3 justify-content-end mt-auto">
|
|
<button class="btn-action-trigger text-uppercase font-monospace extra-small text-muted border-0 bg-transparent" @onclick="() => EditAddress(addr)">Edit</button>
|
|
<button class="btn-action-trigger text-uppercase font-monospace extra-small text-danger border-0 bg-transparent" @onclick="() => DeleteAddress(addr)">Delete</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="profile" role="tabpanel">
|
|
<div class="panel-card-wrapper mb-4">
|
|
<h5 class="panel-section-title fw-bold text-dark font-monospace text-uppercase tracking-wider mb-4">Profile Settings</h5>
|
|
|
|
<div class="profile-hero-banner mb-4 d-flex align-items-center justify-content-between p-4 border rounded-3 bg-light bg-opacity-20 flex-wrap gap-3">
|
|
<div class="hero-text-content">
|
|
<div class="meta-tag font-monospace text-uppercase text-muted extra-small tracking-wider mb-1">Active Identity</div>
|
|
<h5 class="fw-bold text-dark mb-1 h6">@User?.Identity?.Name</h5>
|
|
<p class="text-muted small mb-0 font-monospace extra-small opacity-75">Secure Connection Authorized</p>
|
|
</div>
|
|
<span class="badge rounded-pill bg-success bg-opacity-10 text-success border border-success border-opacity-20 font-monospace px-3 py-1.5 small text-uppercase tracking-wide">
|
|
Verified
|
|
</span>
|
|
</div>
|
|
|
|
<div class="card p-5 text-center bg-white border rounded-3 shadow-sm my-4">
|
|
<div class="mb-4 text-muted opacity-40">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="44" height="44" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
</svg>
|
|
</div>
|
|
<h5 class="fw-bold text-dark mb-2 h6">Centralized Identity Node Settings</h5>
|
|
<p class="text-muted small mx-auto mb-4" style="max-width: 480px; line-height: 1.5;">
|
|
For your structural protection, password alterations, account recovery preferences, cross-tenant factors, and core credential manifests are handled through our global Identity Node security layer.
|
|
</p>
|
|
|
|
<a href="https://sts.security.khongisa.co.za/Manage/Index?returnUrl=https://midrandbooks.co.za/account"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="btn btn-dark rounded-pill px-4 py-2.5 btn-sm font-monospace text-uppercase tracking-wider d-inline-flex align-items-center gap-2 mx-auto"
|
|
style="font-size: 0.75rem;">
|
|
<span>Access Central Security Center</span>
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</Authorized>
|
|
</AuthorizeView>
|
|
</div>
|
|
</div>
|
|
</div> |