admin and vendor backends features
This commit is contained in:
114
static/js/vendor/dashboard.js
vendored
114
static/js/vendor/dashboard.js
vendored
@@ -1 +1,113 @@
|
||||
// Vendor dashboard
|
||||
// Vendor Dashboard Component
|
||||
function vendorDashboard() {
|
||||
return {
|
||||
currentUser: {},
|
||||
vendor: null,
|
||||
vendorRole: '',
|
||||
currentSection: 'dashboard',
|
||||
loading: false,
|
||||
stats: {
|
||||
products_count: 0,
|
||||
orders_count: 0,
|
||||
customers_count: 0,
|
||||
revenue: 0
|
||||
},
|
||||
|
||||
init() {
|
||||
if (!this.checkAuth()) {
|
||||
return;
|
||||
}
|
||||
this.loadDashboard();
|
||||
},
|
||||
|
||||
checkAuth() {
|
||||
const token = localStorage.getItem('vendor_token');
|
||||
const user = localStorage.getItem('vendor_user');
|
||||
const vendorContext = localStorage.getItem('vendor_context');
|
||||
const vendorRole = localStorage.getItem('vendor_role');
|
||||
|
||||
if (!token || !user || !vendorContext) {
|
||||
// Get vendor code from URL
|
||||
const vendorCode = this.getVendorCodeFromUrl();
|
||||
const redirectUrl = vendorCode ?
|
||||
`/vendor/${vendorCode}/login` :
|
||||
'/static/vendor/login.html';
|
||||
window.location.href = redirectUrl;
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.currentUser = JSON.parse(user);
|
||||
this.vendor = JSON.parse(vendorContext);
|
||||
this.vendorRole = vendorRole || 'Member';
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Error parsing stored data:', e);
|
||||
localStorage.removeItem('vendor_token');
|
||||
localStorage.removeItem('vendor_user');
|
||||
localStorage.removeItem('vendor_context');
|
||||
localStorage.removeItem('vendor_role');
|
||||
window.location.href = '/static/vendor/login.html';
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
getVendorCodeFromUrl() {
|
||||
// Try to get vendor code from URL path
|
||||
const pathParts = window.location.pathname.split('/').filter(p => p);
|
||||
const vendorIndex = pathParts.indexOf('vendor');
|
||||
if (vendorIndex !== -1 && pathParts[vendorIndex + 1]) {
|
||||
const code = pathParts[vendorIndex + 1];
|
||||
if (!['login', 'dashboard', 'admin', 'products', 'orders'].includes(code.toLowerCase())) {
|
||||
return code.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to query parameter
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get('vendor');
|
||||
},
|
||||
|
||||
async handleLogout() {
|
||||
const confirmed = await Utils.confirm(
|
||||
'Are you sure you want to logout?',
|
||||
'Confirm Logout'
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
localStorage.removeItem('vendor_token');
|
||||
localStorage.removeItem('vendor_user');
|
||||
localStorage.removeItem('vendor_context');
|
||||
localStorage.removeItem('vendor_role');
|
||||
|
||||
Utils.showToast('Logged out successfully', 'success', 2000);
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = `/vendor/${this.vendor.vendor_code}/login`;
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
|
||||
async loadDashboard() {
|
||||
this.loading = true;
|
||||
try {
|
||||
// In future slices, load actual dashboard data
|
||||
// const data = await apiClient.get(`/vendor/dashboard/stats`);
|
||||
// this.stats = data;
|
||||
|
||||
// For now, show placeholder data
|
||||
this.stats = {
|
||||
products_count: 0,
|
||||
orders_count: 0,
|
||||
customers_count: 0,
|
||||
revenue: 0
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to load dashboard:', error);
|
||||
Utils.showToast('Failed to load dashboard data', 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
170
static/js/vendor/login.js
vendored
Normal file
170
static/js/vendor/login.js
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
// Vendor Login Component
|
||||
function vendorLogin() {
|
||||
return {
|
||||
vendor: null,
|
||||
credentials: {
|
||||
username: '',
|
||||
password: ''
|
||||
},
|
||||
loading: false,
|
||||
checked: false,
|
||||
error: null,
|
||||
success: null,
|
||||
errors: {},
|
||||
|
||||
init() {
|
||||
// Check if already logged in
|
||||
if (this.checkExistingAuth()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect vendor from URL
|
||||
this.detectVendor();
|
||||
},
|
||||
|
||||
checkExistingAuth() {
|
||||
const token = localStorage.getItem('vendor_token');
|
||||
const vendorContext = localStorage.getItem('vendor_context');
|
||||
|
||||
if (token && vendorContext) {
|
||||
try {
|
||||
const vendor = JSON.parse(vendorContext);
|
||||
window.location.href = `/vendor/${vendor.vendor_code}/dashboard`;
|
||||
return true;
|
||||
} catch (e) {
|
||||
localStorage.removeItem('vendor_token');
|
||||
localStorage.removeItem('vendor_context');
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
async detectVendor() {
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const vendorCode = this.getVendorCodeFromUrl();
|
||||
|
||||
if (!vendorCode) {
|
||||
this.error = 'Vendor code not found in URL. Please use the correct vendor login link.';
|
||||
this.checked = true;
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Detected vendor code:', vendorCode);
|
||||
|
||||
// Fetch vendor information
|
||||
const response = await fetch(`/api/v1/public/vendors/by-code/${vendorCode}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Vendor not found');
|
||||
}
|
||||
|
||||
this.vendor = await response.json();
|
||||
this.checked = true;
|
||||
console.log('Loaded vendor:', this.vendor);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error detecting vendor:', error);
|
||||
this.error = 'Unable to load vendor information. The vendor may not exist or is inactive.';
|
||||
this.checked = true;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
getVendorCodeFromUrl() {
|
||||
// Try multiple methods to get vendor code
|
||||
|
||||
// Method 1: From URL path /vendor/VENDORCODE/login or /vendor/VENDORCODE/
|
||||
const pathParts = window.location.pathname.split('/').filter(p => p);
|
||||
const vendorIndex = pathParts.indexOf('vendor');
|
||||
if (vendorIndex !== -1 && pathParts[vendorIndex + 1]) {
|
||||
const code = pathParts[vendorIndex + 1];
|
||||
// Don't return if it's a generic route like 'login', 'dashboard', etc.
|
||||
if (!['login', 'dashboard', 'admin', 'products', 'orders'].includes(code.toLowerCase())) {
|
||||
return code.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: From query parameter ?vendor=VENDORCODE
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const queryVendor = urlParams.get('vendor');
|
||||
if (queryVendor) {
|
||||
return queryVendor.toUpperCase();
|
||||
}
|
||||
|
||||
// Method 3: From subdomain (for production)
|
||||
const hostname = window.location.hostname;
|
||||
const parts = hostname.split('.');
|
||||
if (parts.length > 2 && parts[0] !== 'www') {
|
||||
// Assume subdomain is vendor code
|
||||
return parts[0].toUpperCase();
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
clearErrors() {
|
||||
this.error = null;
|
||||
this.errors = {};
|
||||
},
|
||||
|
||||
validateForm() {
|
||||
this.clearErrors();
|
||||
let isValid = true;
|
||||
|
||||
if (!this.credentials.username.trim()) {
|
||||
this.errors.username = 'Username is required';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (!this.credentials.password) {
|
||||
this.errors.password = 'Password is required';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
},
|
||||
|
||||
async handleLogin() {
|
||||
if (!this.validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.clearErrors();
|
||||
|
||||
try {
|
||||
const response = await apiClient.post('/vendor/auth/login', {
|
||||
username: this.credentials.username.trim(),
|
||||
password: this.credentials.password,
|
||||
vendor_code: this.vendor.vendor_code
|
||||
});
|
||||
|
||||
// Store authentication data
|
||||
localStorage.setItem('vendor_token', response.access_token);
|
||||
localStorage.setItem('vendor_user', JSON.stringify(response.user));
|
||||
localStorage.setItem('vendor_context', JSON.stringify(response.vendor));
|
||||
localStorage.setItem('vendor_role', response.vendor_role);
|
||||
|
||||
// Show success message
|
||||
this.success = 'Login successful! Redirecting...';
|
||||
Utils.showToast('Login successful!', 'success', 2000);
|
||||
|
||||
// Redirect after short delay
|
||||
setTimeout(() => {
|
||||
window.location.href = `/vendor/${this.vendor.vendor_code}/dashboard`;
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
this.error = error.message || 'Login failed. Please check your credentials.';
|
||||
Utils.showToast(this.error, 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
static/js/vendor/vendor-layout-templates.js
vendored
Normal file
67
static/js/vendor/vendor-layout-templates.js
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Vendor Layout Templates
|
||||
* Header and Sidebar specific to Vendor Dashboard
|
||||
*/
|
||||
|
||||
window.vendorLayoutTemplates = {
|
||||
|
||||
/**
|
||||
* Vendor Header
|
||||
*/
|
||||
header: () => `
|
||||
<header class="vendor-header">
|
||||
<div class="header-left">
|
||||
<button @click="toggleMenu()" class="menu-toggle">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
<h1 class="header-title">Vendor Dashboard</h1>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<span class="user-name" x-text="vendor?.name || 'Vendor'"></span>
|
||||
<button @click="confirmLogout()" class="btn-logout">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
`,
|
||||
|
||||
/**
|
||||
* Vendor Sidebar
|
||||
*/
|
||||
sidebar: () => `
|
||||
<aside class="vendor-sidebar" :class="{ 'open': menuOpen }">
|
||||
<nav class="sidebar-nav">
|
||||
<a href="/vendor/dashboard.html"
|
||||
class="nav-item"
|
||||
:class="{ 'active': isActive('dashboard') }">
|
||||
<i class="fas fa-tachometer-alt"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="/vendor/products.html"
|
||||
class="nav-item"
|
||||
:class="{ 'active': isActive('products') }">
|
||||
<i class="fas fa-box"></i>
|
||||
<span>Products</span>
|
||||
</a>
|
||||
<a href="/vendor/orders.html"
|
||||
class="nav-item"
|
||||
:class="{ 'active': isActive('orders') }">
|
||||
<i class="fas fa-shopping-bag"></i>
|
||||
<span>Orders</span>
|
||||
</a>
|
||||
<a href="/vendor/customers.html"
|
||||
class="nav-item"
|
||||
:class="{ 'active': isActive('customers') }">
|
||||
<i class="fas fa-users"></i>
|
||||
<span>Customers</span>
|
||||
</a>
|
||||
<a href="/vendor/settings.html"
|
||||
class="nav-item"
|
||||
:class="{ 'active': isActive('settings') }">
|
||||
<i class="fas fa-cog"></i>
|
||||
<span>Settings</span>
|
||||
</a>
|
||||
</nav>
|
||||
</aside>
|
||||
`
|
||||
};
|
||||
Reference in New Issue
Block a user