refactor: marketplace release polish
Sprint 28, scoped to admin/* (user decision — full marketplace audit declined in favor of a bounded pass over the 8 admin features from Sprints 20-27). - a11y: aria-label added to every bare <select> not already inside a <label> across categories/products/orders/transactions/users/monitoring - loading states: app-skeleton rows/cards added to list pages that previously rendered blank during the initial fetch (categories, orders, transactions, users, monitoring's event feed, analytics summary cards) - admin-dashboard-card's custom shimmer CSS replaced with the shared SkeletonComponent (same visual result, one less duplicated animation) - bundle-size budget warning (~198kB over) confirmed pre-existing — present at Sprint 20's first build before any admin/* code existed, and new admin pages are all lazy-loaded — documented as out of scope for this pass rather than chased docs/ADMIN.md + docs/SPRINT-PLAN.md updated with the scope decision and what was explicitly not done (Lighthouse, animations, SEO/sitemap, storefront/editor a11y). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
<section class="admin-users-page">
|
||||
<div class="card">
|
||||
<h2>{{ 'adminUsers.title' | translate }}</h2>
|
||||
@if (facade.loading()) {
|
||||
<div class="skeleton-rows">
|
||||
@for (i of [1,2,3]; track i) { <app-skeleton shape="rect" height="40px" /> }
|
||||
</div>
|
||||
} @else if (facade.users().length === 0) {
|
||||
<app-empty-state [title]="'adminUsers.emptyTitle' | translate" [description]="'adminUsers.emptyDescription' | translate" />
|
||||
} @else {
|
||||
<app-table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -18,7 +25,7 @@
|
||||
<td>{{ user.name }}<br /><small>{{ user.telegramUsername }}</small></td>
|
||||
<td>{{ ('adminUsers.scopeValue.' + user.scope) | translate }}</td>
|
||||
<td>
|
||||
<select [ngModel]="user.roleId" (ngModelChange)="facade.setRole(user.id, $event)">
|
||||
<select [attr.aria-label]="'adminUsers.role' | translate" [ngModel]="user.roleId" (ngModelChange)="facade.setRole(user.id, $event)">
|
||||
@for (role of facade.roles(); track role.id) {
|
||||
<option [value]="role.id">{{ role.name }}</option>
|
||||
}
|
||||
@@ -35,6 +42,7 @@
|
||||
}
|
||||
</tbody>
|
||||
</app-table>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@@ -42,12 +50,12 @@
|
||||
<p class="hint">{{ 'adminUsers.passwordlessHint' | translate }}</p>
|
||||
<div class="invite-form">
|
||||
<app-input type="email" [ngModel]="inviteEmail()" (ngModelChange)="inviteEmail.set($event)" [placeholder]="'adminUsers.email' | translate" />
|
||||
<select [ngModel]="inviteRoleId()" (ngModelChange)="inviteRoleId.set($event)">
|
||||
<select [attr.aria-label]="'adminUsers.role' | translate" [ngModel]="inviteRoleId()" (ngModelChange)="inviteRoleId.set($event)">
|
||||
@for (role of facade.roles(); track role.id) {
|
||||
<option [value]="role.id">{{ role.name }}</option>
|
||||
}
|
||||
</select>
|
||||
<select [ngModel]="inviteScope()" (ngModelChange)="inviteScope.set($event)">
|
||||
<select [attr.aria-label]="'adminUsers.scope' | translate" [ngModel]="inviteScope()" (ngModelChange)="inviteScope.set($event)">
|
||||
<option value="marketplace">{{ 'adminUsers.scopeValue.marketplace' | translate }}</option>
|
||||
<option value="office">{{ 'adminUsers.scopeValue.office' | translate }}</option>
|
||||
</select>
|
||||
@@ -79,6 +87,8 @@
|
||||
}
|
||||
</tbody>
|
||||
</app-table>
|
||||
} @else {
|
||||
<app-empty-state [title]="'adminUsers.invitationsEmptyTitle' | translate" />
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user