Add website file management workflow and deployment script
This commit is contained in:
362
website/public/shop.html
Normal file
362
website/public/shop.html
Normal file
@@ -0,0 +1,362 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Shop all products - Sky Art Shop" />
|
||||
<title>Shop - Sky Art Shop</title>
|
||||
<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=Roboto:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="/assets/css/main.css" />
|
||||
<link rel="stylesheet" href="/assets/css/navbar.css" />
|
||||
<link rel="stylesheet" href="/assets/css/shopping.css" />
|
||||
</head>
|
||||
<body> <!-- Modern Navigation -->
|
||||
<nav class="modern-navbar">
|
||||
<div class="navbar-wrapper">
|
||||
<div class="navbar-brand">
|
||||
<a href="/home.html" class="brand-link">
|
||||
<img src="/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" alt="Sky Art Shop Logo" class="brand-logo" />
|
||||
<span class="brand-name">Sky Art Shop</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<ul class="nav-menu-list">
|
||||
<li class="nav-item"><a href="/home.html" class="nav-link">Home</a></li>
|
||||
<li class="nav-item"><a href="/shop.html" class="nav-link">Shop</a></li>
|
||||
<li class="nav-item"><a href="/portfolio.html" class="nav-link">Portfolio</a></li>
|
||||
<li class="nav-item"><a href="/about.html" class="nav-link">About</a></li>
|
||||
<li class="nav-item"><a href="/blog.html" class="nav-link">Blog</a></li>
|
||||
<li class="nav-item"><a href="/contact.html" class="nav-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="navbar-actions">
|
||||
<div class="action-item wishlist-dropdown-wrapper">
|
||||
<button class="action-btn" id="wishlistToggle" aria-label="Wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
<span class="action-badge" id="wishlistCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown wishlist-dropdown" id="wishlistPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>My Wishlist</h3>
|
||||
<button class="dropdown-close" id="wishlistClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="wishlistContent">
|
||||
<p class="empty-state">Your wishlist is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<a href="/shop.html" class="btn-outline">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-item cart-dropdown-wrapper">
|
||||
<button class="action-btn" id="cartToggle" aria-label="Shopping Cart">
|
||||
<i class="bi bi-cart3"></i>
|
||||
<span class="action-badge" id="cartCount">0</span>
|
||||
</button>
|
||||
<div class="action-dropdown cart-dropdown" id="cartPanel">
|
||||
<div class="dropdown-head">
|
||||
<h3>Shopping Cart</h3>
|
||||
<button class="dropdown-close" id="cartClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<div class="dropdown-body" id="cartContent">
|
||||
<p class="empty-state">Your cart is empty</p>
|
||||
</div>
|
||||
<div class="dropdown-foot">
|
||||
<div class="cart-summary">
|
||||
<span class="summary-label">Subtotal:</span>
|
||||
<span class="summary-value" id="cartSubtotal">$0.00</span>
|
||||
</div>
|
||||
<a href="/checkout.html" class="btn-primary-full">Proceed to Checkout</a>
|
||||
<a href="/shop.html" class="btn-text">Continue Shopping</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="mobile-toggle" id="mobileMenuToggle" aria-label="Menu">
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<div class="mobile-menu-header">
|
||||
<span class="mobile-brand">Sky Art Shop</span>
|
||||
<button class="mobile-close" id="mobileMenuClose"><i class="bi bi-x-lg"></i></button>
|
||||
</div>
|
||||
<ul class="mobile-menu-list">
|
||||
<li><a href="/home.html" class="mobile-link">Home</a></li>
|
||||
<li><a href="/shop.html" class="mobile-link">Shop</a></li>
|
||||
<li><a href="/portfolio.html" class="mobile-link">Portfolio</a></li>
|
||||
<li><a href="/about.html" class="mobile-link">About</a></li>
|
||||
<li><a href="/blog.html" class="mobile-link">Blog</a></li>
|
||||
<li><a href="/contact.html" class="mobile-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Shop Hero -->
|
||||
<section class="shop-hero">
|
||||
<div class="container">
|
||||
<h1>Shop All Products</h1>
|
||||
<p class="hero-subtitle">
|
||||
Find everything you need for your creative projects
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Shop Filters -->
|
||||
<section class="shop-filters">
|
||||
<div class="container">
|
||||
<div class="filter-bar">
|
||||
<div class="filter-group">
|
||||
<label for="category-filter">Category:</label>
|
||||
<select id="category-filter">
|
||||
<option value="">All Products</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label for="sort-filter">Sort By:</label>
|
||||
<select id="sort-filter">
|
||||
<option value="name">Name</option>
|
||||
<option value="price-low">Price: Low to High</option>
|
||||
<option value="price-high">Price: High to Low</option>
|
||||
<option value="newest">Newest First</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Products Grid -->
|
||||
<section class="shop-products">
|
||||
<div class="container">
|
||||
<div id="loadingMessage" style="text-align: center; padding: 40px">
|
||||
<p>Loading products...</p>
|
||||
</div>
|
||||
<div class="products-grid" id="productsGrid"></div>
|
||||
<div
|
||||
id="noProducts"
|
||||
style="display: none; text-align: center; padding: 40px"
|
||||
>
|
||||
<p>No products found.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-brand">
|
||||
<h2>Sky Art Shop</h2>
|
||||
<p>Follow Us</p>
|
||||
<div class="social-links">
|
||||
<a href="#instagram" aria-label="Instagram">
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<h3>Quick Links</h3>
|
||||
<ul>
|
||||
<li><a href="/shop.html">Shop</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="/contact.html">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 by Sky Art Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/cart.js"></script>
|
||||
<script>
|
||||
let allProducts = [];
|
||||
let categories = [];
|
||||
|
||||
// Load products from API
|
||||
async function loadProducts() {
|
||||
try {
|
||||
const response = await fetch("/api/products");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
allProducts = data.products || [];
|
||||
|
||||
// Extract unique categories
|
||||
categories = [
|
||||
...new Set(allProducts.map((p) => p.category).filter((c) => c)),
|
||||
];
|
||||
|
||||
// Populate category filter
|
||||
const categoryFilter = document.getElementById("category-filter");
|
||||
categories.forEach((cat) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = cat;
|
||||
option.textContent = cat;
|
||||
categoryFilter.appendChild(option);
|
||||
});
|
||||
|
||||
// Display products
|
||||
displayProducts(allProducts);
|
||||
} else {
|
||||
document.getElementById("loadingMessage").style.display = "none";
|
||||
document.getElementById("noProducts").style.display = "block";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading products:", error);
|
||||
document.getElementById("loadingMessage").innerHTML =
|
||||
"<p>Error loading products. Please try again later.</p>";
|
||||
}
|
||||
}
|
||||
|
||||
// Display products
|
||||
function displayProducts(products) {
|
||||
const grid = document.getElementById("productsGrid");
|
||||
const loading = document.getElementById("loadingMessage");
|
||||
const noProducts = document.getElementById("noProducts");
|
||||
|
||||
loading.style.display = "none";
|
||||
|
||||
if (products.length === 0) {
|
||||
grid.innerHTML = "";
|
||||
noProducts.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
noProducts.style.display = "none";
|
||||
|
||||
grid.innerHTML = products
|
||||
.map((product) => {
|
||||
const imageUrl =
|
||||
product.imageurl ||
|
||||
product.images?.[0] ||
|
||||
"/assets/images/placeholder.jpg";
|
||||
const price = parseFloat(product.price || 0).toFixed(2);
|
||||
|
||||
return `
|
||||
<div class="product-card">
|
||||
<a href="/product.html?id=${
|
||||
product.id
|
||||
}" class="product-link">
|
||||
<div class="product-image">
|
||||
<img src="${imageUrl}" alt="${
|
||||
product.name
|
||||
}" loading="lazy" onerror="this.src='/assets/images/placeholder.jpg'" />
|
||||
</div>
|
||||
<h3>${product.name}</h3>
|
||||
${
|
||||
product.color
|
||||
? `<span class="product-color-badge">${product.color}</span>`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
product.shortdescription || product.description
|
||||
? `<div class="product-description">${
|
||||
product.shortdescription ||
|
||||
product.description.substring(0, 100) +
|
||||
"..."
|
||||
}</div>`
|
||||
: ""
|
||||
}
|
||||
<p class="price">$${price}</p>
|
||||
</a>
|
||||
<div style="display: flex; gap: 0.5rem; margin-top: 0.5rem;">
|
||||
<button class="btn btn-small btn-icon"
|
||||
onclick="addToWishlist('${
|
||||
product.id
|
||||
}', '${product.name.replace(/'/g, "\\'")}', ${
|
||||
product.price
|
||||
}, '${imageUrl}')"
|
||||
aria-label="Add to wishlist">
|
||||
<i class="bi bi-heart"></i>
|
||||
</button>
|
||||
<button class="btn btn-small btn-icon"
|
||||
onclick="addToCart('${
|
||||
product.id
|
||||
}', '${product.name.replace(/'/g, "\\'")}', ${
|
||||
product.price
|
||||
}, '${imageUrl}')"
|
||||
aria-label="Add to cart">
|
||||
<i class="bi bi-cart-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
// Filter and sort products
|
||||
function filterAndSortProducts() {
|
||||
const categoryFilter = document.getElementById("category-filter").value;
|
||||
const sortFilter = document.getElementById("sort-filter").value;
|
||||
|
||||
let filtered = allProducts;
|
||||
|
||||
// Apply category filter
|
||||
if (categoryFilter) {
|
||||
filtered = filtered.filter((p) => p.category === categoryFilter);
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
filtered = [...filtered]; // Create copy
|
||||
switch (sortFilter) {
|
||||
case "name":
|
||||
filtered.sort((a, b) => a.name.localeCompare(b.name));
|
||||
break;
|
||||
case "price-low":
|
||||
filtered.sort((a, b) => parseFloat(a.price) - parseFloat(b.price));
|
||||
break;
|
||||
case "price-high":
|
||||
filtered.sort((a, b) => parseFloat(b.price) - parseFloat(a.price));
|
||||
break;
|
||||
case "newest":
|
||||
filtered.sort(
|
||||
(a, b) => new Date(b.createdat) - new Date(a.createdat)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
displayProducts(filtered);
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document
|
||||
.getElementById("category-filter")
|
||||
.addEventListener("change", filterAndSortProducts);
|
||||
document
|
||||
.getElementById("sort-filter")
|
||||
.addEventListener("change", filterAndSortProducts);
|
||||
|
||||
// Initialize
|
||||
loadProducts();
|
||||
</script>
|
||||
<script src="/assets/js/shopping.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user