Added Repository
This commit is contained in:
131
templates/admin/add_product.html
Normal file
131
templates/admin/add_product.html
Normal file
@@ -0,0 +1,131 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/add_product.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/add_product.js') }}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="admin-container">
|
||||
<h2>Add New Product</h2>
|
||||
<form method="POST" enctype="multipart/form-data" class="product-form">
|
||||
<div class="form-grid">
|
||||
<div class="form-section">
|
||||
<h3>Basic Information</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Product Name *</label>
|
||||
<input type="text" id="name" name="name" required class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sku">SKU (Stock Keeping Unit)</label>
|
||||
<input type="text" id="sku" name="sku" class="form-control" placeholder="e.g., TSHIRT-RED-M">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<textarea id="description" name="description" class="form-control" rows="4"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="price">Price ($) *</label>
|
||||
<input type="number" step="0.01" id="price" name="price" required class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="stock">Stock Quantity *</label>
|
||||
<input type="number" id="stock" name="stock" value="0" min="0" required class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h3>Product Details</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="category_id">Category *</label>
|
||||
<select id="category_id" name="category_id" class="form-control">
|
||||
<option value="">-- Select Category --</option>
|
||||
{% for c in categories %}
|
||||
<option value="{{ c.id }}">{{ c.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="material">Material</label>
|
||||
<input type="text" id="material" name="material" class="form-control" placeholder="e.g., Cotton, Polyester">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="size">Size</label>
|
||||
<select id="size" name="size" class="form-control">
|
||||
<option value="">-- Select Size --</option>
|
||||
{% for size in sizes %}
|
||||
<option value="{{ size }}">{{ size }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="color">Color</label>
|
||||
<input type="text" id="color" name="color" class="form-control" placeholder="e.g., Red, Blue">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="company">Brand/Company</label>
|
||||
<input type="text" id="company" name="company" class="form-control" placeholder="e.g., Nike, Adidas">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="weight">Weight (kg)</label>
|
||||
<input type="number" step="0.01" id="weight" name="weight" class="form-control" placeholder="0.5">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dimensions">Dimensions</label>
|
||||
<input type="text" id="dimensions" name="dimensions" class="form-control" placeholder="10x5x3 cm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h3>Product Images</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Upload Images *</label>
|
||||
<div class="image-upload-area" id="image-upload-area">
|
||||
<div class="upload-prompt">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="17 8 12 3 7 8"></polyline>
|
||||
<line x1="12" y1="3" x2="12" y2="15"></line>
|
||||
</svg>
|
||||
<p>Click or drag images here</p>
|
||||
<p class="upload-hint">First image will be primary</p>
|
||||
</div>
|
||||
<input type="file" name="images[]" multiple accept="image/*" class="image-input" id="image-input">
|
||||
</div>
|
||||
|
||||
<div class="image-preview" id="image-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Save Product</button>
|
||||
<a href="{{ url_for('admin.dashboard') }}" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
72
templates/admin/categories.html
Normal file
72
templates/admin/categories.html
Normal file
@@ -0,0 +1,72 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/categories.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="admin-container">
|
||||
<h2>Manage Categories</h2>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h3>Add New Category</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('admin.add_category') }}" class="row g-3">
|
||||
<div class="col-md-8">
|
||||
<input type="text" name="name" class="form-control" placeholder="Category name" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button type="submit" class="btn btn-primary w-100">Add Category</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>All Categories ({{ categories|length }})</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if categories %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Products</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for category in categories %}
|
||||
<tr>
|
||||
<td>{{ category.id }}</td>
|
||||
<td>{{ category.name }}</td>
|
||||
<td>{{ category.products|length }}</td>
|
||||
<td>
|
||||
{% if category.products|length == 0 %}
|
||||
<form method="POST" action="{{ url_for('admin.delete_category', id=category.id) }}"
|
||||
style="display: inline;" onsubmit="return confirm('Delete category {{ category.name }}?')">
|
||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<button class="btn btn-sm btn-secondary" disabled title="Cannot delete category with products">
|
||||
Delete
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">No categories found.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
54
templates/admin/dashboard.html
Normal file
54
templates/admin/dashboard.html
Normal file
@@ -0,0 +1,54 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/dashboard.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Admin Dashboard</h2>
|
||||
<a href="{{ url_for('admin.add_product') }}" class="btn btn-success">Add New Product</a>
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Price</th>
|
||||
<th>Stock</th>
|
||||
<th>Category</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in products %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ p.get_image_url() }}" alt="{{ p.name }}" style="width: 60px; height: 60px; object-fit: cover;">
|
||||
</td>
|
||||
<td>{{ p.id }}</td>
|
||||
<td>{{ p.name }}</td>
|
||||
<td>{{ p.description[:50] }}{% if p.description|length > 50 %}...{% endif %}</td>
|
||||
<td>${{ "%.2f"|format(p.price) }}</td>
|
||||
<td>
|
||||
{% if p.stock > 10 %}
|
||||
<span class="stock-high">{{ p.stock }}</span>
|
||||
{% elif p.stock > 0 %}
|
||||
<span class="stock-low">{{ p.stock }}</span>
|
||||
{% else %}
|
||||
<span class="stock-out">Out of Stock</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ p.category.name if p.category else 'N/A' }}</td>
|
||||
<td class="actions">
|
||||
<a href="{{ url_for('admin.edit_product', id=p.id) }}" class="btn btn-sm btn-primary">Edit</a>
|
||||
<form method="POST" action="{{ url_for('admin.delete_product', id=p.id) }}" style="display:inline;"
|
||||
onsubmit="return confirm('Delete this product?')">
|
||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
169
templates/admin/edit_product.html
Normal file
169
templates/admin/edit_product.html
Normal file
@@ -0,0 +1,169 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/edit_product.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/edit_product.js') }}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="admin-container">
|
||||
<h2>Edit Product: {{ product.name }}</h2>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data" class="product-form" id="product-form">
|
||||
<div class="form-grid">
|
||||
|
||||
<div class="form-section">
|
||||
<h3>Basic Information</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Product Name *</label>
|
||||
<input type="text" id="name" name="name" value="{{ product.name }}" required class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sku">SKU (Stock Keeping Unit)</label>
|
||||
<input type="text" id="sku" name="sku" value="{{ product.sku or '' }}" class="form-control"
|
||||
placeholder="e.g., TSHIRT-RED-M">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<textarea id="description" name="description" class="form-control"
|
||||
rows="4">{{ product.description or '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="price">Price ($) *</label>
|
||||
<input type="number" step="0.01" id="price" name="price" value="{{ product.price }}" required
|
||||
class="form-control" min="0.01">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="stock">Stock Quantity *</label>
|
||||
<input type="number" id="stock" name="stock" value="{{ product.stock }}" min="0" required
|
||||
class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h3>Product Details</h3>
|
||||
<div class="form-group">
|
||||
<label for="category_id">Category *</label>
|
||||
<select id="category_id" name="category_id" class="form-control" required>
|
||||
<option value="">-- Select Category --</option>
|
||||
{% for c in categories %}
|
||||
<option value="{{ c.id }}" {% if product.category_id==c.id %}selected{% endif %}>
|
||||
{{ c.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="material">Material</label>
|
||||
<input type="text" id="material" name="material" value="{{ product.material or '' }}" class="form-control"
|
||||
placeholder="e.g., Cotton, Polyester, Denim">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="size">Size</label>
|
||||
<select id="size" name="size" class="form-control">
|
||||
<option value="">-- Select Size --</option>
|
||||
{% for size in sizes %}
|
||||
<option value="{{ size }}" {% if product.size==size %}selected{% endif %}>
|
||||
{{ size }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="color">Color</label>
|
||||
<input type="text" id="color" name="color" value="{{ product.color or '' }}" class="form-control"
|
||||
placeholder="e.g., Red, Blue, Black">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="company">Brand/Company</label>
|
||||
<input type="text" id="company" name="company" value="{{ product.company or '' }}" class="form-control"
|
||||
placeholder="e.g., Nike, Adidas, Levi's">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="weight">Weight (kg)</label>
|
||||
<input type="number" step="0.01" id="weight" name="weight" value="{{ product.weight or '' }}"
|
||||
class="form-control" placeholder="0.5" min="0">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dimensions">Dimensions</label>
|
||||
<input type="text" id="dimensions" name="dimensions" value="{{ product.dimensions or '' }}"
|
||||
class="form-control" placeholder="10x5x3 cm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h3>Current Images</h3>
|
||||
<div class="current-images">
|
||||
{% if product.images %}
|
||||
<div class="image-grid">
|
||||
{% for image in product.images %}
|
||||
<div class="image-item" data-id="{{ image.id }}">
|
||||
<img src="/static/uploads/products/{{ image.filename }}" alt="Product Image {{ loop.index }}">
|
||||
<div class="image-actions">
|
||||
<label class="primary-checkbox">
|
||||
<input type="radio" name="primary_image" value="{{ image.id }}" {% if image.is_primary %}checked{%
|
||||
endif %}>
|
||||
Primary
|
||||
</label>
|
||||
<button type="button" class="btn-delete-image" onclick="deleteImage('{{ image.id }}')">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="no-images">No images uploaded yet.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 30px;">Add New Images</h3>
|
||||
<div class="form-group">
|
||||
<div class="image-upload-area" id="image-upload-area">
|
||||
<div class="upload-prompt">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="17 8 12 3 7 8"></polyline>
|
||||
<line x1="12" y1="3" x2="12" y2="15"></line>
|
||||
</svg>
|
||||
<p>Click or drag images here</p>
|
||||
</div>
|
||||
<input type="file" name="images[]" multiple accept="image/*" class="image-input" id="image-input">
|
||||
</div>
|
||||
|
||||
<div class="image-preview" id="image-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="deleted_images" id="deleted-images" value="">
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Update Product</button>
|
||||
<a href="{{ url_for('admin.dashboard') }}" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
61
templates/base.html
Normal file
61
templates/base.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WearWell Shop{% block title %}{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
|
||||
{% block styles %}{% endblock %}
|
||||
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<a href="{{ url_for('main.home') }}">Home</a>
|
||||
<a href="{{ url_for('main.products') }}">Products</a>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('cart.view_cart') }}">
|
||||
Cart {% if current_user.cart and current_user.cart.items|length > 0 %}
|
||||
<span class="cart-count">{{ current_user.cart.items|length }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
<a href="{{ url_for('user.profile') }}">My Profile</a>
|
||||
|
||||
{% if current_user.is_admin %}
|
||||
<a href="{{ url_for('admin.dashboard') }}">Admin Panel</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ url_for('auth.logout') }}">Logout</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('auth.login') }}">Login</a>
|
||||
<a href="{{ url_for('auth.register') }}">Register</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash {{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %} {% endwith %}
|
||||
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container text-center">
|
||||
<p>© 2025 WearWell Shop. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
114
templates/cart.html
Normal file
114
templates/cart.html
Normal file
@@ -0,0 +1,114 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/cart.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="cart-container">
|
||||
<h1>Your Shopping Cart</h1>
|
||||
|
||||
{% if items %}
|
||||
<div class="cart-items">
|
||||
<table class="cart-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Price</th>
|
||||
<th>Quantity</th>
|
||||
<th>Subtotal</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td class="product-info">
|
||||
<div class="product-image">
|
||||
<img src="{{ item.product.get_image_url() }}" alt="{{ item.product.name }}">
|
||||
</div>
|
||||
<div class="product-details">
|
||||
<h4>{{ item.product.name }}</h4>
|
||||
<p class="product-description">{{ item.product.description[:80] }}...</p>
|
||||
<div class="stock-info">
|
||||
{% if item.product.stock > 10 %}
|
||||
<span class="in-stock">✓ In Stock</span>
|
||||
{% elif item.product.stock > 0 %}
|
||||
<span class="low-stock">⚠ Low Stock ({{ item.product.stock }} left)</span>
|
||||
{% else %}
|
||||
<span class="out-of-stock">✗ Out of Stock</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="price">${{ "%.2f"|format(item.product.price) }}</td>
|
||||
<td class="quantity">
|
||||
<form method="POST" action="{{ url_for('cart.update_cart', item_id=item.id) }}" class="quantity-form">
|
||||
<input type="number" name="quantity" value="{{ item.quantity }}" min="1" max="{{ item.max_quantity }}"
|
||||
class="quantity-input">
|
||||
<button type="submit" class="btn-update">Update</button>
|
||||
<div class="max-quantity">
|
||||
Max: {{ item.max_quantity }}
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
<td class="subtotal">${{ "%.2f"|format(item.subtotal) }}</td>
|
||||
<td class="actions">
|
||||
<form method="POST" action="{{ url_for('cart.remove_from_cart', item_id=item.id) }}"
|
||||
onsubmit="return confirm('Remove {{ item.product.name }} from cart?')">
|
||||
<button type="submit" class="btn-remove">
|
||||
Remove
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="cart-summary">
|
||||
<div class="summary-card">
|
||||
<h3>Order Summary</h3>
|
||||
<div class="summary-row">
|
||||
<span>Subtotal:</span>
|
||||
<span>${{ "%.2f"|format(total) }}</span>
|
||||
</div>
|
||||
<div class="summary-row">
|
||||
<span>Shipping:</span>
|
||||
<span>Free</span>
|
||||
</div>
|
||||
<div class="summary-row total">
|
||||
<span>Total:</span>
|
||||
<span>${{ "%.2f"|format(total) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="cart-actions">
|
||||
<a href="{{ url_for('main.products') }}" class="btn-continue">
|
||||
← Continue Shopping
|
||||
</a>
|
||||
|
||||
<form method="POST" action="{{ url_for('cart.clear_cart') }}" style="display: inline;">
|
||||
<button type="submit" class="btn-clear" onclick="return confirm('Clear entire cart?')">
|
||||
Clear Cart
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<a href="{{ url_for('cart.checkout') }}" class="btn-checkout">
|
||||
Proceed to Checkout →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-cart">
|
||||
<div class="empty-icon">🛒</div>
|
||||
<h3>Your cart is empty</h3>
|
||||
<p>Add some products to your cart and they will appear here.</p>
|
||||
<a href="{{ url_for('main.products') }}" class="btn-shop">
|
||||
Browse Products
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
71
templates/checkout.html
Normal file
71
templates/checkout.html
Normal file
@@ -0,0 +1,71 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/checkout.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="checkout-container">
|
||||
<h1>Checkout</h1>
|
||||
|
||||
<div class="checkout-content">
|
||||
<div class="order-summary">
|
||||
<h3>Order Summary</h3>
|
||||
{% for item in cart.items %}
|
||||
<div class="order-item">
|
||||
<div class="item-info">
|
||||
<h4>{{ item.product.name }}</h4>
|
||||
<p>Quantity: {{ item.quantity }} × ${{ "%.2f"|format(item.product.price) }}</p>
|
||||
</div>
|
||||
<div class="item-total">
|
||||
${{ "%.2f"|format(item.product.price * item.quantity) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="order-total">
|
||||
<h3>Total: ${{ "%.2f"|format(total) }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkout-form">
|
||||
<h3>Shipping Information</h3>
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label>Full Name:</label>
|
||||
<input type="text" name="name" required class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Email:</label>
|
||||
<input type="email" name="email" value="{{ current_user.email }}" required class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Address:</label>
|
||||
<textarea name="address" required class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>City:</label>
|
||||
<input type="text" name="city" required class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Postal Code:</label>
|
||||
<input type="text" name="postal_code" required class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="{{ url_for('cart.view_cart') }}" class="btn-back">
|
||||
← Back to Cart
|
||||
</a>
|
||||
<button type="submit" class="btn-confirm">
|
||||
Place Order
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
229
templates/home.html
Normal file
229
templates/home.html
Normal file
@@ -0,0 +1,229 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/home.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/home.js') }}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<section class="hero-section">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">Welcome to WearWell Shop</h1>
|
||||
<p class="hero-subtitle">Discover premium fashion for every occasion. Quality clothing, unbeatable prices, and style
|
||||
that lasts.</p>
|
||||
|
||||
<div class="hero-search">
|
||||
<form action="{{ url_for('main.products') }}" method="GET">
|
||||
<input type="text" name="q" placeholder="Search for products, brands, or categories..."
|
||||
class="hero-search-input">
|
||||
<button type="submit" class="hero-search-button">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="categories-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">Shop by Category</h2>
|
||||
<a href="{{ url_for('main.products') }}" class="view-all-link">
|
||||
View All Categories →
|
||||
</a>
|
||||
</div>
|
||||
<div class="categories-grid">
|
||||
{% for category in categories[:6] %}
|
||||
<a href="{{ url_for('main.products', category=category.id) }}" class="category-card">
|
||||
<div class="category-image">
|
||||
{% if category.name == "T-Shirts" %}👕
|
||||
{% elif category.name == "Jeans" %}👖
|
||||
{% elif category.name == "Jackets" %}🧥
|
||||
{% elif category.name == "Shoes" %}👟
|
||||
{% elif category.name == "Accessories" %}👜
|
||||
{% elif category.name == "Dresses" %}👗
|
||||
{% elif category.name == "Shirts" %}👔
|
||||
{% elif category.name == "Hoodies" %}🧢
|
||||
{% else %}👚{% endif %}
|
||||
</div>
|
||||
<div class="category-name">
|
||||
{{ category.name }}
|
||||
<span class="category-count">{{ category.products|length }} items</span>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="featured-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">Featured Products</h2>
|
||||
<a href="{{ url_for('main.products') }}" class="view-all-link">
|
||||
View All Products →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="products-grid">
|
||||
{% for product in featured_products[:8] %}
|
||||
<div class="product-card-home">
|
||||
{% if product.stock <= 5 and product.stock> 0 %}
|
||||
<span class="product-badge">Low Stock</span>
|
||||
{% elif product.stock == 0 %}
|
||||
<span class="product-badge" style="background: #666;">Out of Stock</span>
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ url_for('main.product_detail', id=product.id) }}">
|
||||
<div class="product-image-home">
|
||||
<img src="{{ product.get_image_url() }}" alt="{{ product.name }}">
|
||||
</div>
|
||||
|
||||
<div class="product-info-home">
|
||||
<h3 class="product-title-home">{{ product.name }}</h3>
|
||||
|
||||
<div class="product-price-home">${{ "%.2f"|format(product.price) }}</div>
|
||||
|
||||
{% if product.average_rating > 0 %}
|
||||
<div class="product-rating-home">
|
||||
<div class="stars">
|
||||
{% for i in range(5) %}
|
||||
<span>{% if i < product.average_rating|int %}★{% else %}☆{% endif %}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<span class="rating-count">({{ product.review_count }})</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="product-meta-home">
|
||||
{% if product.company %}
|
||||
<span>{{ product.company }}</span>
|
||||
{% endif %}
|
||||
{% if product.color %}
|
||||
<span>{{ product.color }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="promo-banner">
|
||||
<div class="promo-content">
|
||||
<h2>Summer Sale is Here! 🌞</h2>
|
||||
<p>Get up to 50% off on selected items. Limited time offer!</p>
|
||||
<a href="{{ url_for('main.products') }}" class="promo-button">
|
||||
Shop Now →
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="benefits-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">Why Shop With Us?</h2>
|
||||
</div>
|
||||
|
||||
<div class="benefits-grid">
|
||||
<div class="benefit-card">
|
||||
<span class="benefit-icon">🚚</span>
|
||||
<h3 class="benefit-title">Free Shipping</h3>
|
||||
<p class="benefit-description">Free delivery on all orders over $50. Fast and reliable shipping nationwide.</p>
|
||||
</div>
|
||||
|
||||
<div class="benefit-card">
|
||||
<span class="benefit-icon">🔄</span>
|
||||
<h3 class="benefit-title">Easy Returns</h3>
|
||||
<p class="benefit-description">30-day return policy. If you're not satisfied, we'll make it right.</p>
|
||||
</div>
|
||||
|
||||
<div class="benefit-card">
|
||||
<span class="benefit-icon">🔒</span>
|
||||
<h3 class="benefit-title">Secure Payment</h3>
|
||||
<p class="benefit-description">Your payment information is protected with bank-level security.</p>
|
||||
</div>
|
||||
|
||||
<div class="benefit-card">
|
||||
<span class="benefit-icon">⭐</span>
|
||||
<h3 class="benefit-title">Quality Guarantee</h3>
|
||||
<p class="benefit-description">Premium materials and craftsmanship in every product we sell.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="featured-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">Best Sellers</h2>
|
||||
<a href="{{ url_for('main.products') }}" class="view-all-link">
|
||||
View More →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="products-grid">
|
||||
{% for product in best_sellers[:4] %}
|
||||
<div class="product-card-home">
|
||||
{% if product.stock <= 5 and product.stock> 0 %}
|
||||
<span class="product-badge">Popular</span>
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ url_for('main.product_detail', id=product.id) }}">
|
||||
<div class="product-image-home">
|
||||
<img src="{{ product.get_image_url() }}" alt="{{ product.name }}">
|
||||
</div>
|
||||
|
||||
<div class="product-info-home">
|
||||
<h3 class="product-title-home">{{ product.name }}</h3>
|
||||
|
||||
<div class="product-price-home">${{ "%.2f"|format(product.price) }}</div>
|
||||
|
||||
{% if product.average_rating > 0 %}
|
||||
<div class="product-rating-home">
|
||||
<div class="stars">
|
||||
{% for i in range(5) %}
|
||||
<span>{% if i < product.average_rating|int %}★{% else %}☆{% endif %}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<span class="rating-count">({{ product.review_count }})</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="product-meta-home">
|
||||
<span>{{ product.stock }} in stock</span>
|
||||
<span>{{ product.like_count }} likes</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="newsletter-section">
|
||||
<div class="newsletter-content">
|
||||
<h2>Stay in the Loop</h2>
|
||||
<p>Subscribe to our newsletter for exclusive deals, new arrivals, and style tips.</p>
|
||||
|
||||
<form class="newsletter-form">
|
||||
<input type="email" placeholder="Enter your email address" class="newsletter-input" required>
|
||||
<button type="submit" class="newsletter-button">Subscribe</button>
|
||||
</form>
|
||||
|
||||
<p style="margin-top: 15px; font-size: 0.9em; opacity: 0.8;">
|
||||
By subscribing, you agree to our Privacy Policy and consent to receive updates.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
91
templates/login.html
Normal file
91
templates/login.html
Normal file
@@ -0,0 +1,91 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/auth.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/login.js') }}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="auth-container">
|
||||
<h2>Welcome Back</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash {{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" class="auth-form">
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<input type="email" id="email" name="email" class="form-control" placeholder="you@example.com" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<div class="password-container">
|
||||
<input type="password" id="password" name="password" class="form-control" placeholder="Enter your password"
|
||||
required>
|
||||
<button type="button" class="toggle-password" onclick="togglePassword('password')">
|
||||
👁️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="remember-me">
|
||||
<input type="checkbox" id="remember" name="remember">
|
||||
<label for="remember">Remember me</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-submit">
|
||||
Sign In
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-links">
|
||||
<a href="{{ url_for('auth.register') }}" class="auth-link">
|
||||
Don't have an account? Register here
|
||||
</a>
|
||||
<a href="#" class="auth-link">
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="social-login">
|
||||
<div class="social-divider">
|
||||
<span>Or continue with</span>
|
||||
</div>
|
||||
|
||||
<div class="social-buttons">
|
||||
<button type="button" class="btn-social google">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
|
||||
<path fill="currentColor"
|
||||
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
||||
<path fill="currentColor"
|
||||
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
|
||||
<path fill="currentColor"
|
||||
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
|
||||
</svg>
|
||||
Google
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn-social facebook">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
|
||||
</svg>
|
||||
Facebook
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
93
templates/order_details.html
Normal file
93
templates/order_details.html
Normal file
@@ -0,0 +1,93 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/order_details.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="order-details-container">
|
||||
<h2>Order #{{ order.id }}</h2>
|
||||
|
||||
<div class="order-meta">
|
||||
<p><strong>Order Date:</strong> {{ order.created_at.strftime('%Y-%m-%d %H:%M') }}</p>
|
||||
<div class="order-status">
|
||||
<span class="status-label">Status:</span>
|
||||
<span class="status-badge status-{{ order.status }}">
|
||||
{{ order.status|title }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="items-section">
|
||||
<h3>Order Items</h3>
|
||||
|
||||
{% if order.items %}
|
||||
<table class="items-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Quantity</th>
|
||||
<th>Price</th>
|
||||
<th>Subtotal</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in order.items %}
|
||||
<tr>
|
||||
<td class="product-cell">
|
||||
<div class="product-image">
|
||||
<img src="{{ item.product.get_image_url() }}" alt="{{ item.product.name }}">
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<div class="product-name">{{ item.product.name }}</div>
|
||||
{% if item.selected_size or item.selected_color %}
|
||||
<div class="product-variants">
|
||||
{% if item.selected_size %}Size: {{ item.selected_size }}{% endif %}
|
||||
{% if item.selected_color %} | Color: {{ item.selected_color }}{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="quantity-cell">{{ item.quantity }}</td>
|
||||
<td class="price-cell">${{ "%.2f"|format(item.product.price) }}</td>
|
||||
<td class="subtotal-cell">${{ "%.2f"|format(item.product.price * item.quantity) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="order-summary">
|
||||
<div class="summary-row">
|
||||
<span class="summary-label">Subtotal:</span>
|
||||
<span class="summary-value">
|
||||
${{ "%.2f"|format(order.items|sum(attribute='product.price') * order.items|sum(attribute='quantity')) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="summary-row">
|
||||
<span class="summary-label">Shipping:</span>
|
||||
<span class="summary-value">Free</span>
|
||||
</div>
|
||||
<div class="summary-row">
|
||||
<span class="summary-label">Total:</span>
|
||||
<span class="summary-value">
|
||||
${{ "%.2f"|format(order.items|sum(attribute='product.price') * order.items|sum(attribute='quantity')) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="no-items">
|
||||
<p>No items in this order.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="order-actions">
|
||||
<a href="{{ url_for('user.profile') }}" class="btn-back">
|
||||
← Back to My Orders
|
||||
</a>
|
||||
<button onclick="window.print()" class="btn-print">
|
||||
📄 Print Order
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
178
templates/product_detail.html
Normal file
178
templates/product_detail.html
Normal file
@@ -0,0 +1,178 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/product_detail.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/product_detail.js') }}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="product-detail-container">
|
||||
|
||||
<div class="product-detail">
|
||||
|
||||
<div class="product-gallery">
|
||||
|
||||
<div class="main-image-container">
|
||||
<img id="mainImage" src="{{ product.get_image_url() }}" alt="{{ product.name }}" class="main-image">
|
||||
</div>
|
||||
|
||||
{% if product.images|length > 0 %}
|
||||
<div class="thumbnail-gallery">
|
||||
{% for image in product.images|sort(attribute='display_order') %}
|
||||
<div class="thumbnail-container {% if loop.first %}active{% endif %}"
|
||||
data-image-url="{{ url_for('static', filename='uploads/products/' + image.filename) }}"
|
||||
onclick="changeImage(this)">
|
||||
<img src="{{ url_for('static', filename='uploads/products/' + image.filename) }}"
|
||||
alt="{{ product.name }} - Image {{ loop.index }}" class="thumbnail">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="product-info">
|
||||
<h1>{{ product.name }}</h1>
|
||||
|
||||
<div class="stock-status">
|
||||
{% if product.stock > 10 %}
|
||||
<span class="in-stock">✓ In Stock ({{ product.stock }} available)</span>
|
||||
{% elif product.stock > 0 %}
|
||||
<span class="low-stock">⚠ Low Stock (Only {{ product.stock }} left!)</span>
|
||||
{% else %}
|
||||
<span class="out-of-stock">✗ Out of Stock</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if product.average_rating > 0 %}
|
||||
<div class="product-rating">
|
||||
<div class="stars">
|
||||
{% for i in range(5) %}
|
||||
<span class="star {% if i < product.average_rating|int %}filled{% endif %}">★</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<span class="rating-value">{{ product.average_rating }}/5</span>
|
||||
<span class="review-count">({{ product.review_count }} reviews)</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="price-section">
|
||||
<span class="price">${{ "%.2f"|format(product.price) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="product-meta">
|
||||
{% if product.sku %}
|
||||
<div class="meta-item">
|
||||
<strong>SKU:</strong> {{ product.sku }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if product.company %}
|
||||
<div class="meta-item">
|
||||
<strong>Brand:</strong> {{ product.company }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if product.color %}
|
||||
<div class="meta-item">
|
||||
<strong>Color:</strong> {{ product.color }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if product.size %}
|
||||
<div class="meta-item">
|
||||
<strong>Size:</strong> {{ product.size }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if product.category %}
|
||||
<div class="meta-item">
|
||||
<strong>Category:</strong> {{ product.category.name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="description-section">
|
||||
<h3>Description</h3>
|
||||
<p>{{ product.description or 'No description available.' }}</p>
|
||||
</div>
|
||||
|
||||
{% if product.material or product.weight or product.dimensions %}
|
||||
<div class="specifications">
|
||||
<h3>Specifications</h3>
|
||||
<div class="spec-grid">
|
||||
{% if product.material %}
|
||||
<div class="spec-item">
|
||||
<span class="spec-label">Material:</span>
|
||||
<span class="spec-value">{{ product.material }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if product.weight %}
|
||||
<div class="spec-item">
|
||||
<span class="spec-label">Weight:</span>
|
||||
<span class="spec-value">{{ product.weight }} kg</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if product.dimensions %}
|
||||
<div class="spec-item">
|
||||
<span class="spec-label">Dimensions:</span>
|
||||
<span class="spec-value">{{ product.dimensions }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="actions-section">
|
||||
{% if current_user.is_authenticated and product.stock > 0 %}
|
||||
<a href="{{ url_for('cart.add_to_cart', product_id=product.id) }}" class="btn btn-primary">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<circle cx="9" cy="21" r="1"></circle>
|
||||
<circle cx="20" cy="21" r="1"></circle>
|
||||
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
|
||||
</svg>
|
||||
Add to Cart
|
||||
</a>
|
||||
{% elif current_user.is_authenticated %}
|
||||
<button class="btn btn-secondary" disabled>
|
||||
Out of Stock
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{{ url_for('auth.login') }}" class="btn btn-primary">
|
||||
Login to Purchase
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ url_for('main.products') }}" class="btn btn-outline">
|
||||
← Back to Products
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if related_products %}
|
||||
<div class="related-products">
|
||||
<h2>Related Products</h2>
|
||||
<div class="related-grid">
|
||||
{% for related in related_products %}
|
||||
<div class="product-card">
|
||||
<a href="{{ url_for('main.product_detail', id=related.id) }}" class="product-link">
|
||||
<div class="product-image">
|
||||
<img src="{{ related.get_image_url() }}" alt="{{ related.name }}">
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<h3 class="product-name">{{ related.name }}</h3>
|
||||
<div class="product-price">${{ "%.2f"|format(related.price) }}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
255
templates/products.html
Normal file
255
templates/products.html
Normal file
@@ -0,0 +1,255 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/products.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/products.js') }}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="products-page">
|
||||
<aside class="filters-sidebar">
|
||||
<div class="filters-header">
|
||||
<h3>Filters</h3>
|
||||
{% if search_query or selected_category or selected_size or selected_color or selected_type or selected_material
|
||||
or selected_company or min_price or max_price or in_stock_only %}
|
||||
<a href="{{ url_for('main.products') }}" class="clear-filters">Clear All</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form method="GET" action="{{ url_for('main.products') }}" class="filters-form">
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="search">Search</label>
|
||||
<input type="text" id="search" name="q" value="{{ search_query }}" placeholder="Search products..."
|
||||
class="filter-input">
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="category">Category</label>
|
||||
<select id="category" name="category" class="filter-select">
|
||||
<option value="">All Categories</option>
|
||||
{% for c in categories %}
|
||||
<option value="{{ c.id }}" {% if selected_category==c.id|string %}selected{% endif %}>
|
||||
{{ c.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="size">Size</label>
|
||||
<select id="size" name="size" class="filter-select">
|
||||
<option value="">All Sizes</option>
|
||||
{% for size in sizes %}
|
||||
<option value="{{ size }}" {% if selected_size==size %}selected{% endif %}>
|
||||
{{ size }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="color">Color</label>
|
||||
<select id="color" name="color" class="filter-select">
|
||||
<option value="">All Colors</option>
|
||||
{% for color in colors %}
|
||||
<option value="{{ color }}" {% if selected_color==color %}selected{% endif %}>
|
||||
{{ color }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="material">Material</label>
|
||||
<select id="material" name="material" class="filter-select">
|
||||
<option value="">All Materials</option>
|
||||
{% for material in materials %}
|
||||
<option value="{{ material }}" {% if selected_material==material %}selected{% endif %}>
|
||||
{{ material }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="company">Brand</label>
|
||||
<select id="company" name="company" class="filter-select">
|
||||
<option value="">All Brands</option>
|
||||
{% for company in companies %}
|
||||
<option value="{{ company }}" {% if selected_company==company %}selected{% endif %}>
|
||||
{{ company }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Price Range ($)</label>
|
||||
<div class="price-range">
|
||||
<input type="number" name="min_price" value="{{ min_price }}" placeholder="Min" class="price-input" min="0"
|
||||
step="0.01">
|
||||
<span class="price-separator">-</span>
|
||||
<input type="number" name="max_price" value="{{ max_price }}" placeholder="Max" class="price-input" min="0"
|
||||
step="0.01">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="in_stock" value="1" {% if in_stock_only %}checked{% endif %}>
|
||||
In Stock Only
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-apply-filters">Apply Filters</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<main class="products-main">
|
||||
<div class="products-header">
|
||||
<div class="results-info">
|
||||
<h1>Clothing</h1>
|
||||
<p class="results-count">{{ products|length }} products found</p>
|
||||
{% if search_query or selected_category or selected_size or selected_color or selected_type or selected_material
|
||||
or selected_company or min_price or max_price or in_stock_only %}
|
||||
<div class="active-filters">
|
||||
{% if search_query %}
|
||||
<span class="filter-tag">Search: "{{ search_query }}"
|
||||
<a
|
||||
href="{{ url_for('main.products', q='', category=selected_category, size=selected_size, color=selected_color, type=selected_type, material=selected_material, company=selected_company, min_price=min_price, max_price=max_price, in_stock=in_stock_only, sort=sort_by) }}">×</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if selected_category_name %}
|
||||
<span class="filter-tag">Category: {{ selected_category_name }}
|
||||
<a
|
||||
href="{{ url_for('main.products', q=search_query, category='', size=selected_size, color=selected_color, type=selected_type, material=selected_material, company=selected_company, min_price=min_price, max_price=max_price, in_stock=in_stock_only, sort=sort_by) }}">×</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if selected_size %}
|
||||
<span class="filter-tag">Size: {{ selected_size }}
|
||||
<a
|
||||
href="{{ url_for('main.products', q=search_query, category=selected_category, size='', color=selected_color, type=selected_type, material=selected_material, company=selected_company, min_price=min_price, max_price=max_price, in_stock=in_stock_only, sort=sort_by) }}">×</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if selected_color %}
|
||||
<span class="filter-tag">Color: {{ selected_color }}
|
||||
<a
|
||||
href="{{ url_for('main.products', q=search_query, category=selected_category, size=selected_size, color='', type=selected_type, material=selected_material, company=selected_company, min_price=min_price, max_price=max_price, in_stock=in_stock_only, sort=sort_by) }}">×</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="sort-controls">
|
||||
<label for="sort">Sort by:</label>
|
||||
<select id="sort" name="sort" class="sort-select" onchange="this.form.submit()">
|
||||
<option value="newest" {% if sort_by=='newest' %}selected{% endif %}>Newest</option>
|
||||
<option value="price_low" {% if sort_by=='price_low' %}selected{% endif %}>Price: Low to High</option>
|
||||
<option value="price_high" {% if sort_by=='price_high' %}selected{% endif %}>Price: High to Low</option>
|
||||
<option value="name" {% if sort_by=='name' %}selected{% endif %}>Name: A-Z</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if products %}
|
||||
<div class="products-grid">
|
||||
{% for product in products %}
|
||||
<div class="product-card">
|
||||
<a href="{{ url_for('main.product_detail', id=product.id) }}" class="product-link">
|
||||
<div class="product-image">
|
||||
<img src="{{ product.get_image_url() }}" alt="{{ product.name }}">
|
||||
{% if product.stock <= 0 %} <div class="out-of-stock">Out of Stock
|
||||
</div>
|
||||
{% elif product.stock <= 5 %} <div class="low-stock">Only {{ product.stock }} left
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if product.average_rating > 0 %}
|
||||
<div class="product-rating">
|
||||
<span class="rating-stars">★★★★★</span>
|
||||
<span class="rating-value">{{ product.average_rating }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="product-info">
|
||||
<h3 class="product-name">{{ product.name }}</h3>
|
||||
|
||||
<div class="product-meta">
|
||||
{% if product.color %}
|
||||
<span class="meta-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
</svg>
|
||||
{{ product.color }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if product.size %}
|
||||
<span class="meta-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M20 7h-9"></path>
|
||||
<path d="M14 17H5"></path>
|
||||
<circle cx="17" cy="17" r="3"></circle>
|
||||
<circle cx="7" cy="7" r="3"></circle>
|
||||
</svg>
|
||||
{{ product.size }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if product.company %}
|
||||
<span class="meta-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
||||
</svg>
|
||||
{{ product.company }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="product-footer">
|
||||
<div class="product-price">${{ "%.2f"|format(product.price) }}</div>
|
||||
|
||||
<div class="product-actions">
|
||||
{% if current_user.is_authenticated and product.stock > 0 %}
|
||||
<a href="{{ url_for('cart.add_to_cart', product_id=product.id) }}" class="btn-add-to-cart"
|
||||
onclick="event.stopPropagation();">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<circle cx="9" cy="21" r="1"></circle>
|
||||
<circle cx="20" cy="21" r="1"></circle>
|
||||
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
|
||||
</svg>
|
||||
Add to Cart
|
||||
</a>
|
||||
{% elif current_user.is_authenticated %}
|
||||
<button class="btn-out-of-stock" disabled>
|
||||
Out of Stock
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{{ url_for('auth.login') }}" class="btn-login-to-buy" onclick="event.stopPropagation();">
|
||||
Login to Buy
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="no-products">
|
||||
<div class="no-products-icon">👕</div>
|
||||
<h3>No products found</h3>
|
||||
<p>Try adjusting your filters or search criteria</p>
|
||||
<a href="{{ url_for('main.products') }}" class="btn-view-all">
|
||||
View All Products
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
115
templates/register.html
Normal file
115
templates/register.html
Normal file
@@ -0,0 +1,115 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/auth.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/register.js') }}" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="auth-container">
|
||||
<h2>Create Account</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash {{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" class="auth-form">
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<input type="email" id="email" name="email" class="form-control" placeholder="you@example.com" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<div class="password-container">
|
||||
<input type="password" id="password" name="password" class="form-control" placeholder="Create a strong password"
|
||||
required oninput="checkPasswordStrength(this.value)">
|
||||
<button type="button" class="toggle-password" onclick="togglePassword('password')">
|
||||
👁️
|
||||
</button>
|
||||
</div>
|
||||
<div class="password-strength">
|
||||
<div class="strength-bar" id="strengthBar"></div>
|
||||
</div>
|
||||
<div class="password-hints" id="passwordHints">
|
||||
<strong>Password should contain:</strong>
|
||||
<ul>
|
||||
<li id="lengthHint">At least 8 characters</li>
|
||||
<li id="uppercaseHint">One uppercase letter</li>
|
||||
<li id="lowercaseHint">One lowercase letter</li>
|
||||
<li id="numberHint">One number</li>
|
||||
<li id="specialHint">One special character</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="confirm_password">Confirm Password</label>
|
||||
<div class="password-container">
|
||||
<input type="password" id="confirm_password" name="confirm_password" class="form-control"
|
||||
placeholder="Confirm your password" required oninput="checkPasswordMatch()">
|
||||
<button type="button" class="toggle-password" onclick="togglePassword('confirm_password')">
|
||||
👁️
|
||||
</button>
|
||||
</div>
|
||||
<div id="passwordMatch" style="margin-top: 5px;"></div>
|
||||
</div>
|
||||
|
||||
<div class="terms">
|
||||
<input type="checkbox" id="terms" name="terms" required>
|
||||
<label for="terms">
|
||||
I agree to the <a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-submit">
|
||||
Create Account
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-links">
|
||||
<a href="{{ url_for('auth.login') }}" class="auth-link">
|
||||
Already have an account? Sign in
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="social-login">
|
||||
<div class="social-divider">
|
||||
<span>Or sign up with</span>
|
||||
</div>
|
||||
|
||||
<div class="social-buttons">
|
||||
<button type="button" class="btn-social google">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
|
||||
<path fill="currentColor"
|
||||
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
||||
<path fill="currentColor"
|
||||
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
|
||||
<path fill="currentColor"
|
||||
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
|
||||
</svg>
|
||||
Google
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn-social facebook">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
|
||||
</svg>
|
||||
Facebook
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
63
templates/user_profile.html
Normal file
63
templates/user_profile.html
Normal file
@@ -0,0 +1,63 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/profile.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="profile-container">
|
||||
<h2>My Profile</h2>
|
||||
|
||||
<div class="profile-info">
|
||||
<p><strong>Email:</strong> {{ current_user.email }}</p>
|
||||
</div>
|
||||
|
||||
<div class="orders-section">
|
||||
<h3>My Orders</h3>
|
||||
|
||||
{% if orders %}
|
||||
<table class="orders-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order ID</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
<th>Items</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in orders %}
|
||||
<tr>
|
||||
<td class="order-id">#{{ order.id }}</td>
|
||||
<td class="order-date">{{ order.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>
|
||||
<span class="status-badge status-{{ order.status }}">
|
||||
{{ order.status|title }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="order-items">{{ order.items|length }}</td>
|
||||
<td class="order-actions">
|
||||
<a href="{{ url_for('user.order_details', order_id=order.id) }}" class="view-details-link">
|
||||
View Details
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="no-orders">
|
||||
<p>You haven't placed any orders yet.</p>
|
||||
<a href="{{ url_for('main.products') }}" class="shop-now-link">
|
||||
Start Shopping
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<a href="{{ url_for('main.home') }}" class="back-link">
|
||||
← Back to Home
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user