Files
Warracker/frontend/status.html
sassanix 47a42fb388 feat: Add currency controls, owner role, OIDC-only mode & key enhancements
This major update introduces several significant new features, critical bug fixes, and key enhancements across the application, focusing on user customization, administration, and system stability.
New Features
Currency Position Control: Allows users to choose whether the currency symbol appears on the left or right of numbers. This setting is applied universally across the app, including warranty cards and forms, and is saved per-user.
Super-Admin (Owner) Role: Implements an immutable Owner role for the primary administrator, who cannot be deleted or demoted. A secure ownership transfer process has been added to the admin settings.
OIDC-Only Login Mode: Adds a site-wide setting to enforce OIDC-only authentication, which hides the traditional username/password login form to streamline SSO environments.
Product Age Tracking & Sorting: Displays the age of a product (e.g., "2 years, 3 months") on warranty cards and adds a new "Sort by Age" option to organize items by their purchase date.
Global View Photo Access: Permits users to view product photos on warranties shared in global view, while ensuring other sensitive documents like invoices remain private to the owner.
Persistent View Scope: The application now remembers the user's last selected view (Global or Personal) and automatically loads the appropriate data on page refresh for a seamless experience.
Export Debug Tools: Introduces a comprehensive debugging system, including a new debug page and API endpoint, to help administrators troubleshoot and verify warranty exports.
Key Enhancements
About Page Redesign: A complete visual overhaul of the "About" page with a modern, card-based layout, prominent community links, and improved branding.
Flexible Apprise Notifications: Admins can now configure Apprise notifications to be a single global summary or sent as per-user messages. Additionally, the scope can be set to include warranties from all users or only the admin's warranties.
Larger Product Photo Thumbnails: Increased the size of product photo thumbnails in all views (grid, list, and table) for better product visibility.
Smart Currency Default: The "Add Warranty" form now intelligently defaults to the user's preferred currency setting, rather than always using USD.
Bug Fixes
Critical OIDC & Proxy Fixes: Resolved two major OIDC issues: a RecursionError with gevent workers and incorrect http:// callback URLs when behind an HTTPS reverse proxy, enabling reliable OIDC login.
Critical User Preferences Persistence: Fixed a bug where user settings for currency symbol and date format were not being saved correctly to the database.
Apprise & Notification Settings: Corrected an issue preventing user notification channel and Apprise timing settings from saving. The Apprise message format is now standardized, and the admin UI has been cleaned up.
CSV Import Currency: Ensured that warranties imported via CSV correctly use the user's preferred currency instead of defaulting to USD.
Maintenance & Refactoring
Authentication System Refactoring: Migrated all authentication-related routes from app.py into a dedicated Flask Blueprint (auth_routes.py) to improve code organization and maintainability.
Legacy Code Cleanup: Removed over 290 lines of orphaned and commented-out legacy OIDC code from the main application file.
2025-06-15 23:26:23 -03:00

