|
|
| const API_BASE_URL = 'https://bdwewiveuzpakrzphljo.supabase.co/functions/v1';
|
| let API_KEY = '';
|
| let cart = [];
|
| let currentPage = 1;
|
| let products = [];
|
| let orders = [];
|
|
|
|
|
| function showPage(pageId) {
|
| document.querySelectorAll('.page').forEach(page => page.classList.remove('active'));
|
| document.getElementById(pageId).classList.add('active');
|
|
|
| if (pageId === 'customer-page') {
|
| loadProducts();
|
| loadCategories();
|
| } else if (pageId === 'admin-page') {
|
| loadAdminProducts();
|
| loadAdminOrders();
|
| }
|
| }
|
|
|
|
|
| function adminLogin(event) {
|
| event.preventDefault();
|
| const apiKey = document.getElementById('api-key-input').value;
|
|
|
| if (apiKey) {
|
| API_KEY = apiKey;
|
| showPage('admin-page');
|
| } else {
|
| alert('Please enter a valid API key');
|
| }
|
| }
|
|
|
| function logout() {
|
| API_KEY = '';
|
| showPage('landing-page');
|
| }
|
|
|
|
|
| async function apiRequest(endpoint, method = 'GET', body = null) {
|
| const options = {
|
| method,
|
| headers: {
|
| 'Content-Type': 'application/json'
|
| }
|
| };
|
|
|
| if (API_KEY) {
|
| options.headers['x-api-key'] = API_KEY;
|
| }
|
|
|
| if (body) {
|
| options.body = JSON.stringify(body);
|
| }
|
|
|
| try {
|
| const response = await fetch(`${API_BASE_URL}${endpoint}`, options);
|
| if (!response.ok) {
|
| throw new Error(`API Error: ${response.statusText}`);
|
| }
|
| return await response.json();
|
| } catch (error) {
|
| console.error('API Request failed:', error);
|
| alert('Request failed: ' + error.message);
|
| throw error;
|
| }
|
| }
|
|
|
|
|
| async function loadProducts(page = 1) {
|
| try {
|
| const search = document.getElementById('product-search').value;
|
| const category = document.getElementById('category-filter').value;
|
|
|
| let endpoint = `/products?page=${page}&limit=12`;
|
| if (search) endpoint += `&search=${encodeURIComponent(search)}`;
|
| if (category) endpoint += `&category=${encodeURIComponent(category)}`;
|
|
|
| const data = await apiRequest(endpoint);
|
| products = data.data || [];
|
| currentPage = page;
|
|
|
| renderProducts(products);
|
| renderPagination(data.total, data.page, data.limit);
|
| } catch (error) {
|
| document.getElementById('products-grid').innerHTML =
|
| '<div class="empty-state">Failed to load products</div>';
|
| }
|
| }
|
|
|
| function renderProducts(products) {
|
| const grid = document.getElementById('products-grid');
|
|
|
| if (!products || products.length === 0) {
|
| grid.innerHTML = '<div class="empty-state"><div class="empty-state-icon">📦</div><p>No products found</p></div>';
|
| return;
|
| }
|
|
|
| grid.innerHTML = products.map(product => `
|
| <div class="product-card" onclick="showProductDetails('${product.id}')">
|
| <div class="product-image">
|
| ${product.variants?.[0]?.image_url ?
|
| `<img src="${product.variants[0].image_url}" alt="${product.name}">` :
|
| '👕'}
|
| </div>
|
| <div class="product-info">
|
| <h3 class="product-name">${product.name}</h3>
|
| <span class="product-category">${product.category || 'Uncategorized'}</span>
|
| <p class="product-description">${product.description || ''}</p>
|
| <div class="product-price">$${parseFloat(product.base_price).toFixed(2)}</div>
|
| ${product.variants?.length > 0 ? `<p class="mt-1">${product.variants.length} variants available</p>` : ''}
|
| </div>
|
| </div>
|
| `).join('');
|
| }
|
|
|
| function renderPagination(total, page, limit) {
|
| const pagination = document.getElementById('pagination');
|
| const totalPages = Math.ceil(total / limit);
|
|
|
| if (totalPages <= 1) {
|
| pagination.innerHTML = '';
|
| return;
|
| }
|
|
|
| let html = `<button onclick="loadProducts(${page - 1})" ${page <= 1 ? 'disabled' : ''}>Previous</button>`;
|
|
|
| for (let i = 1; i <= totalPages; i++) {
|
| if (i === 1 || i === totalPages || (i >= page - 1 && i <= page + 1)) {
|
| html += `<button onclick="loadProducts(${i})" ${i === page ? 'class="active"' : ''}>${i}</button>`;
|
| } else if (i === page - 2 || i === page + 2) {
|
| html += '<span>...</span>';
|
| }
|
| }
|
|
|
| html += `<button onclick="loadProducts(${page + 1})" ${page >= totalPages ? 'disabled' : ''}>Next</button>`;
|
| pagination.innerHTML = html;
|
| }
|
|
|
| async function loadCategories() {
|
|
|
| const categories = [...new Set(products.map(p => p.category).filter(Boolean))];
|
| const select = document.getElementById('category-filter');
|
| select.innerHTML = '<option value="">All Categories</option>' +
|
| categories.map(cat => `<option value="${cat}">${cat}</option>`).join('');
|
| }
|
|
|
| function searchProducts() {
|
| clearTimeout(window.searchTimeout);
|
| window.searchTimeout = setTimeout(() => loadProducts(1), 500);
|
| }
|
|
|
| function filterProducts() {
|
| loadProducts(1);
|
| }
|
|
|
| async function showProductDetails(productId) {
|
| try {
|
| const product = await apiRequest(`/products/${productId}`);
|
| const modal = document.getElementById('product-modal');
|
| const details = document.getElementById('product-details');
|
|
|
| details.innerHTML = `
|
| <h2>${product.name}</h2>
|
| <span class="product-category">${product.category || 'Uncategorized'}</span>
|
| <p class="mt-2">${product.description || 'No description'}</p>
|
| <div class="product-price mt-2">$${parseFloat(product.base_price).toFixed(2)}</div>
|
|
|
| ${product.variants?.length > 0 ? `
|
| <div class="product-variants mt-3">
|
| <h4>Select Variant:</h4>
|
| <div class="variant-options">
|
| ${product.variants.map(variant => `
|
| <button class="variant-btn ${variant.stock_quantity === 0 ? 'out-of-stock' : ''}"
|
| onclick="addToCart('${product.id}', '${variant.id}')"
|
| ${variant.stock_quantity === 0 ? 'disabled' : ''}>
|
| ${variant.size || ''} ${variant.color || ''}
|
| ${variant.price_override ? `($${parseFloat(variant.price_override).toFixed(2)})` : ''}
|
| ${variant.stock_quantity === 0 ? '(Out of Stock)' : `(${variant.stock_quantity} available)`}
|
| </button>
|
| `).join('')}
|
| </div>
|
| </div>
|
| ` : '<p class="mt-2">No variants available</p>'}
|
| `;
|
|
|
| modal.classList.add('active');
|
| } catch (error) {
|
| alert('Failed to load product details');
|
| }
|
| }
|
|
|
|
|
| function addToCart(productId, variantId) {
|
| const product = products.find(p => p.id === productId);
|
| if (!product) return;
|
|
|
| const variant = product.variants.find(v => v.id === variantId);
|
| if (!variant || variant.stock_quantity === 0) return;
|
|
|
| const existingItem = cart.find(item => item.variantId === variantId);
|
|
|
| if (existingItem) {
|
| if (existingItem.quantity < variant.stock_quantity) {
|
| existingItem.quantity++;
|
| } else {
|
| alert('Cannot add more than available stock');
|
| return;
|
| }
|
| } else {
|
| cart.push({
|
| productId: product.id,
|
| variantId: variant.id,
|
| productName: product.name,
|
| variantLabel: `${variant.size || ''} ${variant.color || ''}`.trim(),
|
| price: variant.price_override || product.base_price,
|
| quantity: 1,
|
| maxStock: variant.stock_quantity
|
| });
|
| }
|
|
|
| updateCartCount();
|
| closeModal('product-modal');
|
| alert('Added to cart!');
|
| }
|
|
|
| function updateCartCount() {
|
| const count = cart.reduce((sum, item) => sum + item.quantity, 0);
|
| document.getElementById('cart-count').textContent = count;
|
| }
|
|
|
| function showCart() {
|
| const modal = document.getElementById('cart-modal');
|
| const cartItems = document.getElementById('cart-items');
|
|
|
| if (cart.length === 0) {
|
| cartItems.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🛒</div><p>Your cart is empty</p></div>';
|
| } else {
|
| cartItems.innerHTML = '<div class="cart-items">' + cart.map((item, index) => `
|
| <div class="cart-item">
|
| <div class="cart-item-info">
|
| <div class="cart-item-name">${item.productName}</div>
|
| <div class="cart-item-variant">${item.variantLabel}</div>
|
| <div class="cart-item-quantity">
|
| <button class="quantity-btn" onclick="updateCartQuantity(${index}, -1)">-</button>
|
| <span>${item.quantity}</span>
|
| <button class="quantity-btn" onclick="updateCartQuantity(${index}, 1)">+</button>
|
| <button class="btn btn-danger" onclick="removeFromCart(${index})">Remove</button>
|
| </div>
|
| </div>
|
| <div class="cart-item-price">$${(item.price * item.quantity).toFixed(2)}</div>
|
| </div>
|
| `).join('') + '</div>';
|
| }
|
|
|
| const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
| document.getElementById('cart-subtotal').textContent = `$${subtotal.toFixed(2)}`;
|
|
|
| modal.classList.add('active');
|
| }
|
|
|
| function updateCartQuantity(index, delta) {
|
| const item = cart[index];
|
| const newQuantity = item.quantity + delta;
|
|
|
| if (newQuantity <= 0) {
|
| removeFromCart(index);
|
| } else if (newQuantity <= item.maxStock) {
|
| item.quantity = newQuantity;
|
| updateCartCount();
|
| showCart();
|
| } else {
|
| alert('Cannot add more than available stock');
|
| }
|
| }
|
|
|
| function removeFromCart(index) {
|
| cart.splice(index, 1);
|
| updateCartCount();
|
| showCart();
|
| }
|
|
|
|
|
| function showCheckout() {
|
| if (cart.length === 0) {
|
| alert('Your cart is empty');
|
| return;
|
| }
|
|
|
| closeModal('cart-modal');
|
| const modal = document.getElementById('checkout-modal');
|
|
|
| const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
| const shippingFee = 10;
|
| const total = subtotal + shippingFee;
|
|
|
| document.getElementById('checkout-subtotal').textContent = `$${subtotal.toFixed(2)}`;
|
| document.getElementById('checkout-total').textContent = `$${total.toFixed(2)}`;
|
|
|
| modal.classList.add('active');
|
| }
|
|
|
| async function placeOrder(event) {
|
| event.preventDefault();
|
|
|
| const orderData = {
|
| customer_name: document.getElementById('customer-name').value,
|
| customer_email: document.getElementById('customer-email').value,
|
| customer_phone: document.getElementById('customer-phone').value,
|
| shipping_address: {
|
| address: document.getElementById('shipping-address').value
|
| },
|
| shipping_fee: 10,
|
| notes: document.getElementById('order-notes').value,
|
| items: cart.map(item => ({
|
| variant_id: item.variantId,
|
| quantity: item.quantity
|
| }))
|
| };
|
|
|
| try {
|
| const result = await apiRequest('/orders', 'POST', orderData);
|
|
|
| alert(`Order placed successfully! Your tracking ID is: ${result.tracking_id || 'N/A'}`);
|
|
|
| cart = [];
|
| updateCartCount();
|
| closeModal('checkout-modal');
|
|
|
|
|
| document.getElementById('customer-name').value = '';
|
| document.getElementById('customer-email').value = '';
|
| document.getElementById('customer-phone').value = '';
|
| document.getElementById('shipping-address').value = '';
|
| document.getElementById('order-notes').value = '';
|
| } catch (error) {
|
| alert('Failed to place order: ' + error.message);
|
| }
|
| }
|
|
|
|
|
| async function trackOrder() {
|
| const trackingId = document.getElementById('tracking-input').value.trim();
|
|
|
| if (!trackingId) {
|
| alert('Please enter a tracking ID');
|
| return;
|
| }
|
|
|
| try {
|
| const data = await apiRequest(`/orders/track/${trackingId}`);
|
|
|
| const modal = document.getElementById('tracking-modal');
|
| const result = document.getElementById('tracking-result');
|
|
|
| result.innerHTML = `
|
| <div class="mt-2">
|
| <p><strong>Tracking ID:</strong> ${data.tracking_id}</p>
|
| <p><strong>Status:</strong> <span class="status-badge status-${data.status}">${data.status}</span></p>
|
| <p><strong>Customer:</strong> ${data.customer_name}</p>
|
| <p><strong>Order Date:</strong> ${new Date(data.created_at).toLocaleDateString()}</p>
|
| <h3 class="mt-3">Items:</h3>
|
| <ul>
|
| ${data.items_summary?.map(item => `<li>${item}</li>`).join('') || '<li>No items</li>'}
|
| </ul>
|
| </div>
|
| `;
|
|
|
| modal.classList.add('active');
|
| } catch (error) {
|
| alert('Order not found or tracking ID is invalid');
|
| }
|
| }
|
|
|
|
|
| async function loadAdminProducts() {
|
| try {
|
| const data = await apiRequest('/products?limit=100');
|
| products = data.data || [];
|
| renderAdminProducts(products);
|
| } catch (error) {
|
| document.getElementById('admin-products-list').innerHTML =
|
| '<div class="empty-state">Failed to load products</div>';
|
| }
|
| }
|
|
|
| function renderAdminProducts(products) {
|
| const list = document.getElementById('admin-products-list');
|
|
|
| if (!products || products.length === 0) {
|
| list.innerHTML = '<div class="empty-state"><div class="empty-state-icon">📦</div><p>No products found</p></div>';
|
| return;
|
| }
|
|
|
| list.innerHTML = products.map(product => `
|
| <div class="admin-item">
|
| <div class="admin-item-info">
|
| <div class="admin-item-title">${product.name}</div>
|
| <div class="admin-item-meta">
|
| ${product.category || 'Uncategorized'} • $${parseFloat(product.base_price).toFixed(2)} •
|
| ${product.variants?.length || 0} variants •
|
| ${product.is_active ? '✅ Active' : '❌ Inactive'}
|
| </div>
|
| </div>
|
| <div class="admin-item-actions">
|
| <button class="btn btn-primary" onclick="editProduct('${product.id}')">Edit</button>
|
| <button class="btn btn-secondary" onclick="manageVariants('${product.id}')">Variants</button>
|
| <button class="btn btn-danger" onclick="deleteProduct('${product.id}')">Delete</button>
|
| </div>
|
| </div>
|
| `).join('');
|
| }
|
|
|
| function searchAdminProducts() {
|
| const search = document.getElementById('admin-product-search').value.toLowerCase();
|
| const filtered = products.filter(p =>
|
| p.name.toLowerCase().includes(search) ||
|
| (p.category || '').toLowerCase().includes(search)
|
| );
|
| renderAdminProducts(filtered);
|
| }
|
|
|
| function showProductModal(productId = null) {
|
| const modal = document.getElementById('admin-product-modal');
|
| const title = document.getElementById('product-modal-title');
|
|
|
| if (productId) {
|
| title.textContent = 'Edit Product';
|
| const product = products.find(p => p.id === productId);
|
| if (product) {
|
| document.getElementById('edit-product-id').value = product.id;
|
| document.getElementById('product-name').value = product.name;
|
| document.getElementById('product-description').value = product.description || '';
|
| document.getElementById('product-price').value = product.base_price;
|
| document.getElementById('product-category').value = product.category || '';
|
| document.getElementById('product-active').checked = product.is_active;
|
| }
|
| } else {
|
| title.textContent = 'Add Product';
|
| document.getElementById('edit-product-id').value = '';
|
| document.getElementById('product-name').value = '';
|
| document.getElementById('product-description').value = '';
|
| document.getElementById('product-price').value = '';
|
| document.getElementById('product-category').value = '';
|
| document.getElementById('product-active').checked = true;
|
| }
|
|
|
| modal.classList.add('active');
|
| }
|
|
|
| function editProduct(productId) {
|
| showProductModal(productId);
|
| }
|
|
|
| async function saveProduct(event) {
|
| event.preventDefault();
|
|
|
| const productId = document.getElementById('edit-product-id').value;
|
| const productData = {
|
| name: document.getElementById('product-name').value,
|
| description: document.getElementById('product-description').value,
|
| base_price: parseFloat(document.getElementById('product-price').value),
|
| category: document.getElementById('product-category').value,
|
| is_active: document.getElementById('product-active').checked
|
| };
|
|
|
| try {
|
| if (productId) {
|
| await apiRequest(`/products/${productId}`, 'PUT', productData);
|
| alert('Product updated successfully!');
|
| } else {
|
| await apiRequest('/products', 'POST', productData);
|
| alert('Product created successfully!');
|
| }
|
|
|
| closeModal('admin-product-modal');
|
| loadAdminProducts();
|
| } catch (error) {
|
| alert('Failed to save product: ' + error.message);
|
| }
|
| }
|
|
|
| async function deleteProduct(productId) {
|
| if (!confirm('Are you sure you want to delete this product?')) return;
|
|
|
| try {
|
| await apiRequest(`/products/${productId}`, 'DELETE');
|
| alert('Product deleted successfully!');
|
| loadAdminProducts();
|
| } catch (error) {
|
| alert('Failed to delete product: ' + error.message);
|
| }
|
| }
|
|
|
| function manageVariants(productId) {
|
| alert('Variant management feature: Coming soon!\n\nUse the API directly to manage variants for now:\nPOST /variants/' + productId);
|
| }
|
|
|
|
|
| async function loadAdminOrders() {
|
| try {
|
| const data = await apiRequest('/orders?limit=100');
|
| orders = data.data || [];
|
| renderAdminOrders(orders);
|
| } catch (error) {
|
| document.getElementById('admin-orders-list').innerHTML =
|
| '<div class="empty-state">Failed to load orders</div>';
|
| }
|
| }
|
|
|
| function renderAdminOrders(orders) {
|
| const list = document.getElementById('admin-orders-list');
|
|
|
| if (!orders || orders.length === 0) {
|
| list.innerHTML = '<div class="empty-state"><div class="empty-state-icon">📋</div><p>No orders found</p></div>';
|
| return;
|
| }
|
|
|
| list.innerHTML = orders.map(order => `
|
| <div class="admin-item">
|
| <div class="admin-item-info">
|
| <div class="admin-item-title">Order #${order.tracking_id || order.id.substring(0, 8)}</div>
|
| <div class="admin-item-meta">
|
| ${order.customer_name} • ${order.customer_email || order.customer_phone} •
|
| ${order.items?.length || 0} items •
|
| $${parseFloat(order.total).toFixed(2)} •
|
| ${new Date(order.created_at).toLocaleDateString()}
|
| </div>
|
| <div class="mt-1">
|
| <span class="status-badge status-${order.delivery_status}">${order.delivery_status}</span>
|
| </div>
|
| </div>
|
| <div class="admin-item-actions">
|
| <select onchange="updateOrderStatus('${order.id}', this.value)" class="input">
|
| <option value="">Change Status</option>
|
| <option value="pending">Pending</option>
|
| <option value="processing">Processing</option>
|
| <option value="shipped">Shipped</option>
|
| <option value="delivered">Delivered</option>
|
| <option value="cancelled">Cancelled</option>
|
| </select>
|
| <button class="btn btn-primary" onclick="viewOrderDetails('${order.id}')">View</button>
|
| </div>
|
| </div>
|
| `).join('');
|
| }
|
|
|
| function searchAdminOrders() {
|
| const search = document.getElementById('admin-order-search').value.toLowerCase();
|
| const filtered = orders.filter(o =>
|
| o.customer_name.toLowerCase().includes(search) ||
|
| (o.customer_email || '').toLowerCase().includes(search) ||
|
| (o.tracking_id || '').toLowerCase().includes(search)
|
| );
|
| renderAdminOrders(filtered);
|
| }
|
|
|
| function filterOrders() {
|
| const status = document.getElementById('order-status-filter').value;
|
| const filtered = status ? orders.filter(o => o.delivery_status === status) : orders;
|
| renderAdminOrders(filtered);
|
| }
|
|
|
| async function updateOrderStatus(orderId, newStatus) {
|
| if (!newStatus) return;
|
|
|
| try {
|
| await apiRequest(`/orders/${orderId}`, 'PATCH', { delivery_status: newStatus });
|
| alert('Order status updated successfully!');
|
| loadAdminOrders();
|
| } catch (error) {
|
| alert('Failed to update order status: ' + error.message);
|
| }
|
| }
|
|
|
| function viewOrderDetails(orderId) {
|
| const order = orders.find(o => o.id === orderId);
|
| if (!order) return;
|
|
|
| const items = order.items?.map(item =>
|
| `${item.product_name} (${item.variant_label}) x${item.quantity} - $${(item.unit_price * item.quantity).toFixed(2)}`
|
| ).join('\n') || 'No items';
|
|
|
| alert(`Order Details:\n\nTracking ID: ${order.tracking_id}\nCustomer: ${order.customer_name}\nEmail: ${order.customer_email || 'N/A'}\nPhone: ${order.customer_phone}\nStatus: ${order.delivery_status}\n\nItems:\n${items}\n\nSubtotal: $${parseFloat(order.subtotal).toFixed(2)}\nShipping: $${parseFloat(order.shipping_fee).toFixed(2)}\nTotal: $${parseFloat(order.total).toFixed(2)}`);
|
| }
|
|
|
|
|
| function showAdminTab(tabName) {
|
| document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
| document.querySelectorAll('.admin-tab').forEach(tab => tab.classList.remove('active'));
|
|
|
| event.target.classList.add('active');
|
| document.getElementById(`admin-${tabName}`).classList.add('active');
|
|
|
| if (tabName === 'products') {
|
| loadAdminProducts();
|
| } else if (tabName === 'orders') {
|
| loadAdminOrders();
|
| }
|
| }
|
|
|
|
|
| function closeModal(modalId) {
|
| document.getElementById(modalId).classList.remove('active');
|
| }
|
|
|
|
|
| window.onclick = function(event) {
|
| if (event.target.classList.contains('modal')) {
|
| event.target.classList.remove('active');
|
| }
|
| }
|
|
|
|
|
| document.addEventListener('DOMContentLoaded', () => {
|
| updateCartCount();
|
| });
|
|
|