migrating vendor frontend to new architecture

This commit is contained in:
2025-10-31 20:51:30 +01:00
parent 9420483ae6
commit 9611c03a36
25 changed files with 1618 additions and 286 deletions

View File

@@ -0,0 +1,6 @@
This favicon was generated using the following font:
- Font Title: Kotta One
- Font Author: undefined
- Font Source: https://fonts.gstatic.com/s/kottaone/v20/S6u_w41LXzPc_jlfNWqPHA3s5dwt7w.ttf
- Font License: undefined)

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

81
static/vendor/js/dashboard.js vendored Normal file
View File

@@ -0,0 +1,81 @@
// app/static/vendor/js/dashboard.js
/**
* Vendor dashboard page logic
*/
function vendorDashboard() {
return {
loading: false,
error: '',
stats: {
products_count: 0,
orders_count: 0,
customers_count: 0,
revenue: 0
},
recentOrders: [],
recentProducts: [],
async init() {
await this.loadDashboardData();
},
async loadDashboardData() {
this.loading = true;
this.error = '';
try {
// Load stats
const statsResponse = await apiClient.get(
`/api/v1/vendors/${this.vendorCode}/stats`
);
this.stats = statsResponse;
// Load recent orders
const ordersResponse = await apiClient.get(
`/api/v1/vendors/${this.vendorCode}/orders?limit=5&sort=created_at:desc`
);
this.recentOrders = ordersResponse.items || [];
// Load recent products
const productsResponse = await apiClient.get(
`/api/v1/vendors/${this.vendorCode}/products?limit=5&sort=created_at:desc`
);
this.recentProducts = productsResponse.items || [];
logInfo('Dashboard data loaded', {
stats: this.stats,
orders: this.recentOrders.length,
products: this.recentProducts.length
});
} catch (error) {
logError('Failed to load dashboard data', error);
this.error = 'Failed to load dashboard data. Please try refreshing the page.';
} finally {
this.loading = false;
}
},
async refresh() {
await this.loadDashboardData();
},
formatCurrency(amount) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount || 0);
},
formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}
};
}

104
static/vendor/js/init-alpine.js vendored Normal file
View File

@@ -0,0 +1,104 @@
// app/static/vendor/js/init-alpine.js
/**
* Alpine.js initialization for vendor pages
* Provides common data and methods for all vendor pages
*/
function data() {
return {
dark: false,
isSideMenuOpen: false,
isNotificationsMenuOpen: false,
isProfileMenuOpen: false,
currentPage: '',
currentUser: {},
vendor: null,
vendorCode: null,
init() {
// Set current page from URL
const path = window.location.pathname;
const segments = path.split('/').filter(Boolean);
this.currentPage = segments[segments.length - 1] || 'dashboard';
// Get vendor code from URL
if (segments[0] === 'vendor' && segments[1]) {
this.vendorCode = segments[1];
}
// Load user from localStorage
const user = localStorage.getItem('currentUser');
if (user) {
this.currentUser = JSON.parse(user);
}
// Load theme preference
const theme = localStorage.getItem('theme');
if (theme === 'dark') {
this.dark = true;
}
// Load vendor info
this.loadVendorInfo();
},
async loadVendorInfo() {
if (!this.vendorCode) return;
try {
const response = await apiClient.get(`/api/v1/vendors/${this.vendorCode}`);
this.vendor = response;
logDebug('Vendor info loaded', this.vendor);
} catch (error) {
logError('Failed to load vendor info', error);
}
},
toggleSideMenu() {
this.isSideMenuOpen = !this.isSideMenuOpen;
},
closeSideMenu() {
this.isSideMenuOpen = false;
},
toggleNotificationsMenu() {
this.isNotificationsMenuOpen = !this.isNotificationsMenuOpen;
if (this.isNotificationsMenuOpen) {
this.isProfileMenuOpen = false;
}
},
closeNotificationsMenu() {
this.isNotificationsMenuOpen = false;
},
toggleProfileMenu() {
this.isProfileMenuOpen = !this.isProfileMenuOpen;
if (this.isProfileMenuOpen) {
this.isNotificationsMenuOpen = false;
}
},
closeProfileMenu() {
this.isProfileMenuOpen = false;
},
toggleTheme() {
this.dark = !this.dark;
localStorage.setItem('theme', this.dark ? 'dark' : 'light');
},
async handleLogout() {
try {
await apiClient.post('/api/v1/vendor/auth/logout');
} catch (error) {
logError('Logout error', error);
} finally {
localStorage.removeItem('accessToken');
localStorage.removeItem('currentUser');
window.location.href = `/vendor/${this.vendorCode}/login`;
}
}
};
}

110
static/vendor/js/login.js vendored Normal file
View File

@@ -0,0 +1,110 @@
// app/static/vendor/js/login.js
/**
* Vendor login page logic
*/
// ✅ Use centralized logger - ONE LINE!
// Create custom logger for login page
const loginLog = window.LogConfig.createLogger('LOGIN');
function vendorLogin() {
return {
credentials: {
username: '',
password: ''
},
vendor: null,
vendorCode: null,
loading: false,
checked: false,
error: '',
success: '',
errors: {},
dark: false,
async init() {
// Load theme
const theme = localStorage.getItem('theme');
if (theme === 'dark') {
this.dark = true;
}
// Get vendor code from URL path
const pathSegments = window.location.pathname.split('/').filter(Boolean);
if (pathSegments[0] === 'vendor' && pathSegments[1]) {
this.vendorCode = pathSegments[1];
await this.loadVendor();
}
this.checked = true;
},
async loadVendor() {
this.loading = true;
try {
const response = await apiClient.get(`/vendor/${this.vendorCode}`);
this.vendor = response;
logInfo('Vendor loaded', this.vendor);
} catch (error) {
logError('Failed to load vendor', error);
this.error = 'Failed to load vendor information';
} finally {
this.loading = false;
}
},
async handleLogin() {
this.clearErrors();
this.loading = true;
try {
if (!this.credentials.username) {
this.errors.username = 'Username is required';
}
if (!this.credentials.password) {
this.errors.password = 'Password is required';
}
if (Object.keys(this.errors).length > 0) {
this.loading = false;
return;
}
const response = await apiClient.post('/vendor/auth/login', {
username: this.credentials.username,
password: this.credentials.password,
vendor_code: this.vendorCode
});
logInfo('Login successful', response);
localStorage.setItem('accessToken', response.access_token);
localStorage.setItem('currentUser', JSON.stringify(response.user));
localStorage.setItem('vendorCode', this.vendorCode);
this.success = 'Login successful! Redirecting...';
setTimeout(() => {
window.location.href = `/vendor/${this.vendorCode}/dashboard`;
}, 1000);
} catch (error) {
logError('Login failed', error);
if (error.status === 401) {
this.error = 'Invalid username or password';
} else if (error.status === 403) {
this.error = 'Your account does not have access to this vendor';
} else {
this.error = error.message || 'Login failed. Please try again.';
}
} finally {
this.loading = false;
}
},
clearErrors() {
this.error = '';
this.errors = {};
}
};
}