825 lines
38 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<!-- Authentication redirect script -->
<script src="auth-redirect.js" data-protected="true"></script>
<!-- Include authentication script first to handle login state immediately -->
<script src="include-auth-new.js"></script>
<!-- File utilities script for secure file handling -->
<script src="file-utils.js"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Status - Warracker</title>
<!-- Favicons -->
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png?v=2">
<link rel="icon" type="image/png" sizes="32x32" href="img/favicon-32x32.png?v=2">
<link rel="apple-touch-icon" sizes="180x180" href="img/favicon-512x512.png">
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" href="style.css?v=20250529005">
<script src="theme-loader.js"></script> <!-- Apply theme early -->
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Load header fix styles to ensure consistent header styling -->
<link rel="stylesheet" href="header-fix.css?v=20250529005">
<!-- Chart.js for visualizations -->
<script src="chart.js"></script>
<!-- Load fix for auth buttons -->
<script src="fix-auth-buttons-loader.js"></script>
<script src="script.js?v=20250529005" defer></script> <!-- Added script.js -->
<script src="status.js" defer></script> <!-- Status page specific functionality -->
<style>
.user-menu {
position: relative;
margin-left: 15px;
}
.user-btn {
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
display: flex;
align-items: center;
font-size: 0.9rem;
padding: 5px 10px;
border-radius: 20px;
transition: background-color 0.3s;
}
.user-btn:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.dark-mode .user-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.user-btn i {
margin-right: 5px;
}
.user-menu-dropdown {
position: absolute;
top: 100%;
right: 0;
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
width: 200px;
z-index: 1000;
display: none;
padding: 10px 0;
margin-top: 5px;
}
.user-menu-dropdown.active {
display: block;
}
.user-info {
padding: 10px 15px;
border-bottom: 1px solid var(--border-color);
margin-bottom: 5px;
}
.user-name {
font-weight: bold;
margin-bottom: 5px;
}
.user-email {
font-size: 0.8rem;
color: var(--text-muted);
word-break: break-all;
}
.user-menu-item {
padding: 8px 15px;
cursor: pointer;
transition: background-color 0.3s;
display: flex;
align-items: center;
}
.user-menu-item:hover {
background-color: var(--hover-bg);
}
.user-menu-item i {
margin-right: 10px;
width: 16px;
text-align: center;
}
.auth-buttons {
display: flex;
gap: 10px;
margin-left: 15px;
}
.auth-btn {
padding: 5px 15px;
border-radius: 20px;
font-size: 0.9rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.auth-btn i {
margin-right: 5px;
}
.login-btn {
background-color: transparent;
border: 1px solid var(--primary-color);
color: var(--primary-color);
}
.login-btn:hover {
background-color: rgba(var(--primary-rgb), 0.1);
}
.register-btn {
background-color: var(--primary-color);
border: 1px solid var(--primary-color);
color: white;
}
.register-btn:hover {
background-color: var(--primary-dark);
}
@media (max-width: 768px) {
.export-btn .export-text {
display: none;
}
}
/* Dashboard header styles */
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.dashboard-controls {
display: flex;
align-items: center;
gap: 1rem;
}
/* View switcher styles */
.view-switcher {
display: flex;
align-items: center;
}
.toggle-group {
display: flex;
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.toggle-btn {
background: none;
border: none;
padding: 8px 16px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 6px;
color: var(--text-color);
min-width: 100px;
justify-content: center;
}
.toggle-btn:hover {
background-color: var(--hover-bg);
}
.toggle-btn.active {
background-color: var(--primary-color);
color: white;
}
.toggle-btn.active:hover {
background-color: var(--primary-dark);
}
.toggle-btn i {
font-size: 0.8rem;
}
@media (max-width: 768px) {
.dashboard-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.dashboard-controls {
width: 100%;
justify-content: space-between;
}
.toggle-btn {
min-width: 80px;
padding: 6px 12px;
font-size: 0.8rem;
}
}
</style>
<!-- Immediate authentication check script -->
<script>
// Check if user is logged in immediately to hide buttons
(function() {
if (localStorage.getItem('auth_token')) {
// Hide login and register buttons immediately
document.addEventListener('DOMContentLoaded', function() {
console.log('Inline script: User is logged in, hiding login/register buttons');
// Hide auth container
var authContainer = document.getElementById('authContainer');
if (authContainer) {
authContainer.style.display = 'none';
authContainer.style.visibility = 'hidden';
}
// Show user menu
var userMenu = document.getElementById('userMenu');
if (userMenu) {
userMenu.style.display = 'block';
userMenu.style.visibility = 'visible';
}
// Update user info if possible
try {
var userInfo = JSON.parse(localStorage.getItem('user_info'));
if (userInfo) {
var displayName = userInfo.first_name || userInfo.username || 'User';
var userDisplayName = document.getElementById('userDisplayName');
if (userDisplayName) {
userDisplayName.textContent = displayName;
}
var userName = document.getElementById('userName');
if (userName) {
userName.textContent = (userInfo.first_name || '') + ' ' + (userInfo.last_name || '');
if (!userName.textContent.trim()) userName.textContent = userInfo.username || 'User';
}
var userEmail = document.getElementById('userEmail');
if (userEmail && userInfo.email) {
userEmail.textContent = userInfo.email;
}
}
} catch (e) {
console.error('Error updating user info:', e);
}
}, { once: true });
}
})();
</script>
</head>
<body>
<!-- Header -->
<header>
<div class="container">
<div class="app-title">
<i class="fas fa-shield-alt"></i>
<h1><a href="index.html" style="color: inherit; text-decoration: none; cursor: pointer;">Warracker</a></h1>
</div>
<div class="nav-links">
<a href="index.html" class="nav-link">
<i class="fas fa-home"></i> Home
</a>
<a href="status.html" class="nav-link active">
<i class="fas fa-chart-pie"></i> Status
</a>
</div>
<!-- Group for right-aligned elements -->
<div class="header-right-group">
<div id="authContainer" class="auth-buttons">
<a href="login.html" class="auth-btn login-btn">
<i class="fas fa-sign-in-alt"></i> Login
</a>
<a href="register.html" class="auth-btn register-btn">
<i class="fas fa-user-plus"></i> Register
</a>
</div>
<div id="userMenu" class="user-menu" style="display: none;">
<button id="userMenuBtn" class="user-btn">
<i class="fas fa-user-circle"></i>
<span id="userDisplayName">User</span>
</button>
<div id="userMenuDropdown" class="user-menu-dropdown">
<div class="user-info">
<div id="userName" class="user-name">User Name</div>
<div id="userEmail" class="user-email">user@example.com</div>
</div>
<div class="user-menu-item">
<a href="settings-new.html" style="color: inherit; text-decoration: none; display: block;">
<i class="fas fa-cog"></i> Settings
</a>
</div>
<div class="user-menu-item">
<i class="fas fa-info-circle"></i>
<a href="about.html" style="text-decoration: none; color: inherit;">About</a>
</div>
<div class="user-menu-item" id="logoutMenuItem">
<i class="fas fa-sign-out-alt"></i> Logout
</div>
</div>
</div>
</div> <!-- End header-right-group -->
</div>
</header>
<!-- Main Content -->
<div class="container">
<div class="status-content">
<div class="dashboard-header">
<h2 id="dashboardTitle">Warranty Status Dashboard</h2>
<div class="dashboard-controls">
<!-- Global View Controls (hidden by default, shown for eligible users) -->
<div id="viewSwitcher" class="view-switcher" style="display: none;">
<div class="toggle-group">
<button id="personalViewBtn" class="toggle-btn active">
<i class="fas fa-user"></i>
</button>
<button id="globalViewBtn" class="toggle-btn">
<i class="fas fa-globe"></i>
</button>
</div>
</div>
<button id="refreshDashboardBtn" class="refresh-btn" title="Refresh dashboard">
<i class="fas fa-sync-alt"></i>
</button>
</div>
</div>
<!-- Loading Indicator -->
<div id="loadingIndicator" class="loading-container active">
<div class="loading-spinner"></div>
</div>
<!-- Error Message Container -->
<div id="errorContainer" style="display: none;" class="error-message">
<i class="fas fa-exclamation-circle"></i>
<h3>Error Loading Dashboard</h3>
<p id="errorMessage">There was a problem loading the warranty statistics. Please try refreshing the page.</p>
<p id="errorDetails" class="error-details"></p>
</div>
<!-- Dashboard Content -->
<div id="dashboardContent">
<!-- Summary Cards -->
<div class="status-cards">
<div class="status-card" data-status="active">
<div class="card-icon">
<i class="fas fa-check-circle"></i>
</div>
<div class="card-content">
<h3>Active</h3>
<p class="count" id="activeCount">0</p>
</div>
</div>
<div class="status-card" data-status="expiring">
<div class="card-icon">
<i class="fas fa-exclamation-circle"></i>
</div>
<div class="card-content">
<h3>Expiring Soon</h3>
<p class="count" id="expiringCount">0</p>
</div>
</div>
<div class="status-card" data-status="expired">
<div class="card-icon">
<i class="fas fa-times-circle"></i>
</div>
<div class="card-content">
<h3>Expired</h3>
<p class="count" id="expiredCount">0</p>
</div>
</div>
<div class="status-card" data-status="total">
<div class="card-icon">
<i class="fas fa-shield-alt"></i>
</div>
<div class="card-content">
<h3>Total</h3>
<p class="count" id="totalCount">0</p>
</div>
</div>
</div>
<!-- Charts Section -->
<div class="charts-container">
<div class="chart-card">
<h3>Warranty Status Distribution</h3>
<div class="chart-container">
<canvas id="statusChart"></canvas>
</div>
</div>
<div class="chart-card">
<h3>Expiration Timeline</h3>
<div class="chart-container">
<canvas id="timelineChart"></canvas>
</div>
</div>
</div>
<!-- Recent Expirations -->
<div class="recent-expirations">
<div class="table-header">
<h3>Recently Expired or Expiring Soon</h3>
<div class="table-actions">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="searchWarranties" placeholder="Search warranties...">
</div>
<div class="filter-options">
<select id="statusFilter" class="filter-select">
<option value="all">All Statuses</option>
<option value="active">Active</option>
<option value="expiring">Expiring Soon</option>
<option value="expired">Expired</option>
</select>
</div>
<button id="exportBtn" class="export-btn" title="Export data">
<i class="fas fa-download"></i> <span class="export-text">Export</span>
</button>
</div>
</div>
<div class="table-responsive">
<table id="recentExpirationsTable">
<thead>
<tr id="tableHeader">
<th class="sortable" data-sort="product">Product <i class="fas fa-sort"></i></th>
<th class="sortable" data-sort="purchase">Purchase Date <i class="fas fa-sort"></i></th>
<th class="sortable" data-sort="expiration">Expiration Date <i class="fas fa-sort"></i></th>
<th class="sortable" data-sort="status">Status <i class="fas fa-sort"></i></th>
<th id="ownerHeader" class="sortable" data-sort="owner" style="display: none;">Owner <i class="fas fa-sort"></i></th>
</tr>
</thead>
<tbody id="recentExpirationsBody">
<!-- Data will be filled by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Toast Notifications -->
<div class="toast-container" id="toastContainer"></div>
<!-- Edit Warranty Modal -->
<div id="editModal" class="modal-backdrop">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">Edit Warranty</h3>
<button class="close-btn" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<!-- Tab Navigation -->
<div class="edit-tabs-nav">
<button type="button" class="edit-tab-btn active" data-tab="edit-product-info">
<i class="fas fa-box"></i> Product
</button>
<button type="button" class="edit-tab-btn" data-tab="edit-warranty-details">
<i class="fas fa-shield-alt"></i> Warranty
</button>
<button type="button" class="edit-tab-btn" data-tab="edit-documents">
<i class="fas fa-file-alt"></i> Documents
</button>
<button type="button" class="edit-tab-btn" data-tab="edit-tags">
<i class="fas fa-tags"></i> Tags
</button>
</div>
<form id="editWarrantyForm">
<input type="hidden" id="editWarrantyId">
<!-- Product Info Tab -->
<div class="edit-tab-content active" id="edit-product-info">
<div class="form-group">
<label for="editProductName">Product Name</label>
<input type="text" id="editProductName" name="product_name" class="form-control" required>
</div>
<div class="form-group">
<label for="editProductUrl">Product URL (Optional)</label>
<input type="text" id="editProductUrl" name="product_url" class="form-control" placeholder="https://example.com/product">
</div>
<div class="form-group">
<label>Serial Numbers</label>
<div id="editSerialNumbersContainer">
<!-- Serial number inputs will be added dynamically -->
</div>
</div>
<div class="form-group">
<label for="editVendor">Vendor (Optional)</label>
<input type="text" id="editVendor" name="vendor" class="form-control" placeholder="e.g. Amazon, Best Buy, etc.">
</div>
</div>
<!-- Warranty Details Tab -->
<div class="edit-tab-content" id="edit-warranty-details">
<div class="form-group">
<label for="editPurchaseDate">Purchase Date</label>
<input type="date" id="editPurchaseDate" name="purchase_date" class="form-control" required>
</div>
<!-- Add Lifetime Checkbox -->
<div class="form-group">
<label class="lifetime-label">
<input type="checkbox" id="editIsLifetime" name="is_lifetime" value="true">
Lifetime Warranty
</label>
</div>
<!-- End Lifetime Checkbox -->
<!-- Warranty Entry Method Selection -->
<div class="form-group" id="editWarrantyEntryMethod">
<label>Warranty Entry Method</label>
<div class="warranty-method-options">
<label class="radio-option">
<input type="radio" id="editDurationMethod" name="edit_warranty_method" value="duration" checked>
<span>Warranty Duration</span>
</label>
<label class="radio-option">
<input type="radio" id="editExactDateMethod" name="edit_warranty_method" value="exact_date">
<span>Exact Expiration Date</span>
</label>
</div>
</div>
<div id="editWarrantyDurationFields">
<div class="form-group">
<label for="editWarrantyDurationYears">Warranty Period</label>
<div class="duration-inputs">
<div>
<input type="number" id="editWarrantyDurationYears" name="warranty_duration_years" class="form-control" min="0" max="100" placeholder="Years">
<small>Years</small>
</div>
<div>
<input type="number" id="editWarrantyDurationMonths" name="warranty_duration_months" class="form-control" min="0" max="11" placeholder="Months">
<small>Months</small>
</div>
<div>
<input type="number" id="editWarrantyDurationDays" name="warranty_duration_days" class="form-control" min="0" max="365" placeholder="Days">
<small>Days</small>
</div>
</div>
</div>
</div>
<div id="editExactExpirationField" style="display: none;">
<div class="form-group">
<label for="editExactExpirationDate">Expiration Date</label>
<input type="date" id="editExactExpirationDate" name="exact_expiration_date" class="form-control">
</div>
</div>
<div class="form-group">
<label for="editWarrantyType">Warranty Type (Optional)</label>
<select id="editWarrantyType" name="warranty_type" class="form-control">
<option value="">Select warranty type...</option>
<option value="Standard">Standard</option>
<option value="Extended">Extended</option>
<option value="Manufacturer">Manufacturer</option>
<option value="Third Party">Third Party</option>
<option value="Store">Store</option>
<option value="Premium">Premium</option>
<option value="Limited">Limited</option>
<option value="Full">Full</option>
<option value="Parts Only">Parts Only</option>
<option value="Labor Only">Labor Only</option>
<option value="International">International</option>
<option value="Accidental Damage">Accidental Damage</option>
<option value="other">Other (Custom)</option>
</select>
<input type="text" id="editWarrantyTypeCustom" name="warranty_type_custom" class="form-control" style="display: none; margin-top: 8px;" placeholder="Enter custom warranty type">
</div>
<div class="form-group">
<label for="editCurrency">Currency</label>
<select id="editCurrency" name="currency" class="form-control">
<option value="USD">USD - US Dollar ($)</option>
<!-- Currency options will be populated by JavaScript -->
</select>
</div>
<div class="form-group">
<label for="editPurchasePrice">Purchase Price (Optional)</label>
<div class="price-input-wrapper" id="editPriceInputWrapper">
<span class="currency-symbol" id="editCurrencySymbol">$</span>
<input type="number" id="editPurchasePrice" name="purchase_price" class="form-control" min="0" step="0.01" placeholder="0.00">
</div>
</div>
<div class="form-group">
<label for="editNotes">Notes (Optional)</label>
<textarea id="editNotes" name="notes" class="form-control" rows="3" placeholder="Add any notes about this warranty..."></textarea>
</div>
</div>
<!-- Documents Tab -->
<div class="edit-tab-content" id="edit-documents">
<div class="form-group">
<label>Product Photo (Optional)</label>
<div class="file-input-wrapper">
<label for="editProductPhoto" class="file-input-label">
<i class="fas fa-upload"></i> Choose Photo
</label>
<input type="file" id="editProductPhoto" name="product_photo" class="file-input" accept=".png,.jpg,.jpeg,.webp">
</div>
<div id="editProductPhotoFileName" class="file-name"></div>
<div id="editProductPhotoPreview" class="photo-preview" style="display: none;">
<img id="editProductPhotoImg" src="" alt="Product Photo Preview" style="max-width: 200px; max-height: 200px; border-radius: 8px; margin-top: 10px;">
</div>
<div id="currentProductPhoto" class="mt-10"></div>
<button type="button" id="deleteProductPhotoBtn" class="btn btn-danger btn-sm mt-2" style="display:none;"><i class="fas fa-trash"></i> Delete Photo</button>
</div>
<div class="form-group">
<label>Invoice/Receipt</label>
<div class="file-input-wrapper">
<label for="editInvoice" class="file-input-label">
<i class="fas fa-upload"></i> Choose File
</label>
<input type="file" id="editInvoice" name="invoice" class="file-input" accept=".pdf,.png,.jpg,.jpeg">
</div>
<div id="editFileName" class="file-name"></div>
<div id="currentInvoice" class="mt-10"></div>
<button type="button" id="deleteInvoiceBtn" class="btn btn-danger btn-sm mt-2" style="display:none;"><i class="fas fa-trash"></i> Delete Invoice</button>
</div>
<div class="form-group">
<label>Product Manual (Optional)</label>
<div class="file-input-wrapper">
<label for="editManual" class="file-input-label">
<i class="fas fa-upload"></i> Choose File
</label>
<input type="file" id="editManual" name="manual" class="file-input" accept=".pdf,.png,.jpg,.jpeg">
</div>
<div id="editManualFileName" class="file-name"></div>
<div id="currentManual" class="mt-10"></div>
<button type="button" id="deleteManualBtn" class="btn btn-danger btn-sm mt-2" style="display:none;"><i class="fas fa-trash"></i> Delete Manual</button>
</div>
<div class="form-group">
<label>Files (ZIP/RAR, Optional)</label>
<div class="file-input-wrapper">
<label for="editOtherDocument" class="file-input-label">
<i class="fas fa-upload"></i> Choose File
</label>
<input type="file" id="editOtherDocument" name="other_document" class="file-input" accept=".zip,.rar">
</div>
<div id="editOtherDocumentFileName" class="file-name"></div>
<div id="currentOtherDocument" class="mt-10"></div>
<button type="button" id="deleteOtherDocumentBtn" class="btn btn-danger btn-sm mt-2" style="display:none;"><i class="fas fa-trash"></i> Delete Files</button>
</div>
</div>
<!-- Tags Tab -->
<div class="edit-tab-content" id="edit-tags">
<div class="form-group">
<label>Add Tags</label>
<p class="text-muted">Tags help you organize and filter your warranties</p>
<div class="tags-container">
<div class="selected-tags" id="editSelectedTags">
<!-- Selected tags will be displayed here -->
</div>
<div class="tags-dropdown">
<input type="text" id="editTagSearch" class="form-control" placeholder="Search or add new tag...">
<div class="tags-list" id="editTagsList">
<!-- Tag options will be populated by JavaScript -->
</div>
</div>
<div class="mt-10">
<button type="button" id="editManageTagsBtn" class="btn btn-secondary btn-sm">
<i class="fas fa-cog"></i> Manage Tags
</button>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-danger" data-dismiss="modal">Cancel</button>
<button id="saveWarrantyBtn" class="btn btn-primary">Save Changes</button>
</div>
</div>
</div>
<!-- Confirm Delete Modal -->
<div id="deleteModal" class="modal-backdrop">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">Confirm Delete</h3>
<button class="close-btn" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete this warranty? This action cannot be undone.</p>
<p><strong>Product:</strong> <span id="deleteProductName"></span></p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button id="confirmDeleteBtn" class="btn btn-danger">Delete</button>
</div>
</div>
</div>
<!-- Tag Management Modal -->
<div id="tagManagementModal" class="modal-backdrop">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">Manage Tags</h3>
<button class="close-btn" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<form id="newTagForm" class="tag-form">
<input type="text" id="newTagName" class="form-control" placeholder="New tag name" required>
<input type="color" id="newTagColor" value="#808080">
<button type="submit" class="btn btn-primary">Add Tag</button>
</form>
<h4 class="mt-20">Existing Tags</h4>
<div id="existingTags" class="existing-tags">
<!-- Tags will be populated here by JavaScript -->
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
<!-- Loading Spinner -->
<div class="loading-container" id="loadingContainer">
<div class="loading-spinner"></div>
</div>
<script src="auth.js?v=20250529005"></script>
<script src="status.js" defer></script>
<!-- Footer Width Fix -->
<script src="footer-fix.js"></script>
<!-- Footer Content Manager -->
<script src="footer-content.js"></script>
<!-- Powered by Warracker Footer -->
<footer class="warracker-footer" id="warrackerFooter">
<!-- Content will be dynamically generated by footer-content.js -->
</footer>
<script>
// Apply footer styles based on theme
function applyFooterStyles() {
const footer = document.getElementById('warrackerFooter');
const link = document.getElementById('warrackerFooterLink');
const isDarkMode = document.documentElement.getAttribute('data-theme') === 'dark' ||
document.documentElement.classList.contains('dark-mode') ||
document.body.classList.contains('dark-mode');
if (footer) {
if (isDarkMode) {
footer.style.cssText = 'margin-top: 50px; padding: 20px; text-align: center; border-top: 1px solid #444; background-color: #2d2d2d; color: #e0e0e0; font-size: 0.9rem;';
if (link) link.style.cssText = 'color: #4dabf7; text-decoration: none; font-weight: 500;';
} else {
footer.style.cssText = 'margin-top: 50px; padding: 20px; text-align: center; border-top: 1px solid #e0e0e0; background-color: #ffffff; color: #333333; font-size: 0.9rem;';
if (link) link.style.cssText = 'color: #3498db; text-decoration: none; font-weight: 500;';
}
}
}
document.addEventListener('DOMContentLoaded', applyFooterStyles);
const observer = new MutationObserver(applyFooterStyles);
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme', 'class'] });
observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
</script>
</body>
</html>