aboutsummaryrefslogtreecommitdiff
path: root/store/templates
diff options
context:
space:
mode:
Diffstat (limited to 'store/templates')
-rw-r--r--store/templates/store/.book_list.html.kate-swp0
-rw-r--r--store/templates/store/add_edit_book.html84
-rw-r--r--store/templates/store/base.html83
-rw-r--r--store/templates/store/book_detail.html69
-rw-r--r--store/templates/store/book_list.html221
-rw-r--r--store/templates/store/cart.html52
-rw-r--r--store/templates/store/checkout.html47
-rw-r--r--store/templates/store/delete_book_confirm.html16
-rw-r--r--store/templates/store/import_books.html46
-rw-r--r--store/templates/store/login.html32
-rw-r--r--store/templates/store/order_list.html56
-rw-r--r--store/templates/store/order_management.html111
-rw-r--r--store/templates/store/profile.html39
-rw-r--r--store/templates/store/register.html49
14 files changed, 905 insertions, 0 deletions
diff --git a/store/templates/store/.book_list.html.kate-swp b/store/templates/store/.book_list.html.kate-swp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/store/templates/store/.book_list.html.kate-swp
diff --git a/store/templates/store/add_edit_book.html b/store/templates/store/add_edit_book.html
new file mode 100644
index 0000000..c87a9c7
--- /dev/null
+++ b/store/templates/store/add_edit_book.html
@@ -0,0 +1,84 @@
+{% extends 'store/base.html' %}
+{% load static %}
+
+{% block content %}
+<div style="display: flex; justify-content: center; margin-top: 3rem; margin-bottom: 3rem;">
+ <mdui-card style="width: 100%; max-width: 600px; padding: 2rem;">
+ <h2 style="margin-top: 0; text-align: center;">{{ title }}</h2>
+ <form method="POST" enctype="multipart/form-data">
+ {% csrf_token %}
+ {% if form.non_field_errors %}
+ <div style="color: var(--mdui-color-error); margin-bottom: 1rem;">
+ {% for error in form.non_field_errors %}
+ <p>{{ error }}</p>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ {% for field in form %}
+ {% if field.name != 'cover_url' and field.name != 'cover_upload' %}
+ <div style="margin-bottom: 1rem;">
+ <mdui-text-field
+ label="{{ field.label }}"
+ name="{{ field.name }}"
+ value="{{ field.value|default:'' }}"
+ {% if field.field.required %}required{% endif %}
+ {% if field.name == 'description' %}rows="5"{% endif %}
+ {% if field.field.widget.input_type == 'number' %}type="number"{% endif %}
+ style="width: 100%;"
+ ></mdui-text-field>
+ </div>
+ {% endif %}
+ {% endfor %}
+
+ <mdui-select label="Cover Image Type" value="url" style="margin-top: 0.5rem; margin-bottom: 1rem; width: 100%;" onchange="toggleCoverInput(this.value);">
+ <mdui-menu-item value="url">URL</mdui-menu-item>
+ <mdui-menu-item value="file">File Upload</mdui-menu-item>
+ </mdui-select>
+
+ <div id="cover-url-input" style="margin-bottom: 1rem;">
+ <mdui-text-field label="{{ form.cover_url.label }}" name="{{ form.cover_url.name }}" value="{{ form.cover_url.value|default:'' }}" style="width: 100%;"></mdui-text-field>
+ </div>
+ <div id="cover-file-input">
+ <input type="file" name="{{ form.cover_upload.name }}" style="display: none">
+ </div>
+
+ {% if form.cover_url.errors or form.cover_upload.errors or form.non_field_errors %}
+ <div style="color: var(--mdui-color-error); font-size: 0.875rem; margin-top: 0.25rem;">
+ {{ form.cover_url.errors }}
+ {{ form.cover_upload.errors }}
+ {{ form.non_field_errors }}
+ </div>
+ {% endif %}
+
+ <div style="display: flex; gap: 1rem; margin-top: 2rem; justify-content: flex-end;">
+ <mdui-button href="{% if book %}{% url 'book_detail' book.id %}{% else %}{% url 'book_list' %}{% endif %}" variant="text" icon="cancel">Cancel</mdui-button>
+ <mdui-button type="submit" icon="save">Save</mdui-button>
+ </div>
+ </form>
+{% endblock %}
+
+{% block extra_script %}
+ <script>
+ function toggleCoverInput(type) {
+ const urlInput = document.getElementById('cover-url-input');
+ const fileInput = document.getElementById('cover-file-input');
+ const urlField = urlInput.querySelector('mdui-text-field');
+ const fileField = fileInput.querySelector('input');
+
+ if (type === 'url') {
+ urlInput.style.display = 'block';
+ urlField.disabled = false;
+ } else {
+ urlInput.style.display = 'none';
+ urlField.disabled = true;
+ fileField.showPicker();
+ }
+ }
+ // Initialize on page load
+ document.addEventListener('DOMContentLoaded', () => {
+ const select = document.querySelector('mdui-select');
+ toggleCoverInput(select.value);
+ });
+ </script>
+{% endblock %}
diff --git a/store/templates/store/base.html b/store/templates/store/base.html
new file mode 100644
index 0000000..18a9557
--- /dev/null
+++ b/store/templates/store/base.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html lang="en" class="mdui-theme-auto">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>SEU Bookstore</title>
+ <link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css">
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
+ <style>
+ html, body { height: 100%; margin: 0; padding: 0; }
+ body { font-family: 'Roboto', sans-serif; }
+ .main-container { max-width: 1200px; margin: 0 auto; padding: 2rem 1rem; }
+ </style>
+ <script src="https://unpkg.com/mdui@2/mdui.global.js"></script>
+</head>
+<body>
+ <mdui-layout full-height>
+ <mdui-top-app-bar variant="center-aligned" style="margin: 0 2rem;">
+ <mdui-top-app-bar-title href="/">SEU Bookstore</mdui-top-app-bar-title>
+
+ <mdui-dropdown>
+ <mdui-avatar slot="trigger" {% if user.is_authenticated and user.avatar %}src="{{ user.avatar.url }}"{% else %}icon="people_alt"{% endif %} style="cursor: pointer;">{% if user.is_authenticated and not user.avatar %}{{ user.username|first|upper }}{% endif %}</mdui-avatar>
+ <mdui-menu>
+ {% if user.is_authenticated %}
+ <mdui-menu-item href="{% url 'profile' %}" icon="person">Profile</mdui-menu-item>
+ <mdui-menu-item href="{% url 'logout' %}" icon="logout">Logout</mdui-menu-item>
+ {% else %}
+ <mdui-menu-item href="{% url 'login' %}" icon="login">Login</mdui-menu-item>
+ <mdui-menu-item href="{% url 'register' %}" icon="person_add">Register</mdui-menu-item>
+ {% endif %}
+ </mdui-menu>
+ </mdui-dropdown>
+ </mdui-top-app-bar>
+
+ <mdui-navigation-rail divider>
+ <mdui-navigation-rail-item icon="library_books" href="{% url 'book_list' %}">Books</mdui-navigation-rail-item>
+ <mdui-navigation-rail-item icon="shopping_cart" href="{% url 'cart_detail' %}">Cart</mdui-navigation-rail-item>
+ <mdui-navigation-rail-item icon="receipt_long" href="{% url 'order_list' %}">Orders</mdui-navigation-rail-item>
+
+ <div style="flex-grow: 1"></div>
+
+ {% if user.is_staff %}
+ <mdui-navigation-rail-item slot="bottom" href="{% url 'order_management' %}" icon="local_shipping">Manage</mdui-navigation-rail-item>
+ {% endif %}
+ </mdui-navigation-rail>
+
+
+ <mdui-layout-main>
+ <div class="main-container">
+ {% block content %}
+ {% endblock %}
+ </div>
+ </mdui-layout-main>
+ </mdui-layout>
+
+ <script>
+ window.mdui.setColorScheme("#9ecaff");
+ document.addEventListener('DOMContentLoaded', function () {
+ const currentPath = window.location.pathname;
+ const railItems = document.querySelectorAll('mdui-navigation-rail-item');
+
+ railItems.forEach(item => {
+ if (item.getAttribute('href') === currentPath) {
+ item.active = true;
+ }
+ });
+
+ {% if messages %}
+ {% for message in messages %}
+ const snackbar = mdui.snackbar({
+ message: '{{ message }}',
+ closeable: true
+ });
+ {% endfor %}
+ {% endif %}
+ });
+ </script>
+ {% block extra_script %}{% endblock %}
+</body>
+</html>
+
diff --git a/store/templates/store/book_detail.html b/store/templates/store/book_detail.html
new file mode 100644
index 0000000..e9cc2b8
--- /dev/null
+++ b/store/templates/store/book_detail.html
@@ -0,0 +1,69 @@
+{% extends 'store/base.html' %}
+
+{% block content %}
+ <div style="display: flex; gap: 2rem; flex-wrap: wrap; margin-bottom: 2rem;">
+ <div style="flex: 1; min-width: 300px; max-width: 400px;">
+ {% if book.cover %}
+ <img src="{{ book.cover }}" alt="{{ book.title }}" style="width: 100%; border-radius: 12px; box-shadow: var(--mdui-elevation-level2);">
+ {% else %}
+ <div style="width: 100%; height: 400px; background-color: var(--mdui-color-surface-container-highest); display: flex; align-items: center; justify-content: center; border-radius: 12px; color: var(--mdui-color-on-surface-variant);">
+ No Image Available
+ </div>
+ {% endif %}
+ </div>
+ <div style="flex: 2; min-width: 300px;">
+ <div class="mdui-prose">
+ <h1>{{ book.title }}</h1>
+ <h3>{{ book.author }}</h3>
+ <p><strong>Publisher:</strong> {{ book.publisher }}</p>
+ <p><strong>ISBN:</strong> {{ book.isbn }}</p>
+ <p><strong>Price:</strong> {{ book.price }}</p>
+ <p><strong>Stock:</strong> {{ book.stock }}</p>
+ <p>{{ book.description|safe }}</p>
+ </div>
+
+ <div style="margin-top: 1.5rem; display: flex; gap: 1rem; align-items: center; flex-wrap: wrap;">
+ <form action="{% url 'add_to_cart' book.id %}" method="POST" style="display: flex; align-items: center; gap: 1rem;">
+ {% csrf_token %}
+ <mdui-text-field type="number" name="quantity" value="1" min="1" max="{{ book.stock }}" style="width: 80px;"></mdui-text-field>
+ <mdui-button type="submit" variant="filled" icon="shopping_cart">Add to Cart</mdui-button>
+ </form>
+ <mdui-button variant="outlined" href="{% url 'book_list' %}" icon="arrow_back">Back to List</mdui-button>
+
+ {% if user.is_staff %}
+ <mdui-button variant="tonal" href="{% url 'edit_book' book.id %}" icon="edit">Edit</mdui-button>
+ <mdui-button variant="text" style="color: var(--mdui-color-error);" href="{% url 'delete_book' book.id %}" icon="delete">Delete</mdui-button>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+
+ <mdui-divider></mdui-divider>
+
+ <div style="margin-top: 2rem;">
+ <h2>Comments</h2>
+ {% if user.is_authenticated %}
+ <form action="{% url 'post_comment' book.id %}" method="POST" style="margin-bottom: 2rem; max-width: 600px;">
+ {% csrf_token %}
+ <mdui-text-field name="content" label="Add a comment" rows="3" required style="width: 100%; margin-bottom: 1rem;"></mdui-text-field>
+ <mdui-button type="submit" icon="send">Post Comment</mdui-button>
+ </form>
+ {% else %}
+ <p><a href="{% url 'login' %}">Login</a> to post a comment.</p>
+ {% endif %}
+
+ <div style="display: flex; flex-direction: column; gap: 1rem;">
+ {% for comment in comments %}
+ <mdui-card variant="outlined" style="padding: 1rem;">
+ <div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;">
+ <span style="font-weight: bold;">{{ comment.user.username }}</span>
+ <span style="color: var(--mdui-color-on-surface-variant); font-size: 0.9em;">{{ comment.createdAt|date:"M d, Y H:i" }}</span>
+ </div>
+ <div>{{ comment.content }}</div>
+ </mdui-card>
+ {% empty %}
+ <p>No comments yet.</p>
+ {% endfor %}
+ </div>
+ </div>
+{% endblock %}
diff --git a/store/templates/store/book_list.html b/store/templates/store/book_list.html
new file mode 100644
index 0000000..5d68340
--- /dev/null
+++ b/store/templates/store/book_list.html
@@ -0,0 +1,221 @@
+{% extends 'store/base.html' %}
+{% load mathfilters %}
+{% block content %}
+<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem">
+ <div style="display: flex; justify-content: space-between; align-items: center;">
+ <h1>Books</h1>
+ <div style="display: flex; gap: 0.5rem; align-items: center">
+ <mdui-button icon="search" id="search-toggle">Search</mdui-button>
+ {% if user.is_staff %}
+ <mdui-button href="{% url 'import_books' %}" icon="upload">Import Books</mdui-button>
+ <mdui-button href="{% url 'add_book' %}" icon="add">Add Book</mdui-button>
+ {% endif %}
+ </div>
+ </div>
+
+ <form id="search-form" method="GET" style="display: {% if query %}flex{% else %}none{% endif %}; gap: 1rem; align-items: flex-start;">
+ <mdui-text-field
+ name="q"
+ value="{{ query|default:'' }}"
+ placeholder="Search by title or author"
+ icon="search"
+ style="flex-grow: 1;"
+ clearable
+ ></mdui-text-field>
+ <mdui-button type="submit" variant="filled" icon="search">Search</mdui-button>
+ {% if query %}
+ <mdui-button href="{% url 'book_list' %}" variant="text" icon="clear">Clear</mdui-button>
+ {% endif %}
+ </form>
+</div>
+
+<div style="
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1.5rem;
+ ">
+ {% for book in books %}
+ <mdui-card clickable id="{% url 'book_detail' book.id %}">
+ <div style="display: flex; flex-direction: column">
+ {% if book.cover %}
+ <img src="{{ book.cover }}" alt="{{ book.title }}"
+ style="width: 100%; height: 250px; object-fit: cover; flex-shrink: 0" />
+ {% else %}
+ <div style="
+ height: 250px;
+ background-color: var(--mdui-color-surface-container-highest);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--mdui-color-on-surface-variant);
+ flex-shrink: 0;
+ ">
+ No Image
+ </div>
+ {% endif %}
+
+ <div style="
+ padding: 1rem;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ ">
+ <div style="margin-bottom: 1rem">
+ <h3 style="
+ margin: 0 0 0.5rem 0;
+ font-size: 1.25rem;
+ line-height: 1.4;
+ color: var(--mdui-color-on-surface);
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ ">
+ {{ book.title }}
+ </h3>
+ <p style="
+ margin: 0;
+ color: var(--mdui-color-on-surface-variant);
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ ">
+ {{ book.author }}
+ </p>
+ </div>
+
+ <div style="
+ margin-top: auto;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-top: 1rem;
+ border-top: 1px solid var(--mdui-color-surface-container-highest);
+ ">
+ <span style="font-weight: bold; font-size: 1.1rem; color: var(--mdui-color-primary);">{{ book.price }}¥</span>
+ <div>
+ <form action="{% url 'add_to_cart' book.id %}" method="POST" style="display: inline">
+ {% csrf_token %}
+ <mdui-button type="submit" variant="tonal">Add to Cart</mdui-button>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+ </mdui-card>
+ {% empty %}
+ <div style="
+ grid-column: 1 / -1;
+ text-align: center;
+ padding: 3rem;
+ color: var(--mdui-color-on-surface-variant);
+ ">
+ <mdui-icon name="library_books--outlined"
+ style="font-size: 4rem; display: block; margin: 0 auto 1rem"></mdui-icon>
+ <div class="mdui-prose">
+ <h2>No books yet</h2>
+ {% if query %}
+ <p>We couldn't find any books matching "{{ query }}".</p>
+ <mdui-button href="{% url 'book_list' %}" variant="text">Clear Search</mdui-button>
+ {% else %}
+ <p>Check back later!</p>
+ {% endif %}
+ </div>
+ </div>
+ {% endfor %}
+</div>
+
+{% if books.paginator.num_pages > 1 %}
+<div style="
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+ margin-top: 3rem;
+ width: 100%;
+ ">
+ <div style="
+ display: grid;
+ grid-template-columns: 1fr auto 1fr;
+ gap: 1rem;
+ align-items: center;
+ width: 100%;
+ max-width: 600px;
+ ">
+ <div style="justify-self: end">
+ <mdui-button {% if books.has_previous %}onclick="jumpToPage({{ books.previous_page_number }})" {% else
+ %}disabled{% endif %} variant="outlined" icon="arrow_back">
+ {% if books.has_previous %}{{ books.number|sub:1 }}{% endif %}
+ </mdui-button>
+ </div>
+
+ <div style="
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ justify-content: center;
+ ">
+ <mdui-text-field variant="outlined" id="page-jump-input" value="{{ books.number }}"
+ style="width: 60px"></mdui-text-field>
+ <span style="color: var(--mdui-color-on-surface); white-space: nowrap">
+ of {{ books.paginator.num_pages }}
+ </span>
+ </div>
+
+ <div style="justify-self: start">
+ <mdui-button {% if books.has_next %}onclick="jumpToPage({{ books.next_page_number }})" {% else %}disabled{% endif %} variant="outlined" end-icon="arrow_forward">
+ {% if books.has_next %}{{ books.number|add:1 }}{% endif %}
+ </mdui-button>
+ </div>
+ </div>
+</div>
+{% endif %}
+{% endblock %}
+
+{% block extra_script %}
+<script>
+ const jumpToPage = (i) => {
+ const page = parseInt(i);
+ if (!isNaN(page)) {
+ const params = new URLSearchParams(window.location.search);
+ params.set("page", page);
+ window.location.search = params.toString();
+ }
+ };
+
+ document.addEventListener("DOMContentLoaded", function () {
+ const toggleBtn = document.getElementById("search-toggle");
+ const searchForm = document.getElementById("search-form");
+
+ toggleBtn.addEventListener('click', () => {
+ if (searchForm.style.display === 'none') {
+ searchForm.style.display = 'flex';
+ } else {
+ searchForm.style.display = 'none';
+ }
+ });
+
+ const pageInput = document.getElementById("page-jump-input");
+ if (pageInput) {
+
+ pageInput.addEventListener("keydown", (e) => {
+ if (e.key === "Enter") jumpToPage(pageInput.value);
+ });
+ pageInput.addEventListener("change", () => jumpToPage(pageInput.value));
+ }
+
+ const bookCards = document.querySelectorAll("mdui-card");
+ bookCards.forEach((card) => {
+ const form = card.querySelector("form");
+ if (form) {
+ card.addEventListener("click", function (event) {
+ if (!form.contains(event.target)) {
+ window.location.href = this.id;
+ }
+ });
+ }
+ });
+ });
+</script>
+{% endblock %}
diff --git a/store/templates/store/cart.html b/store/templates/store/cart.html
new file mode 100644
index 0000000..f634c58
--- /dev/null
+++ b/store/templates/store/cart.html
@@ -0,0 +1,52 @@
+{% extends 'store/base.html' %}
+
+{% block content %}
+ <h2>Shopping Cart</h2>
+ {% if cart_items %}
+ <div style="overflow-x: auto;">
+ <table style="width: 100%; border-collapse: collapse; margin-bottom: 2rem;">
+ <thead>
+ <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant); text-align: left;">
+ <th style="padding: 1rem;">Book</th>
+ <th style="padding: 1rem;">Price</th>
+ <th style="padding: 1rem;">Quantity</th>
+ <th style="padding: 1rem;">Total</th>
+ <th style="padding: 1rem;">Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for item in cart_items %}
+ <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant);">
+ <td style="padding: 1rem;">{{ item.book.title }}</td>
+ <td style="padding: 1rem;">{{ item.book.price }}¥</td>
+ <td style="padding: 1rem;">
+ <form action="{% url 'update_cart_quantity' item.book.id %}" method="POST" style="display: flex; align-items: center; gap: 0.5rem;">
+ {% csrf_token %}
+ <input type="number" name="quantity" value="{{ item.quantity }}" min="1" style="width: 60px; padding: 0.5rem; border: 1px solid var(--mdui-color-outline); border-radius: 4px;">
+ <mdui-button type="submit" variant="text" icon="refresh">Update</mdui-button>
+ </form>
+ </td>
+ <td style="padding: 1rem;">{{ item.total_price }}¥</td>
+ <td style="padding: 1rem;">
+ <mdui-button href="{% url 'remove_from_cart' item.book.id %}" variant="text" style="color: var(--mdui-color-error);" icon="delete">Remove</mdui-button>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ <tfoot>
+ <tr>
+ <td colspan="3" style="text-align: right; padding: 1rem;"><strong>Total:</strong></td>
+ <td style="padding: 1rem;"><strong>{{ total_price }}¥</strong></td>
+ <td></td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ <div style="display: flex; justify-content: flex-end;">
+ <mdui-button href="{% url 'checkout' %}" variant="filled" icon="payment">Checkout</mdui-button>
+ </div>
+ {% else %}
+ <p>Your cart is empty.</p>
+ <mdui-button href="{% url 'book_list' %}" variant="filled" icon="shopping_basket">Go Shopping</mdui-button>
+ {% endif %}
+{% endblock %}
diff --git a/store/templates/store/checkout.html b/store/templates/store/checkout.html
new file mode 100644
index 0000000..11dc99c
--- /dev/null
+++ b/store/templates/store/checkout.html
@@ -0,0 +1,47 @@
+{% extends 'store/base.html' %}
+
+{% block content %}
+ <h2>Checkout</h2>
+ <div style="display: flex; gap: 2rem; flex-wrap: wrap;">
+ <div style="flex: 2; min-width: 300px;">
+ <h4>Order Summary</h4>
+ <table style="width: 100%; border-collapse: collapse;">
+ <thead>
+ <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant); text-align: left;">
+ <th style="padding: 0.5rem;">Book</th>
+ <th style="padding: 0.5rem;">Price</th>
+ <th style="padding: 0.5rem;">Quantity</th>
+ <th style="padding: 0.5rem;">Total</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for item in cart_items %}
+ <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant);">
+ <td style="padding: 0.5rem;">{{ item.book.title }}</td>
+ <td style="padding: 0.5rem;">${{ item.book.price }}</td>
+ <td style="padding: 0.5rem;">{{ item.quantity }}</td>
+ <td style="padding: 0.5rem;">${{ item.total_price }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ <tfoot>
+ <tr>
+ <td colspan="3" style="text-align: right; padding: 0.5rem;"><strong>Total:</strong></td>
+ <td style="padding: 0.5rem;"><strong>${{ total_price }}</strong></td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ <div style="flex: 1; min-width: 300px;">
+ <h4>Shipping Information</h4>
+ <form method="POST">
+ {% csrf_token %}
+ <div style="margin-bottom: 1rem;">
+ <mdui-text-field label="Address" name="address" rows="3" required value="{{ user.address|default:'' }}" style="width: 100%;"></mdui-text-field>
+ </div>
+ <mdui-button type="submit" full-width icon="check">Place Order</mdui-button>
+ </form>
+ </div>
+ </div>
+{% endblock %}
+
diff --git a/store/templates/store/delete_book_confirm.html b/store/templates/store/delete_book_confirm.html
new file mode 100644
index 0000000..8450973
--- /dev/null
+++ b/store/templates/store/delete_book_confirm.html
@@ -0,0 +1,16 @@
+{% extends 'store/base.html' %}
+{% load static %}
+
+{% block content %}
+<div style="display: flex; justify-content: center; margin-top: 3rem;">
+ <mdui-card style="width: 100%; max-width: 400px; padding: 2rem;">
+ <h2 style="margin-top: 0; text-align: center;">Confirm Deletion</h2>
+ <p style="text-align: center;">Are you sure you want to delete "<strong>{{ book.title }}</strong>"?</p>
+ <form method="POST" style="display: flex; justify-content: center; gap: 1rem; margin-top: 2rem;">
+ {% csrf_token %}
+ <mdui-button href="{% url 'book_detail' book.id %}" variant="text" icon="cancel">Cancel</mdui-button>
+ <mdui-button type="submit" style="background-color: var(--mdui-color-error); color: var(--mdui-color-on-error);" icon="delete">Delete</mdui-button>
+ </form>
+ </mdui-card>
+</div>
+{% endblock %}
diff --git a/store/templates/store/import_books.html b/store/templates/store/import_books.html
new file mode 100644
index 0000000..81d4f0e
--- /dev/null
+++ b/store/templates/store/import_books.html
@@ -0,0 +1,46 @@
+{% extends 'store/base.html' %}
+{% load static %}
+
+{% block content %}
+<div style="display: flex; justify-content: center; margin-top: 3rem; margin-bottom: 3rem;">
+ <mdui-card style="width: 100%; max-width: 600px; padding: 2rem;">
+ <h2 style="margin-top: 0; text-align: center;">{{ title }}</h2>
+ <div class="mdui-prose" style="margin-bottom: 1.5rem;">
+ <p>Upload a JSONL file to import books. Each line should be a JSON object with a "metadata" key containing book details.</p>
+ </div>
+ <form method="POST" enctype="multipart/form-data">
+ {% csrf_token %}
+ {% if form.non_field_errors %}
+ <div style="color: var(--mdui-color-error); margin-bottom: 1rem;">
+ {% for error in form.non_field_errors %}
+ <p>{{ error }}</p>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ <div style="margin-bottom: 1.5rem;">
+ <div style="display: flex; flex-direction: column; gap: 0.5rem; align-items: center; padding: 2rem; border: 2px dashed var(--mdui-color-outline-variant); border-radius: 12px; background-color: var(--mdui-color-surface-container-low);">
+ <mdui-icon name="upload_file" style="font-size: 3rem; color: var(--mdui-color-primary);"></mdui-icon>
+ <input type="file" name="file" id="jsonl-file-input" accept=".jsonl,.json" required style="display: none;">
+ <mdui-button type="button" variant="tonal" onclick="document.getElementById('jsonl-file-input').click()">Select JSONL File</mdui-button>
+ <div id="file-name-display" style="margin-top: 0.5rem; color: var(--mdui-color-on-surface-variant); font-size: 0.9rem;">No file selected</div>
+ </div>
+ </div>
+
+ <div style="display: flex; gap: 1rem; margin-top: 2rem; justify-content: flex-end;">
+ <mdui-button href="{% url 'book_list' %}" variant="text">Cancel</mdui-button>
+ <mdui-button type="submit" icon="upload">Import</mdui-button>
+ </div>
+ </form>
+ </mdui-card>
+</div>
+{% endblock %}
+
+{% block extra_script %}
+ <script>
+ document.getElementById('jsonl-file-input').addEventListener('change', function(e) {
+ const fileName = e.target.files[0] ? e.target.files[0].name : 'No file selected';
+ document.getElementById('file-name-display').textContent = fileName;
+ });
+ </script>
+{% endblock %}
diff --git a/store/templates/store/login.html b/store/templates/store/login.html
new file mode 100644
index 0000000..ff34b5e
--- /dev/null
+++ b/store/templates/store/login.html
@@ -0,0 +1,32 @@
+{% extends 'store/base.html' %}
+{% load static %}
+
+{% block content %}
+<div style="display: flex; justify-content: center; margin-top: 3rem;">
+ <mdui-card style="width: 100%; max-width: 400px; padding: 2rem;">
+ <h2 style="margin-top: 0; text-align: center;">Login</h2>
+ <form method="POST">
+ {% csrf_token %}
+ {% if form.non_field_errors %}
+ <div style="color: var(--mdui-color-error); margin-bottom: 1rem;">
+ {% for error in form.non_field_errors %}
+ <p>{{ error }}</p>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ <div style="margin-bottom: 1rem;">
+ <mdui-text-field label="Username" name="username" required style="width: 100%;"></mdui-text-field>
+ </div>
+ <div style="margin-bottom: 1.5rem;">
+ <mdui-text-field label="Password" name="password" type="password" required style="width: 100%;"></mdui-text-field>
+ </div>
+
+ <mdui-button type="submit" full-width icon="login">Login</mdui-button>
+ </form>
+ <div style="margin-top: 1rem; text-align: center;">
+ <p>Don't have an account? <a href="{% url 'register' %}" style="color: var(--mdui-color-primary);">Register</a></p>
+ </div>
+ </mdui-card>
+</div>
+{% endblock %}
diff --git a/store/templates/store/order_list.html b/store/templates/store/order_list.html
new file mode 100644
index 0000000..c45680e
--- /dev/null
+++ b/store/templates/store/order_list.html
@@ -0,0 +1,56 @@
+{% extends 'store/base.html' %}
+
+{% block content %}
+ <h2>My Orders</h2>
+ {% if orders %}
+ <mdui-collapse accordion>
+ {% for order in orders %}
+ <mdui-collapse-item>
+ <mdui-list-item slot="header"
+ {% if order.status == 1 %}
+ icon="pending_actions"
+ {% elif order.status == 2 %}
+ icon="done_all"
+ {% elif order.status == 3 %}
+ icon="error_outline"
+ {% endif %}>
+ Order #{{ order.id }} - {{ order.createdAt|date:"M d, Y" }} - {{ order.total_price }}¥ -
+ {% if order.status == 1 %}
+ <span style="color: var(--mdui-color-secondary);">Pending</span>
+ {% elif order.status == 2 %}
+ <span style="color: var(--mdui-color-success);">Shipped</span>
+ {% elif order.status == 3 %}
+ <span style="color: var(--mdui-color-error);">Rejected</span>
+ {% endif %}
+ </mdui-list-item>
+ <div style="padding: 1rem;">
+ <p><strong>Shipping Address:</strong> {{ order.address }}</p>
+ <table style="width: 100%; border-collapse: collapse;">
+ <thead>
+ <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant); text-align: left;">
+ <th style="padding: 0.5rem;">Book</th>
+ <th style="padding: 0.5rem;">Price</th>
+ <th style="padding: 0.5rem;">Quantity</th>
+ <th style="padding: 0.5rem;">Subtotal</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for item in order.items.all %}
+ <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant);">
+ <td style="padding: 0.5rem;">{{ item.book.title }}</td>
+ <td style="padding: 0.5rem;">{{ item.bookPrice }}¥</td>
+ <td style="padding: 0.5rem;">{{ item.amount }}</td>
+ <td style="padding: 0.5rem;">{{ item.total_price }}¥</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ </mdui-collapse-item>
+ {% endfor %}
+ </mdui-collapse>
+ {% else %}
+ <p>You haven't placed any orders yet.</p>
+ {% endif %}
+{% endblock %}
+
diff --git a/store/templates/store/order_management.html b/store/templates/store/order_management.html
new file mode 100644
index 0000000..18b6e4b
--- /dev/null
+++ b/store/templates/store/order_management.html
@@ -0,0 +1,111 @@
+{% extends 'store/base.html' %}
+
+{% block content %}
+ <h2>Order Management</h2>
+
+ <div style="overflow-x: auto; background-color: var(--mdui-color-surface); border-radius: 12px; border: 1px solid var(--mdui-color-outline-variant);">
+ <table style="border-collapse: collapse; min-width: 800px;">
+ <thead>
+ <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant); background-color: var(--mdui-color-surface-container);">
+ <th style="padding: 1rem; text-align: center; width: 80px;">ID</th>
+ <th style="padding: 1rem; text-align: center; width: 120px;">Buyer</th>
+ <th style="padding: 1rem; text-align: left;">Address</th>
+ <th style="padding: 1rem; text-align: center; width: 180px;">Date</th>
+ <th style="padding: 1rem; text-align: center; width: 100px;">Total</th>
+ <th style="padding: 1rem; text-align: center; width: 120px;">Status</th>
+ <th style="padding: 1rem; text-align: center; width: 160px;">Actions</th>
+ <th style="padding: 1rem; text-align: center; width: 60px;"></th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for order in orders %}
+ <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant); transition: background-color 0.2s;" class="order-row">
+ <td style="padding: 1rem; text-align: center;">#{{ order.id }}</td>
+ <td style="padding: 1rem; text-align: center;">{{ order.buyer.username }}</td>
+ <td style="padding: 1rem; text-align: left;">
+ <div style="max-width: 250px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
+ {{ order.address }}
+ </div>
+ </td>
+ <td style="padding: 1rem; text-align: center;">{{ order.createdAt|date:"M d, Y H:i" }}</td>
+ <td style="padding: 1rem; text-align: center;">${{ order.total_price }}</td>
+ <td style="padding: 1rem; text-align: center;">
+ {% if order.status == 1 %}
+ <span style="color: var(--mdui-color-secondary); font-weight: 500;">Pending</span>
+ {% elif order.status == 2 %}
+ <span style="color: var(--mdui-color-success); font-weight: 500;">Shipped</span>
+ {% elif order.status == 3 %}
+ <span style="color: var(--mdui-color-error); font-weight: 500;">Rejected</span>
+ {% endif %}
+ </td>
+ <td style="padding: 1rem; text-align: center;">
+ <div style="display: flex; justify-content: center; align-items: center; gap: 0.5rem;">
+ {% if order.status == 1 %}
+ <form action="{% url 'update_order_status' order.id %}" method="POST" style="display: inline-flex;">
+ {% csrf_token %}
+ <input type="hidden" name="status" value="2">
+ <mdui-button-icon type="submit" icon="local_shipping" title="Ship"></mdui-button-icon>
+ </form>
+ <form action="{% url 'update_order_status' order.id %}" method="POST" style="display: inline-flex;">
+ {% csrf_token %}
+ <input type="hidden" name="status" value="3">
+ <mdui-button-icon type="submit" icon="cancel" style="color: var(--mdui-color-error);" title="Reject"></mdui-button-icon>
+ </form>
+ {% else %}
+ <span style="font-size: 0.8rem; opacity: 0.7;">{{ order.get_status_display }}</span>
+ {% endif %}
+ </div>
+ </td>
+ <td style="padding: 1rem; text-align: center;">
+ <mdui-button-icon icon="expand_more" onclick="toggleDetails({{ order.id }}, this)"></mdui-button-icon>
+ </td>
+ </tr>
+ <tr id="details-{{ order.id }}" style="display: none; background-color: var(--mdui-color-surface-container-low);">
+ <td colspan="7" style="padding: 0;">
+ <div style="padding: 1.5rem; border-bottom: 1px solid var(--mdui-color-outline-variant);">
+ <h4 style="margin: 0 0 1rem 0;">Ordered Items</h4>
+ <table style="border-collapse: collapse; background-color: var(--mdui-color-surface); border-radius: 8px; overflow: hidden;">
+ <thead>
+ <tr style="background-color: var(--mdui-color-surface-container); font-size: 0.85rem;">
+ <th style="padding: 0.75rem; text-align: left;">Book</th>
+ <th style="padding: 0.75rem; text-align: center;">Price</th>
+ <th style="padding: 0.75rem; text-align: center;">Quantity</th>
+ <th style="padding: 0.75rem; text-align: right;">Subtotal</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for item in order.items.all %}
+ <tr style="border-bottom: 1px solid var(--mdui-color-surface-container-high);">
+ <td style="padding: 0.75rem;">{{ item.book.title }}</td>
+ <td style="padding: 0.75rem; text-align: center;">${{ item.bookPrice }}</td>
+ <td style="padding: 0.75rem; text-align: center;">{{ item.amount }}</td>
+ <td style="padding: 0.75rem; text-align: right;">${{ item.total_price }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+{% endblock %}
+
+{% block extra_script %}
+ <script>
+ function toggleDetails(id, btn) {
+ const row = document.getElementById('details-' + id);
+ if (row.style.display === 'none') {
+ row.style.display = 'table-row';
+ btn.icon = 'expand_less';
+ btn.closest('tr').style.backgroundColor = 'var(--mdui-color-surface-container-low)';
+ } else {
+ row.style.display = 'none';
+ btn.icon = 'expand_more';
+ btn.closest('tr').style.backgroundColor = '';
+ }
+ }
+ </script>
+{% endblock %}
diff --git a/store/templates/store/profile.html b/store/templates/store/profile.html
new file mode 100644
index 0000000..d06cc7e
--- /dev/null
+++ b/store/templates/store/profile.html
@@ -0,0 +1,39 @@
+{% extends 'store/base.html' %}
+
+{% block content %}
+<div style="display: flex; justify-content: center; margin-top: 3rem; margin-bottom: 3rem;">
+ <mdui-card style="width: 100%; max-width: 600px; padding: 2rem;">
+ <h2 style="margin-top: 0; margin-bottom: 2rem; text-align: center;">User Profile</h2>
+
+ <form method="POST" enctype="multipart/form-data" id="profile-form">
+ {% csrf_token %}
+ <div style="text-align: center; margin-bottom: 2rem; position: relative; cursor: pointer;" onclick="document.getElementById('avatar-input').click();">
+ {% if user.avatar %}
+ <img src="{{ user.avatar.url }}" alt="Avatar" style="width: 150px; height: 150px; object-fit: cover; border-radius: 50%; box-shadow: var(--mdui-elevation-level1);">
+ {% else %}
+ <div style="width: 150px; height: 150px; background-color: var(--mdui-color-secondary-container); color: var(--mdui-color-on-secondary-container); border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 3rem;">
+ {{ user.username|first|upper }}
+ </div>
+ {% endif %}
+ <div style="position: absolute; bottom: 5px; right: calc(50% - 65px); background: rgba(0,0,0,0.5); border-radius: 50%; padding: 5px;">
+ <mdui-icon name="edit" style="color: white;"></mdui-icon>
+ </div>
+ </div>
+ <input type="file" name="avatar" id="avatar-input" style="display: none;" onchange="document.getElementById('profile-form').submit();">
+
+ <div style="display: grid; gap: 1rem;">
+ <mdui-text-field label="Username" value="{{ user.username }}" disabled style="width: 100%;"></mdui-text-field>
+ <mdui-text-field label="Email" value="{{ user.email }}" disabled style="width: 100%;"></mdui-text-field>
+
+ <mdui-text-field label="Full Name" name="name" value="{{ user.name|default:'' }}" style="width: 100%;"></mdui-text-field>
+ <mdui-text-field label="Phone" name="phone" value="{{ user.phone|default:'' }}" style="width: 100%;"></mdui-text-field>
+ <mdui-text-field label="Address" name="address" value="{{ user.address|default:'' }}" rows="3" style="width: 100%;"></mdui-text-field>
+ </div>
+
+ <div style="margin-top: 2rem;">
+ <mdui-button type="submit" full-width>Update Profile</mdui-button>
+ </div>
+ </form>
+ </mdui-card>
+</div>
+{% endblock %}
diff --git a/store/templates/store/register.html b/store/templates/store/register.html
new file mode 100644
index 0000000..3d68916
--- /dev/null
+++ b/store/templates/store/register.html
@@ -0,0 +1,49 @@
+{% extends 'store/base.html' %}
+{% load static %}
+
+{% block content %}
+<div style="display: flex; justify-content: center; margin-top: 3rem; margin-bottom: 3rem;">
+ <mdui-card style="max-width: 500px; padding: 2rem;">
+ <h2 style="margin-top: 0; text-align: center;">Register</h2>
+ <form method="POST">
+ {% csrf_token %}
+ {% if form.non_field_errors %}
+ <div style="color: var(--mdui-color-error); margin-bottom: 1rem;">
+ {% for error in form.non_field_errors %}
+ <p>{{ error }}</p>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ {% for field in form %}
+ <div style="margin-bottom: 1rem;">
+ <mdui-text-field
+ label="{{ field.label }}"
+ name="{{ field.name }}"
+ value="{{ field.value|default:'' }}"
+ {% if field.field.widget.input_type == 'password' %}type="password"{% endif %}
+ {% if field.field.required %}required{% endif %}
+ {% if field.name == 'address' %}rows="3"{% endif %}
+ >
+ {% if field.help_text %}
+ <span slot="helper">{{ field.help_text }}</span>
+ {% endif %}
+ </mdui-text-field>
+ {% if field.errors %}
+ <div style="color: var(--mdui-color-error); font-size: 0.875rem; margin-top: 0.25rem;">
+ {% for error in field.errors %}
+ {{ error }}
+ {% endfor %}
+ </div>
+ {% endif %}
+ </div>
+ {% endfor %}
+
+ <mdui-button type="submit" full-width icon="person_add">Register</mdui-button>
+ </form>
+ <div style="margin-top: 1rem; text-align: center;">
+ <p>Already have an account? <a href="{% url 'login' %}" style="color: var(--mdui-color-primary);">Login</a></p>
+ </div>
+ </mdui-card>
+</div>
+{% endblock %}