Files
orion/app/modules/tenancy/static/store/js/login.js
Samir Boulahtit 319900623a
Some checks failed
CI / ruff (push) Successful in 14s
CI / pytest (push) Failing after 50m12s
CI / validate (push) Successful in 25s
CI / dependency-scanning (push) Successful in 32s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
feat: add SQL query tool, platform debug, loyalty settings, and multi-module improvements
- Add admin SQL query tool with saved queries, schema explorer presets,
  and collapsible category sections (dev_tools module)
- Add platform debug tool for admin diagnostics
- Add loyalty settings page with owner-only access control
- Fix loyalty settings owner check (use currentUser instead of window.__userData)
- Replace HTTPException with AuthorizationException in loyalty routes
- Expand loyalty module with PIN service, Apple Wallet, program management
- Improve store login with platform detection and multi-platform support
- Update billing feature gates and subscription services
- Add store platform sync improvements and remove is_primary column
- Add unit tests for loyalty (PIN, points, stamps, program services)
- Update i18n translations across dev_tools locales

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 20:08:07 +01:00

242 lines
9.4 KiB
JavaScript

// app/static/store/js/login.js
// noqa: js-003 - Standalone login page without store layout
// noqa: js-004 - Standalone page has no currentPage sidebar highlight
/**
* Store login page logic
*/
// Create custom logger for store login page
const storeLoginLog = window.LogConfig.createLogger('STORE-LOGIN');
function languageSelector(currentLang, enabledLanguages) {
return {
currentLang: currentLang || 'fr',
languages: enabledLanguages || ['en', 'fr', 'de', 'lb'],
async setLanguage(lang) {
if (lang === this.currentLang) return;
try {
// noqa: JS-008 - Login page has no apiClient; raw fetch is intentional
await fetch('/api/v1/platform/language/set', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ language: lang }),
});
window.location.reload();
} catch (error) {
storeLoginLog.error('Failed to set language:', error);
}
},
};
}
function storeLogin() {
return {
credentials: {
username: '',
password: ''
},
store: null,
storeCode: null,
loading: false,
checked: false,
error: '',
success: '',
errors: {},
dark: false,
async init() {
// Guard against multiple initialization
if (window._storeLoginInitialized) return;
window._storeLoginInitialized = true;
try {
storeLoginLog.info('=== STORE LOGIN PAGE INITIALIZING ===');
// Load theme
const theme = localStorage.getItem('theme');
if (theme === 'dark') {
this.dark = true;
}
storeLoginLog.debug('Dark mode:', this.dark);
// Get store code from URL path
// Supports both /store/{code}/login and /platforms/{platform}/store/{code}/login
const pathSegments = window.location.pathname.split('/').filter(Boolean);
const storeIndex = pathSegments.indexOf('store');
if (storeIndex !== -1 && pathSegments[storeIndex + 1]) {
this.storeCode = pathSegments[storeIndex + 1];
storeLoginLog.debug('Store code from URL:', this.storeCode);
await this.loadStore();
}
this.checked = true;
storeLoginLog.info('=== STORE LOGIN PAGE INITIALIZATION COMPLETE ===');
} catch (error) {
storeLoginLog.error('Failed to initialize login page:', error);
this.checked = true;
}
},
async loadStore() {
storeLoginLog.info('Loading store information...');
this.loading = true;
try {
const response = await apiClient.get(`/store/info/${this.storeCode}`);
this.store = response;
storeLoginLog.info('Store loaded successfully:', {
code: this.store.code,
name: this.store.name
});
} catch (error) {
window.LogConfig.logError(error, 'Load Store');
this.error = 'Failed to load store information';
} finally {
this.loading = false;
}
},
async handleLogin() {
storeLoginLog.info('=== STORE LOGIN ATTEMPT STARTED ===');
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) {
storeLoginLog.warn('Validation failed:', this.errors);
this.loading = false;
return;
}
storeLoginLog.info('Calling store login API...');
storeLoginLog.debug('Username:', this.credentials.username);
storeLoginLog.debug('Store code:', this.storeCode);
window.LogConfig.logApiCall('POST', '/store/auth/login', {
username: this.credentials.username,
store_code: this.storeCode
}, 'request');
const startTime = performance.now();
const response = await apiClient.post('/store/auth/login', {
email_or_username: this.credentials.username,
password: this.credentials.password,
store_code: this.storeCode,
platform_code: window.STORE_PLATFORM_CODE || localStorage.getItem('store_platform') || null
});
const duration = performance.now() - startTime;
window.LogConfig.logApiCall('POST', '/store/auth/login', {
hasToken: !!response.access_token,
user: response.user?.username
}, 'response');
window.LogConfig.logPerformance('Store Login', duration);
storeLoginLog.info('Login successful!');
storeLoginLog.debug('Storing authentication data...');
// Store token with correct key that apiClient expects
localStorage.setItem('store_token', response.access_token);
localStorage.setItem('currentUser', JSON.stringify(response.user));
localStorage.setItem('storeCode', this.storeCode);
if (response.platform_code) {
localStorage.setItem('store_platform', response.platform_code);
}
storeLoginLog.debug('Token stored as store_token in localStorage');
this.success = 'Login successful! Redirecting...';
// Build platform-aware base path
const platformCode = window.STORE_PLATFORM_CODE;
const basePath = platformCode
? `/platforms/${platformCode}/store/${this.storeCode}`
: `/store/${this.storeCode}`;
// Check for last visited page (saved before logout)
const lastPage = localStorage.getItem('store_last_visited_page');
let redirectTo = `${basePath}/dashboard`;
if (lastPage && !lastPage.includes('/login') && !lastPage.includes('/onboarding')) {
// Extract the store-relative path (strip any existing prefix)
const storePathMatch = lastPage.match(/\/store\/[^/]+(\/.*)/);
if (storePathMatch) {
redirectTo = `${basePath}${storePathMatch[1]}`;
}
}
storeLoginLog.info('Last visited page:', lastPage);
storeLoginLog.info('Redirecting to:', redirectTo);
setTimeout(() => {
window.location.href = redirectTo;
}, 1000);
} catch (error) {
window.LogConfig.logError(error, 'Store Login');
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 store';
} else {
this.error = error.message || 'Login failed. Please try again.';
}
storeLoginLog.info('Error message displayed to user:', this.error);
} finally {
this.loading = false;
storeLoginLog.info('=== STORE LOGIN ATTEMPT FINISHED ===');
}
},
// Forgot password state
rememberMe: false,
showForgotPassword: false,
forgotPasswordEmail: '',
forgotPasswordLoading: false,
async handleForgotPassword() {
storeLoginLog.info('=== FORGOT PASSWORD ATTEMPT ===');
if (!this.forgotPasswordEmail.trim()) {
this.error = 'Email is required';
return;
}
this.forgotPasswordLoading = true;
this.clearErrors();
try {
await apiClient.post('/store/auth/forgot-password', {
email: this.forgotPasswordEmail.trim()
});
this.success = 'If an account exists with this email, a password reset link has been sent.';
this.forgotPasswordEmail = '';
} catch (error) {
window.LogConfig.logError(error, 'ForgotPassword');
this.error = error.message || 'Failed to send reset email. Please try again.';
} finally {
this.forgotPasswordLoading = false;
}
},
clearErrors() {
storeLoginLog.debug('Clearing form errors');
this.error = '';
this.errors = {};
},
toggleDarkMode() {
storeLoginLog.debug('Toggling dark mode...');
this.dark = !this.dark;
localStorage.setItem('theme', this.dark ? 'dark' : 'light');
storeLoginLog.info('Dark mode:', this.dark ? 'ON' : 'OFF');
}
};
}
storeLoginLog.info('Store login module loaded');