adding developer tools in admin panel, adding vendor management
This commit is contained in:
140
static/admin/js/components.js
Normal file
140
static/admin/js/components.js
Normal file
@@ -0,0 +1,140 @@
|
||||
// static/admin/js/components.js
|
||||
|
||||
// Setup logging
|
||||
const COMPONENTS_LOG_LEVEL = 3;
|
||||
|
||||
const componentsLog = {
|
||||
error: (...args) => COMPONENTS_LOG_LEVEL >= 1 && console.error('❌ [COMPONENTS ERROR]', ...args),
|
||||
warn: (...args) => COMPONENTS_LOG_LEVEL >= 2 && console.warn('⚠️ [COMPONENTS WARN]', ...args),
|
||||
info: (...args) => COMPONENTS_LOG_LEVEL >= 3 && console.info('ℹ️ [COMPONENTS INFO]', ...args),
|
||||
debug: (...args) => COMPONENTS_LOG_LEVEL >= 4 && console.log('🔍 [COMPONENTS DEBUG]', ...args)
|
||||
};
|
||||
|
||||
/**
|
||||
* Components Library Alpine.js Component
|
||||
* UI components reference with live examples
|
||||
*/
|
||||
function adminComponents() {
|
||||
return {
|
||||
// ✅ CRITICAL: Inherit base layout functionality
|
||||
...data(),
|
||||
|
||||
// ✅ CRITICAL: Set page identifier
|
||||
currentPage: 'components',
|
||||
|
||||
// Active section for navigation
|
||||
activeSection: 'forms',
|
||||
|
||||
// Component sections
|
||||
sections: [
|
||||
{ id: 'forms', name: 'Forms', icon: 'clipboard-list' },
|
||||
{ id: 'buttons', name: 'Buttons', icon: 'cursor-click' },
|
||||
{ id: 'cards', name: 'Cards', icon: 'collection' },
|
||||
{ id: 'badges', name: 'Badges', icon: 'tag' },
|
||||
{ id: 'tables', name: 'Tables', icon: 'table' },
|
||||
{ id: 'modals', name: 'Modals', icon: 'window' },
|
||||
{ id: 'alerts', name: 'Alerts', icon: 'exclamation' }
|
||||
],
|
||||
|
||||
// Sample form data for examples
|
||||
exampleForm: {
|
||||
textInput: 'Sample text',
|
||||
email: 'user@example.com',
|
||||
textarea: 'Sample description text...',
|
||||
select: 'option1',
|
||||
checkbox: true,
|
||||
radio: 'option1',
|
||||
disabled: 'Read-only value'
|
||||
},
|
||||
|
||||
// Sample errors for validation examples
|
||||
exampleErrors: {
|
||||
email: 'Please enter a valid email address',
|
||||
required: 'This field is required'
|
||||
},
|
||||
|
||||
// ✅ CRITICAL: Proper initialization with guard
|
||||
async init() {
|
||||
componentsLog.info('=== COMPONENTS PAGE INITIALIZING ===');
|
||||
|
||||
// Prevent multiple initializations
|
||||
if (window._componentsInitialized) {
|
||||
componentsLog.warn('Components page already initialized, skipping...');
|
||||
return;
|
||||
}
|
||||
window._componentsInitialized = true;
|
||||
|
||||
// Set active section from URL hash if present
|
||||
this.setActiveSectionFromHash();
|
||||
|
||||
// Listen for hash changes
|
||||
window.addEventListener('hashchange', () => {
|
||||
this.setActiveSectionFromHash();
|
||||
});
|
||||
|
||||
componentsLog.info('=== COMPONENTS PAGE INITIALIZATION COMPLETE ===');
|
||||
},
|
||||
|
||||
/**
|
||||
* Set active section from URL hash
|
||||
*/
|
||||
setActiveSectionFromHash() {
|
||||
const hash = window.location.hash.replace('#', '');
|
||||
if (hash && this.sections.find(s => s.id === hash)) {
|
||||
this.activeSection = hash;
|
||||
componentsLog.debug('Set active section from hash:', hash);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigate to section
|
||||
*/
|
||||
goToSection(sectionId) {
|
||||
componentsLog.info('Navigating to section:', sectionId);
|
||||
this.activeSection = sectionId;
|
||||
window.location.hash = sectionId;
|
||||
|
||||
// Smooth scroll to section
|
||||
const element = document.getElementById(sectionId);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if section is active
|
||||
*/
|
||||
isSectionActive(sectionId) {
|
||||
return this.activeSection === sectionId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy code to clipboard
|
||||
*/
|
||||
async copyCode(code) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
Utils.showToast('Code copied to clipboard!', 'success');
|
||||
componentsLog.debug('Code copied to clipboard');
|
||||
} catch (error) {
|
||||
componentsLog.error('Failed to copy code:', error);
|
||||
Utils.showToast('Failed to copy code', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show toast example
|
||||
*/
|
||||
showToastExample(type) {
|
||||
const messages = {
|
||||
success: 'Operation completed successfully!',
|
||||
error: 'An error occurred!',
|
||||
warning: 'Please review your input.',
|
||||
info: 'Here is some information.'
|
||||
};
|
||||
Utils.showToast(messages[type] || messages.info, type);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentsLog.info('Components module loaded');
|
||||
210
static/admin/js/icons-page.js
Normal file
210
static/admin/js/icons-page.js
Normal file
@@ -0,0 +1,210 @@
|
||||
// static/admin/js/icons-page.js
|
||||
|
||||
// Setup logging
|
||||
const ICONS_PAGE_LOG_LEVEL = 3;
|
||||
|
||||
const iconsLog = {
|
||||
error: (...args) => ICONS_PAGE_LOG_LEVEL >= 1 && console.error('❌ [ICONS PAGE ERROR]', ...args),
|
||||
warn: (...args) => ICONS_PAGE_LOG_LEVEL >= 2 && console.warn('⚠️ [ICONS PAGE WARN]', ...args),
|
||||
info: (...args) => ICONS_PAGE_LOG_LEVEL >= 3 && console.info('ℹ️ [ICONS PAGE INFO]', ...args),
|
||||
debug: (...args) => ICONS_PAGE_LOG_LEVEL >= 4 && console.log('🔍 [ICONS PAGE DEBUG]', ...args)
|
||||
};
|
||||
|
||||
/**
|
||||
* Icons Browser Alpine.js Component
|
||||
* Browse and search all available icons
|
||||
*/
|
||||
function adminIcons() {
|
||||
return {
|
||||
// ✅ CRITICAL: Inherit base layout functionality
|
||||
...data(),
|
||||
|
||||
// ✅ CRITICAL: Set page identifier
|
||||
currentPage: 'icons',
|
||||
|
||||
// Search and filter
|
||||
searchQuery: '',
|
||||
activeCategory: 'all',
|
||||
|
||||
// Icon categories
|
||||
categories: [
|
||||
{ id: 'all', name: 'All Icons', icon: 'collection' },
|
||||
{ id: 'navigation', name: 'Navigation', icon: 'menu' },
|
||||
{ id: 'user', name: 'User & Profile', icon: 'user' },
|
||||
{ id: 'actions', name: 'Actions', icon: 'lightning-bolt' },
|
||||
{ id: 'ecommerce', name: 'E-commerce', icon: 'shopping-bag' },
|
||||
{ id: 'inventory', name: 'Inventory', icon: 'cube' },
|
||||
{ id: 'communication', name: 'Communication', icon: 'mail' },
|
||||
{ id: 'files', name: 'Files', icon: 'document' },
|
||||
{ id: 'settings', name: 'Settings', icon: 'cog' },
|
||||
{ id: 'status', name: 'Status', icon: 'check-circle' },
|
||||
{ id: 'testing', name: 'Testing', icon: 'beaker' }
|
||||
],
|
||||
|
||||
// All icons organized by category
|
||||
iconsByCategory: {},
|
||||
allIcons: [],
|
||||
filteredIcons: [],
|
||||
|
||||
// Selected icon for detail view
|
||||
selectedIcon: null,
|
||||
|
||||
// ✅ CRITICAL: Proper initialization with guard
|
||||
async init() {
|
||||
iconsLog.info('=== ICONS PAGE INITIALIZING ===');
|
||||
|
||||
// Prevent multiple initializations
|
||||
if (window._iconsPageInitialized) {
|
||||
iconsLog.warn('Icons page already initialized, skipping...');
|
||||
return;
|
||||
}
|
||||
window._iconsPageInitialized = true;
|
||||
|
||||
// Load icons from global Icons object
|
||||
this.loadIcons();
|
||||
|
||||
iconsLog.info('=== ICONS PAGE INITIALIZATION COMPLETE ===');
|
||||
},
|
||||
|
||||
/**
|
||||
* Load icons from global Icons object
|
||||
*/
|
||||
loadIcons() {
|
||||
if (!window.Icons) {
|
||||
iconsLog.error('Icons object not found! Make sure icons.js is loaded.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all icon names
|
||||
this.allIcons = Object.keys(window.Icons).map(name => ({
|
||||
name: name,
|
||||
category: this.categorizeIcon(name)
|
||||
}));
|
||||
|
||||
// Organize by category
|
||||
this.iconsByCategory = this.allIcons.reduce((acc, icon) => {
|
||||
if (!acc[icon.category]) {
|
||||
acc[icon.category] = [];
|
||||
}
|
||||
acc[icon.category].push(icon);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Initial filter
|
||||
this.filterIcons();
|
||||
|
||||
iconsLog.info(`Loaded ${this.allIcons.length} icons across ${Object.keys(this.iconsByCategory).length} categories`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Categorize icon based on name
|
||||
*/
|
||||
categorizeIcon(iconName) {
|
||||
const categoryMap = {
|
||||
navigation: ['home', 'menu', 'search', 'arrow', 'chevron'],
|
||||
user: ['user', 'identification', 'badge'],
|
||||
actions: ['edit', 'delete', 'plus', 'check', 'close', 'refresh', 'duplicate', 'eye', 'filter', 'dots'],
|
||||
ecommerce: ['shopping', 'credit-card', 'currency', 'gift', 'tag', 'truck', 'receipt'],
|
||||
inventory: ['cube', 'collection', 'photograph', 'chart'],
|
||||
communication: ['mail', 'phone', 'chat', 'bell', 'inbox'],
|
||||
files: ['document', 'folder', 'download', 'upload'],
|
||||
settings: ['cog', 'adjustments', 'calendar', 'moon', 'sun'],
|
||||
status: ['exclamation', 'information', 'spinner', 'star', 'heart', 'flag'],
|
||||
testing: ['view-grid', 'beaker', 'clipboard-list', 'check-circle', 'lightning-bolt', 'clock', 'lock-closed', 'database', 'light-bulb', 'book-open', 'play']
|
||||
};
|
||||
|
||||
for (const [category, keywords] of Object.entries(categoryMap)) {
|
||||
if (keywords.some(keyword => iconName.includes(keyword))) {
|
||||
return category;
|
||||
}
|
||||
}
|
||||
return 'navigation'; // default
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter icons based on search and category
|
||||
*/
|
||||
filterIcons() {
|
||||
let icons = this.allIcons;
|
||||
|
||||
// Filter by category
|
||||
if (this.activeCategory !== 'all') {
|
||||
icons = icons.filter(icon => icon.category === this.activeCategory);
|
||||
}
|
||||
|
||||
// Filter by search query
|
||||
if (this.searchQuery.trim()) {
|
||||
const query = this.searchQuery.toLowerCase();
|
||||
icons = icons.filter(icon => icon.name.toLowerCase().includes(query));
|
||||
}
|
||||
|
||||
this.filteredIcons = icons;
|
||||
iconsLog.debug(`Filtered to ${icons.length} icons`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set active category
|
||||
*/
|
||||
setCategory(categoryId) {
|
||||
iconsLog.info('Setting category:', categoryId);
|
||||
this.activeCategory = categoryId;
|
||||
this.filterIcons();
|
||||
},
|
||||
|
||||
/**
|
||||
* Select icon for detail view
|
||||
*/
|
||||
selectIcon(iconName) {
|
||||
iconsLog.info('Selected icon:', iconName);
|
||||
this.selectedIcon = iconName;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy icon usage code to clipboard
|
||||
*/
|
||||
async copyIconUsage(iconName) {
|
||||
const code = `x-html="$icon('${iconName}', 'w-5 h-5')"`;
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
Utils.showToast(`'${iconName}' code copied!`, 'success');
|
||||
iconsLog.debug('Icon usage code copied:', iconName);
|
||||
} catch (error) {
|
||||
iconsLog.error('Failed to copy code:', error);
|
||||
Utils.showToast('Failed to copy code', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy icon name to clipboard
|
||||
*/
|
||||
async copyIconName(iconName) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(iconName);
|
||||
Utils.showToast(`'${iconName}' copied!`, 'success');
|
||||
iconsLog.debug('Icon name copied:', iconName);
|
||||
} catch (error) {
|
||||
iconsLog.error('Failed to copy name:', error);
|
||||
Utils.showToast('Failed to copy name', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get category info
|
||||
*/
|
||||
getCategoryInfo(categoryId) {
|
||||
return this.categories.find(c => c.id === categoryId) || this.categories[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get icon count for category
|
||||
*/
|
||||
getCategoryCount(categoryId) {
|
||||
if (categoryId === 'all') {
|
||||
return this.allIcons.length;
|
||||
}
|
||||
return this.iconsByCategory[categoryId]?.length || 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
iconsLog.info('Icons page module loaded');
|
||||
131
static/admin/js/testing-hub.js
Normal file
131
static/admin/js/testing-hub.js
Normal file
@@ -0,0 +1,131 @@
|
||||
// static/admin/js/testing-hub.js
|
||||
|
||||
// Setup logging
|
||||
const TESTING_HUB_LOG_LEVEL = 3;
|
||||
|
||||
const testingLog = {
|
||||
error: (...args) => TESTING_HUB_LOG_LEVEL >= 1 && console.error('❌ [TESTING HUB ERROR]', ...args),
|
||||
warn: (...args) => TESTING_HUB_LOG_LEVEL >= 2 && console.warn('⚠️ [TESTING HUB WARN]', ...args),
|
||||
info: (...args) => TESTING_HUB_LOG_LEVEL >= 3 && console.info('ℹ️ [TESTING HUB INFO]', ...args),
|
||||
debug: (...args) => TESTING_HUB_LOG_LEVEL >= 4 && console.log('🔍 [TESTING HUB DEBUG]', ...args)
|
||||
};
|
||||
|
||||
/**
|
||||
* Testing Hub Alpine.js Component
|
||||
* Central hub for all test suites and QA tools
|
||||
*/
|
||||
function adminTestingHub() {
|
||||
return {
|
||||
// ✅ CRITICAL: Inherit base layout functionality
|
||||
...data(),
|
||||
|
||||
// ✅ CRITICAL: Set page identifier
|
||||
currentPage: 'testing',
|
||||
|
||||
// Test suites data
|
||||
testSuites: [
|
||||
{
|
||||
id: 'auth-flow',
|
||||
name: 'Authentication Flow',
|
||||
description: 'Test login, logout, token expiration, redirects, and protected route access.',
|
||||
url: '/admin/test/auth-flow',
|
||||
icon: 'lock-closed',
|
||||
color: 'blue',
|
||||
testCount: 6,
|
||||
features: [
|
||||
'Login with valid/invalid credentials',
|
||||
'Token expiration handling',
|
||||
'Protected route access & redirects',
|
||||
'localStorage state monitoring'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'vendors-users',
|
||||
name: 'Data Migration & CRUD',
|
||||
description: 'Test vendor and user creation, listing, editing, deletion, and data migration scenarios.',
|
||||
url: '/admin/test/vendors-users-migration',
|
||||
icon: 'database',
|
||||
color: 'orange',
|
||||
testCount: 10,
|
||||
features: [
|
||||
'Vendor CRUD operations',
|
||||
'User management & roles',
|
||||
'Data migration validation',
|
||||
'Form validation & error handling'
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
// Stats
|
||||
stats: {
|
||||
totalSuites: 2,
|
||||
totalTests: 16,
|
||||
coverage: 'Auth, CRUD',
|
||||
avgDuration: '< 5 min'
|
||||
},
|
||||
|
||||
// Loading state
|
||||
loading: false,
|
||||
|
||||
// ✅ CRITICAL: Proper initialization with guard
|
||||
async init() {
|
||||
testingLog.info('=== TESTING HUB INITIALIZING ===');
|
||||
|
||||
// Prevent multiple initializations
|
||||
if (window._testingHubInitialized) {
|
||||
testingLog.warn('Testing hub already initialized, skipping...');
|
||||
return;
|
||||
}
|
||||
window._testingHubInitialized = true;
|
||||
|
||||
// Calculate stats
|
||||
this.calculateStats();
|
||||
|
||||
testingLog.info('=== TESTING HUB INITIALIZATION COMPLETE ===');
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculate test statistics
|
||||
*/
|
||||
calculateStats() {
|
||||
this.stats.totalSuites = this.testSuites.length;
|
||||
this.stats.totalTests = this.testSuites.reduce((sum, suite) => sum + suite.testCount, 0);
|
||||
testingLog.debug('Stats calculated:', this.stats);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get color classes for test suite cards
|
||||
*/
|
||||
getColorClasses(color) {
|
||||
const colorMap = {
|
||||
blue: {
|
||||
gradient: 'from-blue-500 to-blue-600',
|
||||
button: 'bg-blue-600 hover:bg-blue-700'
|
||||
},
|
||||
orange: {
|
||||
gradient: 'from-orange-500 to-orange-600',
|
||||
button: 'bg-orange-600 hover:bg-orange-700'
|
||||
},
|
||||
green: {
|
||||
gradient: 'from-green-500 to-green-600',
|
||||
button: 'bg-green-600 hover:bg-green-700'
|
||||
},
|
||||
purple: {
|
||||
gradient: 'from-purple-500 to-purple-600',
|
||||
button: 'bg-purple-600 hover:bg-purple-700'
|
||||
}
|
||||
};
|
||||
return colorMap[color] || colorMap.blue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigate to test suite
|
||||
*/
|
||||
goToTest(url) {
|
||||
testingLog.info('Navigating to test suite:', url);
|
||||
window.location.href = url;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
testingLog.info('Testing hub module loaded');
|
||||
135
static/admin/js/vendor-detail.js
Normal file
135
static/admin/js/vendor-detail.js
Normal file
@@ -0,0 +1,135 @@
|
||||
// static/admin/js/vendor-detail.js
|
||||
|
||||
// Log levels: 0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug
|
||||
const VENDOR_DETAIL_LOG_LEVEL = 3;
|
||||
|
||||
const detailLog = {
|
||||
error: (...args) => VENDOR_DETAIL_LOG_LEVEL >= 1 && console.error('❌ [VENDOR_DETAIL ERROR]', ...args),
|
||||
warn: (...args) => VENDOR_DETAIL_LOG_LEVEL >= 2 && console.warn('⚠️ [VENDOR_DETAIL WARN]', ...args),
|
||||
info: (...args) => VENDOR_DETAIL_LOG_LEVEL >= 3 && console.info('ℹ️ [VENDOR_DETAIL INFO]', ...args),
|
||||
debug: (...args) => VENDOR_DETAIL_LOG_LEVEL >= 4 && console.log('🔍 [VENDOR_DETAIL DEBUG]', ...args)
|
||||
};
|
||||
|
||||
function adminVendorDetail() {
|
||||
return {
|
||||
// Inherit base layout functionality from init-alpine.js
|
||||
...data(),
|
||||
|
||||
// Vendor detail page specific state
|
||||
currentPage: 'vendor-detail',
|
||||
vendor: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
vendorCode: null,
|
||||
|
||||
// Initialize
|
||||
async init() {
|
||||
detailLog.info('=== VENDOR DETAIL PAGE INITIALIZING ===');
|
||||
|
||||
// Prevent multiple initializations
|
||||
if (window._vendorDetailInitialized) {
|
||||
detailLog.warn('Vendor detail page already initialized, skipping...');
|
||||
return;
|
||||
}
|
||||
window._vendorDetailInitialized = true;
|
||||
|
||||
// Get vendor code from URL
|
||||
const path = window.location.pathname;
|
||||
const match = path.match(/\/admin\/vendors\/([^\/]+)$/);
|
||||
|
||||
if (match) {
|
||||
this.vendorCode = match[1];
|
||||
detailLog.info('Viewing vendor:', this.vendorCode);
|
||||
await this.loadVendor();
|
||||
} else {
|
||||
detailLog.error('No vendor code in URL');
|
||||
this.error = 'Invalid vendor URL';
|
||||
Utils.showToast('Invalid vendor URL', 'error');
|
||||
}
|
||||
|
||||
detailLog.info('=== VENDOR DETAIL PAGE INITIALIZATION COMPLETE ===');
|
||||
},
|
||||
|
||||
// Load vendor data
|
||||
async loadVendor() {
|
||||
detailLog.info('Loading vendor details...');
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const response = await apiClient.get(`/admin/vendors/${this.vendorCode}`);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
this.vendor = response;
|
||||
|
||||
detailLog.info(`Vendor loaded in ${duration}ms`, {
|
||||
vendor_code: this.vendor.vendor_code,
|
||||
name: this.vendor.name,
|
||||
is_verified: this.vendor.is_verified,
|
||||
is_active: this.vendor.is_active
|
||||
});
|
||||
detailLog.debug('Full vendor data:', this.vendor);
|
||||
|
||||
} catch (error) {
|
||||
detailLog.error('Failed to load vendor:', error);
|
||||
this.error = error.message || 'Failed to load vendor details';
|
||||
Utils.showToast('Failed to load vendor details', 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// Format date (matches dashboard pattern)
|
||||
formatDate(dateString) {
|
||||
if (!dateString) {
|
||||
detailLog.debug('formatDate called with empty dateString');
|
||||
return '-';
|
||||
}
|
||||
const formatted = Utils.formatDate(dateString);
|
||||
detailLog.debug(`Date formatted: ${dateString} -> ${formatted}`);
|
||||
return formatted;
|
||||
},
|
||||
|
||||
// Delete vendor
|
||||
async deleteVendor() {
|
||||
detailLog.info('Delete vendor requested:', this.vendorCode);
|
||||
|
||||
if (!confirm(`Are you sure you want to delete vendor "${this.vendor.name}"?\n\nThis action cannot be undone and will delete:\n- All products\n- All orders\n- All customers\n- All team members`)) {
|
||||
detailLog.info('Delete cancelled by user');
|
||||
return;
|
||||
}
|
||||
|
||||
// Second confirmation for safety
|
||||
if (!confirm(`FINAL CONFIRMATION\n\nType the vendor code to confirm: ${this.vendor.vendor_code}\n\nAre you absolutely sure?`)) {
|
||||
detailLog.info('Delete cancelled by user (second confirmation)');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
detailLog.info('Deleting vendor:', this.vendorCode);
|
||||
await apiClient.delete(`/admin/vendors/${this.vendorCode}?confirm=true`);
|
||||
|
||||
Utils.showToast('Vendor deleted successfully', 'success');
|
||||
detailLog.info('Vendor deleted successfully');
|
||||
|
||||
// Redirect to vendors list
|
||||
setTimeout(() => window.location.href = '/admin/vendors', 1500);
|
||||
|
||||
} catch (error) {
|
||||
detailLog.error('Failed to delete vendor:', error);
|
||||
Utils.showToast(error.message || 'Failed to delete vendor', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
// Refresh vendor data
|
||||
async refresh() {
|
||||
detailLog.info('=== VENDOR REFRESH TRIGGERED ===');
|
||||
await this.loadVendor();
|
||||
Utils.showToast('Vendor details refreshed', 'success');
|
||||
detailLog.info('=== VENDOR REFRESH COMPLETE ===');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
detailLog.info('Vendor detail module loaded');
|
||||
@@ -18,8 +18,10 @@ function adminVendors() {
|
||||
// Inherit base layout functionality from init-alpine.js
|
||||
...data(),
|
||||
|
||||
// Vendors page specific state
|
||||
// ✅ CRITICAL: Page identifier for sidebar active state
|
||||
currentPage: 'vendors',
|
||||
|
||||
// Vendors page specific state
|
||||
vendors: [],
|
||||
stats: {
|
||||
total: 0,
|
||||
@@ -30,8 +32,8 @@ function adminVendors() {
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
// Pagination state
|
||||
currentPage: 1,
|
||||
// Pagination state (renamed from currentPage to avoid conflict)
|
||||
page: 1, // ✅ FIXED: Was 'currentPage' which conflicted with sidebar
|
||||
itemsPerPage: 10,
|
||||
|
||||
// Initialize
|
||||
@@ -53,7 +55,7 @@ function adminVendors() {
|
||||
|
||||
// Computed: Get paginated vendors for current page
|
||||
get paginatedVendors() {
|
||||
const start = (this.currentPage - 1) * this.itemsPerPage;
|
||||
const start = (this.page - 1) * this.itemsPerPage;
|
||||
const end = start + this.itemsPerPage;
|
||||
return this.vendors.slice(start, end);
|
||||
},
|
||||
@@ -66,12 +68,12 @@ function adminVendors() {
|
||||
// Computed: Start index for pagination display
|
||||
get startIndex() {
|
||||
if (this.vendors.length === 0) return 0;
|
||||
return (this.currentPage - 1) * this.itemsPerPage + 1;
|
||||
return (this.page - 1) * this.itemsPerPage + 1;
|
||||
},
|
||||
|
||||
// Computed: End index for pagination display
|
||||
get endIndex() {
|
||||
const end = this.currentPage * this.itemsPerPage;
|
||||
const end = this.page * this.itemsPerPage;
|
||||
return end > this.vendors.length ? this.vendors.length : end;
|
||||
},
|
||||
|
||||
@@ -79,7 +81,7 @@ function adminVendors() {
|
||||
get pageNumbers() {
|
||||
const pages = [];
|
||||
const totalPages = this.totalPages;
|
||||
const current = this.currentPage;
|
||||
const current = this.page;
|
||||
|
||||
if (totalPages <= 7) {
|
||||
// Show all pages if 7 or fewer
|
||||
@@ -137,7 +139,7 @@ function adminVendors() {
|
||||
}
|
||||
|
||||
// Reset to first page when data is loaded
|
||||
this.currentPage = 1;
|
||||
this.page = 1;
|
||||
|
||||
} catch (error) {
|
||||
vendorsLog.error('Failed to load vendors:', error);
|
||||
@@ -167,27 +169,27 @@ function adminVendors() {
|
||||
},
|
||||
|
||||
// Pagination: Go to specific page
|
||||
goToPage(page) {
|
||||
if (page === '...' || page < 1 || page > this.totalPages) {
|
||||
goToPage(pageNum) {
|
||||
if (pageNum === '...' || pageNum < 1 || pageNum > this.totalPages) {
|
||||
return;
|
||||
}
|
||||
vendorsLog.info('Going to page:', page);
|
||||
this.currentPage = page;
|
||||
vendorsLog.info('Going to page:', pageNum);
|
||||
this.page = pageNum;
|
||||
},
|
||||
|
||||
// Pagination: Go to next page
|
||||
nextPage() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
if (this.page < this.totalPages) {
|
||||
vendorsLog.info('Going to next page');
|
||||
this.currentPage++;
|
||||
this.page++;
|
||||
}
|
||||
},
|
||||
|
||||
// Pagination: Go to previous page
|
||||
previousPage() {
|
||||
if (this.currentPage > 1) {
|
||||
if (this.page > 1) {
|
||||
vendorsLog.info('Going to previous page');
|
||||
this.currentPage--;
|
||||
this.page--;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1,644 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Auth Flow Testing - Admin Panel</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
|
||||
.test-section h2 {
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.test-description {
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.test-steps {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.test-steps ol {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.test-steps li {
|
||||
margin-bottom: 8px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.expected-result {
|
||||
background: #e8f5e9;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #4caf50;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.expected-result strong {
|
||||
color: #2e7d32;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.expected-result ul {
|
||||
margin-left: 20px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #f59e0b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: #d97706;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #059669;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6b7280;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #4b5563;
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
margin-top: 30px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.status-panel h3 {
|
||||
color: #38bdf8;
|
||||
margin-bottom: 15px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
color: #34d399;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-value.false {
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
.log-level-control {
|
||||
background: #fef3c7;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #f59e0b;
|
||||
}
|
||||
|
||||
.log-level-control h3 {
|
||||
color: #92400e;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.log-level-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.log-level-buttons button {
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.warning-box h3 {
|
||||
color: #991b1b;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.warning-box ul {
|
||||
margin-left: 20px;
|
||||
color: #7f1d1d;
|
||||
}
|
||||
|
||||
.warning-box li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🧪 Auth Flow Testing</h1>
|
||||
<p class="subtitle">Comprehensive testing for the Jinja2 migration auth loop fix</p>
|
||||
|
||||
<!-- Log Level Control -->
|
||||
<div class="log-level-control">
|
||||
<h3>📊 Log Level Control</h3>
|
||||
<p style="color: #78350f; font-size: 13px; margin-bottom: 10px;">
|
||||
Change logging verbosity for login.js and api-client.js
|
||||
</p>
|
||||
<div class="log-level-buttons">
|
||||
<button onclick="setLogLevel(0)" class="btn-secondary">0 - None</button>
|
||||
<button onclick="setLogLevel(1)" class="btn-danger">1 - Errors Only</button>
|
||||
<button onclick="setLogLevel(2)" class="btn-warning">2 - Warnings</button>
|
||||
<button onclick="setLogLevel(3)" class="btn-success">3 - Info (Production)</button>
|
||||
<button onclick="setLogLevel(4)" class="btn-primary">4 - Debug (Full)</button>
|
||||
</div>
|
||||
<p style="color: #78350f; font-size: 12px; margin-top: 10px; font-style: italic;">
|
||||
Current levels: LOGIN = <span id="currentLoginLevel">4</span>, API = <span id="currentApiLevel">3</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Test 1: Clean Slate -->
|
||||
<div class="test-section">
|
||||
<h2>Test 1: Clean Slate - Fresh Login Flow</h2>
|
||||
<p class="test-description">
|
||||
Tests the complete login flow from scratch with no existing tokens.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Click "Clear All Data" below</li>
|
||||
<li>Click "Navigate to /admin"</li>
|
||||
<li>Observe browser behavior and console logs</li>
|
||||
<li>You should land on login page</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Single redirect: /admin → /admin/login</li>
|
||||
<li>Login page loads with NO API calls to /admin/auth/me</li>
|
||||
<li>No loops, no errors in console</li>
|
||||
<li>Form is ready for input</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="clearAllData()" class="btn-danger">Clear All Data</button>
|
||||
<button onclick="navigateToAdmin()" class="btn-primary">Navigate to /admin</button>
|
||||
<button onclick="navigateToLogin()" class="btn-secondary">Go to Login</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 2: Login Success -->
|
||||
<div class="test-section">
|
||||
<h2>Test 2: Successful Login</h2>
|
||||
<p class="test-description">
|
||||
Tests that login works correctly and redirects to dashboard.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Ensure you're on /admin/login</li>
|
||||
<li>Enter valid admin credentials</li>
|
||||
<li>Click "Login"</li>
|
||||
<li>Observe redirect and dashboard load</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Login API call succeeds (check Network tab)</li>
|
||||
<li>Token stored in localStorage</li>
|
||||
<li>Success message shows briefly</li>
|
||||
<li>Redirect to /admin/dashboard after 500ms</li>
|
||||
<li>Dashboard loads with stats and recent vendors</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="navigateToLogin()" class="btn-primary">Go to Login Page</button>
|
||||
<button onclick="checkAuthStatus()" class="btn-secondary">Check Auth Status</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 3: Dashboard Refresh -->
|
||||
<div class="test-section">
|
||||
<h2>Test 3: Dashboard Refresh (Authenticated)</h2>
|
||||
<p class="test-description">
|
||||
Tests that refreshing the dashboard works without redirect loops.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Complete Test 2 (login successfully)</li>
|
||||
<li>On dashboard, press F5 or click "Refresh Page"</li>
|
||||
<li>Observe page reload behavior</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Dashboard reloads normally</li>
|
||||
<li>No redirects to login</li>
|
||||
<li>Stats and vendors load correctly</li>
|
||||
<li>No console errors</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="navigateToDashboard()" class="btn-primary">Go to Dashboard</button>
|
||||
<button onclick="window.location.reload()" class="btn-secondary">Refresh Page</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 4: Expired Token -->
|
||||
<div class="test-section">
|
||||
<h2>Test 4: Expired Token Handling</h2>
|
||||
<p class="test-description">
|
||||
Tests that expired tokens are handled gracefully with redirect to login.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Click "Set Expired Token"</li>
|
||||
<li>Click "Navigate to Dashboard"</li>
|
||||
<li>Observe authentication failure and redirect</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Server detects expired token</li>
|
||||
<li>Returns 401 Unauthorized</li>
|
||||
<li>Browser redirects to /admin/login</li>
|
||||
<li>Token is cleared from localStorage</li>
|
||||
<li>No infinite loops</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="setExpiredToken()" class="btn-warning">Set Expired Token</button>
|
||||
<button onclick="navigateToDashboard()" class="btn-primary">Navigate to Dashboard</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 5: Direct Dashboard Access (No Token) -->
|
||||
<div class="test-section">
|
||||
<h2>Test 5: Direct Dashboard Access (Unauthenticated)</h2>
|
||||
<p class="test-description">
|
||||
Tests that accessing dashboard without token redirects to login.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Click "Clear All Data"</li>
|
||||
<li>Click "Navigate to Dashboard"</li>
|
||||
<li>Observe immediate redirect to login</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Redirect from /admin/dashboard to /admin/login</li>
|
||||
<li>No API calls attempted</li>
|
||||
<li>Login page loads correctly</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="clearAllData()" class="btn-danger">Clear All Data</button>
|
||||
<button onclick="navigateToDashboard()" class="btn-primary">Navigate to Dashboard</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 6: Login Page with Valid Token -->
|
||||
<div class="test-section">
|
||||
<h2>Test 6: Login Page with Valid Token</h2>
|
||||
<p class="test-description">
|
||||
Tests what happens when user visits login page while already authenticated.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Login successfully (Test 2)</li>
|
||||
<li>Click "Go to Login Page" below</li>
|
||||
<li>Observe behavior</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Login page loads</li>
|
||||
<li>Existing token is cleared (init() clears it)</li>
|
||||
<li>Form is displayed normally</li>
|
||||
<li>NO redirect loops</li>
|
||||
<li>NO API calls to validate token</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="setValidToken()" class="btn-success">Set Valid Token (Mock)</button>
|
||||
<button onclick="navigateToLogin()" class="btn-primary">Go to Login Page</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Panel -->
|
||||
<div class="status-panel">
|
||||
<h3>🔍 Current Auth Status</h3>
|
||||
<div id="statusDisplay">
|
||||
<div class="status-item">
|
||||
<span class="status-label">Current URL:</span>
|
||||
<span class="status-value" id="currentUrl">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Has admin_token:</span>
|
||||
<span class="status-value" id="hasToken">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Has admin_user:</span>
|
||||
<span class="status-value" id="hasUser">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Token Preview:</span>
|
||||
<span class="status-value" id="tokenPreview">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Username:</span>
|
||||
<span class="status-value" id="username">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="updateStatus()" style="margin-top: 15px; background: #38bdf8; color: #0f172a; padding: 8px 16px; border-radius: 4px; font-size: 12px; cursor: pointer; border: none;">
|
||||
🔄 Refresh Status
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Warning Box -->
|
||||
<div class="warning-box">
|
||||
<h3>⚠️ Important Notes</h3>
|
||||
<ul>
|
||||
<li>Always check browser console for detailed logs</li>
|
||||
<li>Use Network tab to see actual HTTP requests and redirects</li>
|
||||
<li>Clear browser cache if you see unexpected behavior</li>
|
||||
<li>Make sure FastAPI server is running on localhost:8000</li>
|
||||
<li>Valid admin credentials required for login tests</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Update status display
|
||||
function updateStatus() {
|
||||
const token = localStorage.getItem('admin_token');
|
||||
const userStr = localStorage.getItem('admin_user');
|
||||
let user = null;
|
||||
|
||||
try {
|
||||
user = userStr ? JSON.parse(userStr) : null;
|
||||
} catch (e) {
|
||||
console.error('Failed to parse user data:', e);
|
||||
}
|
||||
|
||||
document.getElementById('currentUrl').textContent = window.location.href;
|
||||
|
||||
const hasTokenEl = document.getElementById('hasToken');
|
||||
hasTokenEl.textContent = token ? 'Yes' : 'No';
|
||||
hasTokenEl.className = token ? 'status-value' : 'status-value false';
|
||||
|
||||
const hasUserEl = document.getElementById('hasUser');
|
||||
hasUserEl.textContent = user ? 'Yes' : 'No';
|
||||
hasUserEl.className = user ? 'status-value' : 'status-value false';
|
||||
|
||||
document.getElementById('tokenPreview').textContent = token
|
||||
? token.substring(0, 30) + '...'
|
||||
: 'No token';
|
||||
|
||||
document.getElementById('username').textContent = user?.username || 'Not logged in';
|
||||
|
||||
console.log('📊 Status Updated:', {
|
||||
hasToken: !!token,
|
||||
hasUser: !!user,
|
||||
user: user
|
||||
});
|
||||
}
|
||||
|
||||
// Test functions
|
||||
function clearAllData() {
|
||||
console.log('🗑️ Clearing all localStorage data...');
|
||||
localStorage.clear();
|
||||
console.log('✅ All data cleared');
|
||||
alert('✅ All localStorage data cleared!\n\nCheck console for details.');
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
function navigateToAdmin() {
|
||||
console.log('🚀 Navigating to /admin...');
|
||||
window.location.href = '/admin';
|
||||
}
|
||||
|
||||
function navigateToLogin() {
|
||||
console.log('🚀 Navigating to /admin/login...');
|
||||
window.location.href = '/admin/login';
|
||||
}
|
||||
|
||||
function navigateToDashboard() {
|
||||
console.log('🚀 Navigating to /admin/dashboard...');
|
||||
window.location.href = '/admin/dashboard';
|
||||
}
|
||||
|
||||
function checkAuthStatus() {
|
||||
updateStatus();
|
||||
alert('Check console and status panel for auth details.');
|
||||
}
|
||||
|
||||
function setExpiredToken() {
|
||||
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.invalidexpiredtoken';
|
||||
console.log('⚠️ Setting expired/invalid token...');
|
||||
localStorage.setItem('admin_token', expiredToken);
|
||||
localStorage.setItem('admin_user', JSON.stringify({
|
||||
id: 1,
|
||||
username: 'test_expired',
|
||||
role: 'admin'
|
||||
}));
|
||||
console.log('✅ Expired token set');
|
||||
alert('⚠️ Expired token set!\n\nNow try navigating to dashboard.');
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
function setValidToken() {
|
||||
// This is a mock token - won't actually work with backend
|
||||
const mockToken = 'mock_valid_token_' + Date.now();
|
||||
console.log('✅ Setting mock valid token...');
|
||||
localStorage.setItem('admin_token', mockToken);
|
||||
localStorage.setItem('admin_user', JSON.stringify({
|
||||
id: 1,
|
||||
username: 'test_user',
|
||||
role: 'admin'
|
||||
}));
|
||||
console.log('✅ Mock token set (will not work with real backend)');
|
||||
alert('✅ Mock token set!\n\nNote: This is a fake token and won\'t work with the real backend.');
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
// Log level control
|
||||
function setLogLevel(level) {
|
||||
console.log(`📊 Setting log level to ${level}...`);
|
||||
|
||||
// Note: This only works if login.js and api-client.js are loaded
|
||||
// In production, you'd need to reload the page or use a more sophisticated approach
|
||||
if (typeof LOG_LEVEL !== 'undefined') {
|
||||
window.LOG_LEVEL = level;
|
||||
document.getElementById('currentLoginLevel').textContent = level;
|
||||
console.log('✅ LOGIN log level set to', level);
|
||||
} else {
|
||||
console.warn('⚠️ LOG_LEVEL not found (login.js not loaded)');
|
||||
}
|
||||
|
||||
if (typeof API_LOG_LEVEL !== 'undefined') {
|
||||
window.API_LOG_LEVEL = level;
|
||||
document.getElementById('currentApiLevel').textContent = level;
|
||||
console.log('✅ API log level set to', level);
|
||||
} else {
|
||||
console.warn('⚠️ API_LOG_LEVEL not found (api-client.js not loaded)');
|
||||
}
|
||||
|
||||
alert(`Log level set to ${level}\n\n0 = None\n1 = Errors\n2 = Warnings\n3 = Info\n4 = Debug\n\nNote: Changes apply to current page. Reload to apply to all scripts.`);
|
||||
}
|
||||
|
||||
// Initialize status on load
|
||||
updateStatus();
|
||||
|
||||
// Auto-refresh status every 2 seconds
|
||||
setInterval(updateStatus, 2000);
|
||||
|
||||
console.log('🧪 Auth Flow Testing Script Loaded');
|
||||
console.log('📊 Use the buttons above to run tests');
|
||||
console.log('🔍 Watch browser console and Network tab for details');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user