This commit completes the migration to a fully module-driven architecture: ## Models Migration - Moved all domain models from models/database/ to their respective modules: - tenancy: User, Admin, Vendor, Company, Platform, VendorDomain, etc. - cms: MediaFile, VendorTheme - messaging: Email, VendorEmailSettings, VendorEmailTemplate - core: AdminMenuConfig - models/database/ now only contains Base and TimestampMixin (infrastructure) ## Schemas Migration - Moved all domain schemas from models/schema/ to their respective modules: - tenancy: company, vendor, admin, team, vendor_domain - cms: media, image, vendor_theme - messaging: email - models/schema/ now only contains base.py and auth.py (infrastructure) ## Routes Migration - Moved admin routes from app/api/v1/admin/ to modules: - menu_config.py -> core module - modules.py -> tenancy module - module_config.py -> tenancy module - app/api/v1/admin/ now only aggregates auto-discovered module routes ## Menu System - Implemented module-driven menu system with MenuDiscoveryService - Extended FrontendType enum: PLATFORM, ADMIN, VENDOR, STOREFRONT - Added MenuItemDefinition and MenuSectionDefinition dataclasses - Each module now defines its own menu items in definition.py - MenuService integrates with MenuDiscoveryService for template rendering ## Documentation - Updated docs/architecture/models-structure.md - Updated docs/architecture/menu-management.md - Updated architecture validation rules for new exceptions ## Architecture Validation - Updated MOD-019 rule to allow base.py in models/schema/ - Created core module exceptions.py and schemas/ directory - All validation errors resolved (only warnings remain) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
200 lines
5.8 KiB
JavaScript
200 lines
5.8 KiB
JavaScript
// static/vendor/js/profile.js
|
|
/**
|
|
* Vendor profile management page logic
|
|
* Edit vendor business profile and contact information
|
|
*/
|
|
|
|
const vendorProfileLog = window.LogConfig.loggers.vendorProfile ||
|
|
window.LogConfig.createLogger('vendorProfile', false);
|
|
|
|
vendorProfileLog.info('Loading...');
|
|
|
|
function vendorProfile() {
|
|
vendorProfileLog.info('vendorProfile() called');
|
|
|
|
return {
|
|
// Inherit base layout state
|
|
...data(),
|
|
|
|
// Set page identifier
|
|
currentPage: 'profile',
|
|
|
|
// Loading states
|
|
loading: true,
|
|
error: '',
|
|
saving: false,
|
|
|
|
// Profile data
|
|
profile: null,
|
|
|
|
// Edit form
|
|
form: {
|
|
name: '',
|
|
contact_email: '',
|
|
contact_phone: '',
|
|
website: '',
|
|
business_address: '',
|
|
tax_number: '',
|
|
description: ''
|
|
},
|
|
|
|
// Form validation
|
|
errors: {},
|
|
|
|
// Track if form has changes
|
|
hasChanges: false,
|
|
|
|
async init() {
|
|
// Load i18n translations
|
|
await I18n.loadModule('tenancy');
|
|
|
|
vendorProfileLog.info('Profile init() called');
|
|
|
|
// Guard against multiple initialization
|
|
if (window._vendorProfileInitialized) {
|
|
vendorProfileLog.warn('Already initialized, skipping');
|
|
return;
|
|
}
|
|
window._vendorProfileInitialized = true;
|
|
|
|
// IMPORTANT: Call parent init first to set vendorCode from URL
|
|
const parentInit = data().init;
|
|
if (parentInit) {
|
|
await parentInit.call(this);
|
|
}
|
|
|
|
try {
|
|
await this.loadProfile();
|
|
} catch (error) {
|
|
vendorProfileLog.error('Init failed:', error);
|
|
this.error = 'Failed to initialize profile page';
|
|
}
|
|
|
|
vendorProfileLog.info('Profile initialization complete');
|
|
},
|
|
|
|
/**
|
|
* Load vendor profile
|
|
*/
|
|
async loadProfile() {
|
|
this.loading = true;
|
|
this.error = '';
|
|
|
|
try {
|
|
const response = await apiClient.get(`/vendor/profile`);
|
|
|
|
this.profile = response;
|
|
this.form = {
|
|
name: response.name || '',
|
|
contact_email: response.contact_email || '',
|
|
contact_phone: response.contact_phone || '',
|
|
website: response.website || '',
|
|
business_address: response.business_address || '',
|
|
tax_number: response.tax_number || '',
|
|
description: response.description || ''
|
|
};
|
|
|
|
this.hasChanges = false;
|
|
vendorProfileLog.info('Loaded profile:', this.profile.vendor_code);
|
|
} catch (error) {
|
|
vendorProfileLog.error('Failed to load profile:', error);
|
|
this.error = error.message || 'Failed to load profile';
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Mark form as changed
|
|
*/
|
|
markChanged() {
|
|
this.hasChanges = true;
|
|
},
|
|
|
|
/**
|
|
* Validate form
|
|
*/
|
|
validateForm() {
|
|
this.errors = {};
|
|
|
|
if (!this.form.name?.trim()) {
|
|
this.errors.name = 'Business name is required';
|
|
}
|
|
|
|
if (this.form.contact_email && !this.isValidEmail(this.form.contact_email)) {
|
|
this.errors.contact_email = 'Invalid email address';
|
|
}
|
|
|
|
if (this.form.website && !this.isValidUrl(this.form.website)) {
|
|
this.errors.website = 'Invalid URL format';
|
|
}
|
|
|
|
return Object.keys(this.errors).length === 0;
|
|
},
|
|
|
|
/**
|
|
* Check if email is valid
|
|
*/
|
|
isValidEmail(email) {
|
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
},
|
|
|
|
/**
|
|
* Check if URL is valid
|
|
*/
|
|
isValidUrl(url) {
|
|
try {
|
|
new URL(url);
|
|
return true;
|
|
} catch {
|
|
return url.match(/^(https?:\/\/)?[\w-]+(\.[\w-]+)+/) !== null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Save profile changes
|
|
*/
|
|
async saveProfile() {
|
|
if (!this.validateForm()) {
|
|
Utils.showToast(I18n.t('tenancy.messages.please_fix_the_errors_before_saving'), 'error');
|
|
return;
|
|
}
|
|
|
|
this.saving = true;
|
|
try {
|
|
await apiClient.put(`/vendor/profile`, this.form);
|
|
|
|
Utils.showToast(I18n.t('tenancy.messages.profile_updated_successfully'), 'success');
|
|
vendorProfileLog.info('Profile updated');
|
|
|
|
this.hasChanges = false;
|
|
await this.loadProfile();
|
|
} catch (error) {
|
|
vendorProfileLog.error('Failed to save profile:', error);
|
|
Utils.showToast(error.message || 'Failed to save profile', 'error');
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Reset form to original values
|
|
*/
|
|
resetForm() {
|
|
if (this.profile) {
|
|
this.form = {
|
|
name: this.profile.name || '',
|
|
contact_email: this.profile.contact_email || '',
|
|
contact_phone: this.profile.contact_phone || '',
|
|
website: this.profile.website || '',
|
|
business_address: this.profile.business_address || '',
|
|
tax_number: this.profile.tax_number || '',
|
|
description: this.profile.description || ''
|
|
};
|
|
}
|
|
this.hasChanges = false;
|
|
this.errors = {};
|
|
}
|
|
};
|
|
}
|