Add complete customer authentication UI with login, registration, forgot password, and dashboard pages. Templates Added: - app/templates/shop/account/login.html - Two-column layout with vendor branding - Email/password login with validation - Password visibility toggle - "Remember me" functionality - Error/success alerts - Loading states with spinner - app/templates/shop/account/register.html - Customer registration form - Client-side validation (password strength, email format) - Marketing consent checkbox - Confirm password matching - app/templates/shop/account/forgot-password.html - Password reset request page - Email validation - Success confirmation - app/templates/shop/account/dashboard.html - Customer account dashboard - Overview of orders, profile, addresses Styles Added: - static/shared/css/auth.css - Authentication page styling - Two-column layout system - Form components and validation states - Theme-aware with CSS variables - Dark mode support - Mobile responsive - static/shared/css/base.css updates - Enhanced utility classes - Additional form styles - Improved button states Documentation Added: - docs/frontend/shop/authentication-pages.md - Comprehensive guide to auth page implementation - Component architecture - API integration patterns - Theme customization - docs/development/CUSTOMER_AUTHENTICATION_IMPLEMENTATION.md - Implementation details and technical decisions - Security considerations - Testing procedures - docs/development/CUSTOMER_AUTH_SUMMARY.md - Quick reference guide - Endpoints and flows - Updated docs/frontend/shop/architecture.md - Added authentication section - Documented all auth pages - Updated docs/frontend/shop/page-templates.md - Added auth template documentation - Updated mkdocs.yml - Added new documentation pages to navigation Features: - Full theme integration with vendor branding - Alpine.js reactive components - Tailwind CSS utility-first styling - Client and server-side validation - JWT token management - Multi-access routing support (domain/subdomain/path) - Error handling with user-friendly messages - Loading states and animations - Mobile responsive design - Dark mode support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
229 lines
11 KiB
HTML
229 lines
11 KiB
HTML
{# app/templates/shop/account/dashboard.html #}
|
|
{% extends "shop/base.html" %}
|
|
|
|
{% block title %}My Account - {{ vendor.name }}{% endblock %}
|
|
|
|
{% block alpine_data %}accountDashboard(){% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<!-- Page Header -->
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">My Account</h1>
|
|
<p class="mt-2 text-gray-600 dark:text-gray-400">Welcome back, {{ user.first_name }}!</p>
|
|
</div>
|
|
|
|
<!-- Dashboard Grid -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
|
|
|
<!-- Orders Card -->
|
|
<a href="{{ base_url }}shop/account/orders"
|
|
class="block bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-lg transition-shadow p-6 border border-gray-200 dark:border-gray-700">
|
|
<div class="flex items-center mb-4">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-8 w-8 text-primary" style="color: var(--color-primary)" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-4">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Orders</h3>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">View order history</p>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<p class="text-2xl font-bold text-primary" style="color: var(--color-primary)">{{ user.total_orders }}</p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Total Orders</p>
|
|
</div>
|
|
</a>
|
|
|
|
<!-- Profile Card -->
|
|
<a href="{{ base_url }}shop/account/profile"
|
|
class="block bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-lg transition-shadow p-6 border border-gray-200 dark:border-gray-700">
|
|
<div class="flex items-center mb-4">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-8 w-8 text-primary" style="color: var(--color-primary)" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-4">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Profile</h3>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">Edit your information</p>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300 truncate">{{ user.email }}</p>
|
|
</div>
|
|
</a>
|
|
|
|
<!-- Addresses Card -->
|
|
<a href="{{ base_url }}shop/account/addresses"
|
|
class="block bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-lg transition-shadow p-6 border border-gray-200 dark:border-gray-700">
|
|
<div class="flex items-center mb-4">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-8 w-8 text-primary" style="color: var(--color-primary)" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-4">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Addresses</h3>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">Manage addresses</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Account Summary -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 border border-gray-200 dark:border-gray-700">
|
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-6">Account Summary</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">Customer Since</p>
|
|
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ user.created_at.strftime('%B %Y') }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">Total Orders</p>
|
|
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ user.total_orders }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">Customer Number</p>
|
|
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ user.customer_number }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="mt-8 flex justify-end">
|
|
<button @click="showLogoutModal = true"
|
|
class="px-6 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors">
|
|
Logout
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logout Confirmation Modal -->
|
|
<div x-show="showLogoutModal"
|
|
x-cloak
|
|
class="fixed inset-0 z-50 overflow-y-auto"
|
|
aria-labelledby="modal-title"
|
|
role="dialog"
|
|
aria-modal="true">
|
|
<!-- Background overlay -->
|
|
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
|
<!-- Overlay backdrop -->
|
|
<div x-show="showLogoutModal"
|
|
x-transition:enter="ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="showLogoutModal = false"
|
|
class="fixed inset-0 bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-75 transition-opacity"
|
|
aria-hidden="true">
|
|
</div>
|
|
|
|
<!-- Center modal -->
|
|
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
|
|
|
<!-- Modal panel -->
|
|
<div x-show="showLogoutModal"
|
|
x-transition:enter="ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
|
x-transition:leave="ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
|
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
|
|
|
|
<div class="sm:flex sm:items-start">
|
|
<!-- Icon -->
|
|
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 dark:bg-red-900 sm:mx-0 sm:h-10 sm:w-10">
|
|
<svg class="h-6 w-6 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white" id="modal-title">
|
|
Logout Confirmation
|
|
</h3>
|
|
<div class="mt-2">
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
Are you sure you want to logout? You'll need to sign in again to access your account.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
|
<button @click="confirmLogout()"
|
|
type="button"
|
|
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm transition-colors">
|
|
Logout
|
|
</button>
|
|
<button @click="showLogoutModal = false"
|
|
type="button"
|
|
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-700 text-base font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm transition-colors">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script>
|
|
function accountDashboard() {
|
|
return {
|
|
...shopLayoutData(),
|
|
showLogoutModal: false,
|
|
|
|
confirmLogout() {
|
|
// Close modal
|
|
this.showLogoutModal = false;
|
|
|
|
fetch('/api/v1/shop/auth/logout', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => {
|
|
if (response.ok) {
|
|
// Clear localStorage token if any
|
|
localStorage.removeItem('customer_token');
|
|
|
|
// Show success message
|
|
this.showToast('Logged out successfully', 'success');
|
|
|
|
// Redirect to login page
|
|
setTimeout(() => {
|
|
window.location.href = '{{ base_url }}shop/account/login';
|
|
}, 500);
|
|
} else {
|
|
console.error('Logout failed with status:', response.status);
|
|
this.showToast('Logout failed', 'error');
|
|
// Still redirect on failure (cookie might be deleted)
|
|
setTimeout(() => {
|
|
window.location.href = '{{ base_url }}shop/account/login';
|
|
}, 1000);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Logout error:', error);
|
|
this.showToast('Logout failed', 'error');
|
|
// Redirect anyway
|
|
setTimeout(() => {
|
|
window.location.href = '{{ base_url }}shop/account/login';
|
|
}, 1000);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|