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>
49 lines
2.2 KiB
TypeScript
49 lines
2.2 KiB
TypeScript
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { AdminUsersFacade } from '../facade/admin-users.facade';
|
|
import { AdminUserScope, AdminUserStatus } from '../models/admin-user.model';
|
|
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
|
import { TranslateService } from '../../../../i18n/translate.service';
|
|
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
|
|
import { InputComponent } from '../../../../shared/ui/input/input.component';
|
|
import { BadgeComponent } from '../../../../shared/ui/badge/badge.component';
|
|
import { TableComponent } from '../../../../shared/ui/table/table.component';
|
|
import { DialogComponent } from '../../../../shared/ui/dialog/dialog.component';
|
|
import { SkeletonComponent } from '../../../../shared/ui/skeleton/skeleton.component';
|
|
import { EmptyStateComponent } from '../../../../shared/ui/empty-state/empty-state.component';
|
|
|
|
@Component({
|
|
selector: 'app-admin-users-page',
|
|
standalone: true,
|
|
imports: [CommonModule, FormsModule, TranslatePipe, ButtonComponent, InputComponent, BadgeComponent, TableComponent, DialogComponent, SkeletonComponent, EmptyStateComponent],
|
|
templateUrl: './admin-users-page.component.html',
|
|
styleUrls: ['./admin-users-page.component.scss'],
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
})
|
|
export class AdminUsersPageComponent {
|
|
readonly facade = inject(AdminUsersFacade);
|
|
private readonly translate = inject(TranslateService);
|
|
|
|
readonly inviteEmail = signal('');
|
|
readonly inviteRoleId = signal('viewer');
|
|
readonly inviteScope = signal<AdminUserScope>('office');
|
|
|
|
constructor() {
|
|
this.facade.loadAll();
|
|
}
|
|
|
|
sendInvite(): void {
|
|
this.facade.invite(this.inviteEmail(), this.inviteRoleId(), this.inviteScope());
|
|
this.inviteEmail.set('');
|
|
}
|
|
|
|
toggleStatus(userId: string, current: AdminUserStatus): void {
|
|
const next: AdminUserStatus = current === 'suspended' ? 'active' : 'suspended';
|
|
if (next === 'suspended' && !window.confirm(this.translate.t('adminUsers.confirmSuspend'))) {
|
|
return;
|
|
}
|
|
this.facade.setStatus(userId, next);
|
|
}
|
|
}
|