Files
orion/app/modules/tenancy/static/store/js/roles.js
Samir Boulahtit c82210795f
Some checks failed
CI / ruff (push) Successful in 10s
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has started running
CI / validate (push) Has been cancelled
fix: move storeRoles() to external JS with base layout inheritance
The inline storeRoles() was missing ...data() spread, causing Alpine
errors for dark mode, sidebar, storeCode etc. Follow the same pattern
as team.js: external JS file with ...data() and parent init() call.
Uses apiClient and Utils.showToast per architecture rules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:15:19 +01:00

180 lines
6.6 KiB
JavaScript

/**
* Store Roles Management — Alpine.js component
*
* Provides CRUD for custom roles with a permission matrix UI.
* Loaded on /store/{store_code}/team/roles.
*/
const storeRolesLog = createModuleLogger?.('storeRoles') ?? console;
function storeRoles() {
storeRolesLog.info('storeRoles() called');
return {
// Inherit base layout state
...data(),
// Set page identifier
currentPage: 'team',
// Component state
roles: [],
loading: true,
error: false,
saving: false,
showRoleModal: false,
editingRole: null,
roleForm: { name: '', permissions: [] },
permissionsByCategory: {},
presetRoles: ['manager', 'staff', 'support', 'viewer', 'marketing'],
async init() {
// Guard against multiple initialization
if (window._storeRolesInitialized) {
storeRolesLog.warn('Already initialized, skipping');
return;
}
window._storeRolesInitialized = true;
// Call parent init to set storeCode from URL
const parentInit = data().init;
if (parentInit) {
await parentInit.call(this);
}
await this.loadPermissions();
await this.loadRoles();
},
async loadPermissions() {
try {
// Group known permissions by category prefix
const allPerms = window.USER_PERMISSIONS || [];
this.permissionsByCategory = this.groupPermissions(allPerms);
} catch (e) {
storeRolesLog.warn('Could not load permission categories:', e);
}
},
groupPermissions(permIds) {
// Known permission categories from the codebase
const knownPerms = [
'dashboard.view',
'settings.view', 'settings.edit', 'settings.theme', 'settings.domains',
'products.view', 'products.create', 'products.edit', 'products.delete', 'products.import', 'products.export',
'orders.view', 'orders.edit', 'orders.cancel', 'orders.refund',
'customers.view', 'customers.edit', 'customers.delete', 'customers.export',
'stock.view', 'stock.edit', 'stock.transfer',
'team.view', 'team.invite', 'team.edit', 'team.remove',
'analytics.view', 'analytics.export',
'messaging.view_messages', 'messaging.send_messages', 'messaging.manage_templates',
'billing.view_tiers', 'billing.manage_tiers', 'billing.view_subscriptions', 'billing.manage_subscriptions', 'billing.view_invoices',
'cms.view_pages', 'cms.manage_pages', 'cms.view_media', 'cms.manage_media', 'cms.manage_themes',
'loyalty.view_programs', 'loyalty.manage_programs', 'loyalty.view_rewards', 'loyalty.manage_rewards',
'cart.view', 'cart.manage',
];
const groups = {};
for (const perm of knownPerms) {
const cat = perm.split('.')[0];
if (!groups[cat]) groups[cat] = [];
groups[cat].push({ id: perm });
}
return groups;
},
async loadRoles() {
this.loading = true;
this.error = false;
try {
const response = await apiClient.get('/store/team/roles');
this.roles = response.roles || [];
} catch (e) {
this.error = true;
storeRolesLog.error('Error loading roles:', e);
} finally {
this.loading = false;
}
},
isPresetRole(name) {
return this.presetRoles.includes(name.toLowerCase());
},
openCreateModal() {
this.editingRole = null;
this.roleForm = { name: '', permissions: [] };
this.showRoleModal = true;
},
openEditModal(role) {
this.editingRole = role;
this.roleForm = {
name: role.name,
permissions: [...(role.permissions || [])],
};
this.showRoleModal = true;
},
togglePermission(permId) {
const idx = this.roleForm.permissions.indexOf(permId);
if (idx >= 0) {
this.roleForm.permissions.splice(idx, 1);
} else {
this.roleForm.permissions.push(permId);
}
},
toggleCategory(category) {
const perms = this.permissionsByCategory[category] || [];
const permIds = perms.map(p => p.id);
const allSelected = permIds.every(id => this.roleForm.permissions.includes(id));
if (allSelected) {
this.roleForm.permissions = this.roleForm.permissions.filter(id => !permIds.includes(id));
} else {
for (const id of permIds) {
if (!this.roleForm.permissions.includes(id)) {
this.roleForm.permissions.push(id);
}
}
}
},
isCategoryFullySelected(category) {
const perms = this.permissionsByCategory[category] || [];
return perms.length > 0 && perms.every(p => this.roleForm.permissions.includes(p.id));
},
async saveRole() {
this.saving = true;
try {
if (this.editingRole) {
await apiClient.put(`/store/team/roles/${this.editingRole.id}`, this.roleForm);
} else {
await apiClient.post('/store/team/roles', this.roleForm);
}
this.showRoleModal = false;
Utils.showToast('Role saved successfully', 'success');
await this.loadRoles();
} catch (e) {
storeRolesLog.error('Error saving role:', e);
Utils.showToast(e.message || 'Failed to save role', 'error');
} finally {
this.saving = false;
}
},
async confirmDelete(role) {
if (!confirm(`Delete role "${role.name}"? This cannot be undone.`)) return;
try {
await apiClient.delete(`/store/team/roles/${role.id}`);
Utils.showToast('Role deleted successfully', 'success');
await this.loadRoles();
} catch (e) {
storeRolesLog.error('Error deleting role:', e);
Utils.showToast(e.message || 'Failed to delete role', 'error');
}
},
};
}