Some checks failed
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>
180 lines
6.6 KiB
JavaScript
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');
|
|
}
|
|
},
|
|
};
|
|
}
|