Files
SkyArtShop/website/public/account.html
Local Server 2a2a3d99e5 webupdate
2026-01-18 02:22:05 -06:00

843 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Account | Sky Art Shop</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--color-bg-main: #ffebeb;
--color-bg-secondary: #ffd0d0;
--color-bg-promotion: #f6ccde;
--color-accent: #fcb1d8;
--color-text-main: #202023;
}
body {
font-family: "Poppins", -apple-system, BlinkMacSystemFont, sans-serif;
min-height: 100vh;
background: linear-gradient(180deg, #ffd0d0 0%, #ffebeb 100%);
position: relative;
}
/* Header */
.header {
background: var(--color-bg-secondary);
padding: 16px 32px;
box-shadow: 0 2px 12px rgba(252, 177, 216, 0.2);
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
text-decoration: none;
}
.logo img {
width: 45px;
height: 45px;
object-fit: contain;
}
.logo span {
color: var(--color-text-main);
font-size: 1.25rem;
font-weight: 700;
letter-spacing: -0.3px;
}
.header-actions {
display: flex;
align-items: center;
gap: 20px;
}
.nav-link {
color: var(--color-text-main);
text-decoration: none;
font-size: 0.95rem;
font-weight: 500;
padding: 8px 16px;
border-radius: 8px;
transition: all 0.2s;
}
.nav-link:hover {
background: rgba(255, 255, 255, 0.5);
}
.logout-btn {
background: white;
border: 2px solid #f0f0f0;
color: #ef4444;
padding: 10px 20px;
border-radius: 10px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
display: flex;
align-items: center;
gap: 6px;
}
.logout-btn:hover {
background: #fef2f2;
border-color: #ef4444;
}
/* Main Content */
.main-content {
max-width: 1200px;
margin: 0 auto;
padding: 40px 32px;
}
/* Welcome Section */
.welcome-section {
background: white;
border-radius: 24px;
padding: 36px 40px;
margin-bottom: 28px;
display: flex;
align-items: center;
gap: 24px;
box-shadow: 0 8px 32px rgba(252, 177, 216, 0.15);
}
.avatar {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #fcb1d8 0%, #f6ccde 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: var(--color-text-main);
font-weight: 700;
flex-shrink: 0;
}
.welcome-text h1 {
color: var(--color-text-main);
font-size: 1.6rem;
font-weight: 700;
margin-bottom: 6px;
}
.welcome-text p {
color: #666;
font-size: 0.95rem;
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 28px;
}
@media (max-width: 900px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 500px) {
.stats-grid {
grid-template-columns: 1fr;
}
}
.stat-card {
background: white;
border-radius: 20px;
padding: 24px;
text-align: center;
box-shadow: 0 4px 20px rgba(252, 177, 216, 0.12);
transition: all 0.3s;
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 32px rgba(252, 177, 216, 0.25);
}
.stat-icon {
width: 52px;
height: 52px;
background: linear-gradient(
135deg,
rgba(252, 177, 216, 0.3) 0%,
rgba(246, 204, 222, 0.3) 100%
);
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 14px;
font-size: 1.3rem;
color: #e85a9c;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: var(--color-text-main);
margin-bottom: 4px;
}
.stat-label {
color: #888;
font-size: 0.9rem;
font-weight: 500;
}
/* Content Grid */
.content-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 768px) {
.content-grid {
grid-template-columns: 1fr;
}
}
.content-card {
background: white;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(252, 177, 216, 0.12);
}
.card-header {
padding: 20px 24px;
border-bottom: 1px solid #f5f5f5;
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(
135deg,
rgba(252, 177, 216, 0.08) 0%,
rgba(246, 204, 222, 0.08) 100%
);
}
.card-header h3 {
color: var(--color-text-main);
font-size: 1.05rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 10px;
}
.card-header h3 i {
color: #e85a9c;
}
.view-all {
color: #e85a9c;
text-decoration: none;
font-size: 0.85rem;
font-weight: 600;
transition: color 0.2s;
}
.view-all:hover {
color: #d14485;
}
.card-content {
padding: 24px;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
}
.empty-state i {
font-size: 3rem;
margin-bottom: 14px;
opacity: 0.4;
color: var(--color-accent);
}
.empty-state p {
font-size: 0.95rem;
margin-bottom: 16px;
color: #666;
}
.empty-state .btn-browse {
display: inline-flex;
align-items: center;
gap: 8px;
background: linear-gradient(135deg, #fcb1d8 0%, #f6ccde 100%);
color: var(--color-text-main);
text-decoration: none;
padding: 12px 24px;
border-radius: 10px;
font-size: 0.9rem;
font-weight: 600;
transition: all 0.3s;
}
.empty-state .btn-browse:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(252, 177, 216, 0.4);
}
/* Item List */
.item-list {
display: flex;
flex-direction: column;
gap: 14px;
}
.item {
display: flex;
align-items: center;
gap: 14px;
padding: 14px;
background: #fafafa;
border-radius: 12px;
transition: all 0.2s;
}
.item:hover {
background: #f5f5f5;
}
.item-image {
width: 56px;
height: 56px;
background: linear-gradient(
135deg,
rgba(252, 177, 216, 0.2) 0%,
rgba(246, 204, 222, 0.2) 100%
);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.item-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.item-image i {
font-size: 1.4rem;
color: #e85a9c;
}
.item-details {
flex: 1;
}
.item-name {
color: var(--color-text-main);
font-size: 0.9rem;
font-weight: 600;
margin-bottom: 4px;
}
.item-price {
color: #e85a9c;
font-size: 0.85rem;
font-weight: 600;
}
.item-qty {
color: #888;
font-size: 0.8rem;
}
/* Profile Section */
.profile-card {
grid-column: 1 / -1;
}
.profile-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
@media (max-width: 600px) {
.profile-grid {
grid-template-columns: 1fr;
}
}
.profile-field {
display: flex;
flex-direction: column;
gap: 6px;
}
.profile-field label {
color: #888;
font-size: 0.85rem;
font-weight: 500;
}
.profile-field .value {
color: var(--color-text-main);
font-size: 1rem;
font-weight: 500;
}
.edit-profile-btn {
margin-top: 24px;
background: linear-gradient(135deg, #fcb1d8 0%, #f6ccde 100%);
color: var(--color-text-main);
border: none;
padding: 14px 28px;
border-radius: 12px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
font-family: inherit;
}
.edit-profile-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(252, 177, 216, 0.4);
}
/* Loading State */
.loading {
display: flex;
align-items: center;
justify-content: center;
min-height: 60vh;
}
.loading-spinner {
width: 48px;
height: 48px;
border: 3px solid rgba(252, 177, 216, 0.3);
border-top-color: var(--color-accent);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Toast */
.toast {
position: fixed;
top: 24px;
right: 24px;
padding: 16px 24px;
border-radius: 12px;
color: white;
font-weight: 500;
font-size: 0.95rem;
display: flex;
align-items: center;
gap: 12px;
transform: translateX(120%);
transition: transform 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
z-index: 1000;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.toast.show {
transform: translateX(0);
}
.toast.success {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
}
.toast.error {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
}
.toast.info {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
}
</style>
</head>
<body>
<!-- Toast -->
<div class="toast" id="toast">
<i class="bi bi-check-circle"></i>
<span id="toastMessage">Message</span>
</div>
<!-- Header -->
<header class="header">
<div class="header-content">
<a href="/" class="logo">
<img
src="/uploads/cat-logo-only-1766962993568-201212396.png"
alt="Sky Art Shop"
/>
<span>Sky Art Shop</span>
</a>
<div class="header-actions">
<a href="/" class="nav-link">Shop</a>
<a href="/portfolio" class="nav-link">Portfolio</a>
<button class="logout-btn" onclick="logout()">
<i class="bi bi-box-arrow-right"></i> Sign Out
</button>
</div>
</div>
</header>
<!-- Loading State -->
<div class="loading" id="loadingState">
<div class="loading-spinner"></div>
</div>
<!-- Main Content -->
<main class="main-content" id="mainContent" style="display: none">
<!-- Welcome Section -->
<section class="welcome-section">
<div class="avatar" id="userAvatar">J</div>
<div class="welcome-text">
<h1>Welcome back, <span id="userName">User</span>!</h1>
<p>Manage your account, view your cart, and track your orders</p>
</div>
</section>
<!-- Stats Grid -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">
<i class="bi bi-cart3"></i>
</div>
<div class="stat-value" id="cartCount">0</div>
<div class="stat-label">Cart Items</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="bi bi-heart"></i>
</div>
<div class="stat-value" id="wishlistCount">0</div>
<div class="stat-label">Wishlist</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="bi bi-box-seam"></i>
</div>
<div class="stat-value" id="ordersCount">0</div>
<div class="stat-label">Orders</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="bi bi-star"></i>
</div>
<div class="stat-value" id="reviewsCount">0</div>
<div class="stat-label">Reviews</div>
</div>
</div>
<!-- Content Grid -->
<div class="content-grid">
<!-- Cart -->
<div class="content-card">
<div class="card-header">
<h3><i class="bi bi-cart3"></i> My Cart</h3>
<a href="/cart" class="view-all">View All</a>
</div>
<div class="card-content" id="cartContent">
<div class="empty-state">
<i class="bi bi-cart3"></i>
<p>Your cart is empty</p>
<a href="/" class="btn-browse">
<i class="bi bi-shop"></i> Browse Shop
</a>
</div>
</div>
</div>
<!-- Wishlist -->
<div class="content-card">
<div class="card-header">
<h3><i class="bi bi-heart"></i> My Wishlist</h3>
<a href="/wishlist" class="view-all">View All</a>
</div>
<div class="card-content" id="wishlistContent">
<div class="empty-state">
<i class="bi bi-heart"></i>
<p>Your wishlist is empty</p>
<a href="/" class="btn-browse">
<i class="bi bi-search"></i> Discover Art
</a>
</div>
</div>
</div>
<!-- Profile -->
<div class="content-card profile-card">
<div class="card-header">
<h3><i class="bi bi-person"></i> Profile Information</h3>
</div>
<div class="card-content">
<div class="profile-grid">
<div class="profile-field">
<label>First Name</label>
<div class="value" id="profileFirstName">-</div>
</div>
<div class="profile-field">
<label>Last Name</label>
<div class="value" id="profileLastName">-</div>
</div>
<div class="profile-field">
<label>Email Address</label>
<div class="value" id="profileEmail">-</div>
</div>
<div class="profile-field">
<label>Member Since</label>
<div class="value" id="profileSince">-</div>
</div>
</div>
<button
class="edit-profile-btn"
onclick="showToast('Profile editing coming soon!', 'info')"
>
<i class="bi bi-pencil"></i> Edit Profile
</button>
</div>
</div>
</div>
</main>
<script>
let customer = null;
function showToast(message, type = "success") {
const toast = document.getElementById("toast");
const toastMessage = document.getElementById("toastMessage");
const icon = toast.querySelector("i");
toastMessage.textContent = message;
toast.className = "toast " + type;
icon.className = "bi";
if (type === "success") icon.classList.add("bi-check-circle");
else if (type === "error") icon.classList.add("bi-exclamation-circle");
else if (type === "info") icon.classList.add("bi-info-circle");
toast.classList.add("show");
setTimeout(() => toast.classList.remove("show"), 4000);
}
function logout() {
localStorage.removeItem("customer");
fetch("/api/customers/logout", {
method: "POST",
credentials: "include",
}).finally(() => {
window.location.href = "/signin";
});
}
async function loadAccountData() {
const stored = localStorage.getItem("customer");
if (!stored) {
window.location.href = "/signin";
return;
}
try {
customer = JSON.parse(stored);
} catch (e) {
window.location.href = "/signin";
return;
}
document.getElementById("userName").textContent =
customer.firstName || "User";
document.getElementById("userAvatar").textContent = (
customer.firstName || "U"
)
.charAt(0)
.toUpperCase();
document.getElementById("profileFirstName").textContent =
customer.firstName || "-";
document.getElementById("profileLastName").textContent =
customer.lastName || "-";
document.getElementById("profileEmail").textContent =
customer.email || "-";
if (customer.createdAt) {
const date = new Date(customer.createdAt);
document.getElementById("profileSince").textContent =
date.toLocaleDateString("en-US", {
month: "long",
year: "numeric",
});
}
try {
const [cartRes, wishlistRes] = await Promise.all([
fetch("/api/customers/cart/count", { credentials: "include" }),
fetch("/api/customers/wishlist/count", { credentials: "include" }),
]);
if (cartRes.ok) {
const cartData = await cartRes.json();
document.getElementById("cartCount").textContent =
cartData.count || 0;
}
if (wishlistRes.ok) {
const wishlistData = await wishlistRes.json();
document.getElementById("wishlistCount").textContent =
wishlistData.count || 0;
}
} catch (e) {
console.error("Error loading counts:", e);
}
try {
const cartItemsRes = await fetch("/api/customers/cart", {
credentials: "include",
});
if (cartItemsRes.ok) {
const cartItems = await cartItemsRes.json();
if (cartItems.items && cartItems.items.length > 0) {
renderCartItems(cartItems.items.slice(0, 3));
}
}
} catch (e) {
console.error("Error loading cart items:", e);
}
try {
const wishlistRes = await fetch("/api/customers/wishlist", {
credentials: "include",
});
if (wishlistRes.ok) {
const wishlistItems = await wishlistRes.json();
if (wishlistItems.items && wishlistItems.items.length > 0) {
renderWishlistItems(wishlistItems.items.slice(0, 3));
}
}
} catch (e) {
console.error("Error loading wishlist:", e);
}
document.getElementById("loadingState").style.display = "none";
document.getElementById("mainContent").style.display = "block";
}
function renderCartItems(items) {
const container = document.getElementById("cartContent");
if (!items || items.length === 0) return;
container.innerHTML = `
<div class="item-list">
${items
.map(
(item) => `
<div class="item">
<div class="item-image">
${
item.product_image
? `<img src="${item.product_image}" alt="${item.product_name}">`
: `<i class="bi bi-image"></i>`
}
</div>
<div class="item-details">
<div class="item-name">${item.product_name || "Product"}</div>
<div class="item-price">$${parseFloat(item.price || 0).toFixed(
2
)}</div>
<div class="item-qty">Qty: ${item.quantity || 1}</div>
</div>
</div>
`
)
.join("")}
</div>
`;
}
function renderWishlistItems(items) {
const container = document.getElementById("wishlistContent");
if (!items || items.length === 0) return;
container.innerHTML = `
<div class="item-list">
${items
.map(
(item) => `
<div class="item">
<div class="item-image">
${
item.product_image
? `<img src="${item.product_image}" alt="${item.product_name}">`
: `<i class="bi bi-image"></i>`
}
</div>
<div class="item-details">
<div class="item-name">${item.product_name || "Product"}</div>
<div class="item-price">$${parseFloat(item.price || 0).toFixed(
2
)}</div>
</div>
</div>
`
)
.join("")}
</div>
`;
}
document.addEventListener("DOMContentLoaded", loadAccountData);
</script>
</body>
</html>