feat(admin): users and permissions
Sprint 25.
New features/admin/users/ module, net-new /:lang/backoffice/users route +
Dashboard Quick Action.
- users: name, Telegram username, scope (marketplace vs office admin),
role (inline change), status (active/invited/suspended), last login
- 4 built-in roles (owner/admin/editor/viewer) with flat permission lists
- invitations: email + role + scope form, pending list + revoke (no email
actually sends - local record only)
- passwordless login confirmed already real (AdminAuthService Telegram QR,
docs/BACKEND.md item 1) - linked, not reimplemented
- per-user mock session list (device/IP/last-active, revoke) - flagged as
mock since the real AdminAuthService only ever tracks the current
browser's session
- per-user audit log dialog (role/status changes), same pattern as
Sprint 24's per-transaction audit, intentionally separate from the
system-wide log planned for Sprint 26
docs/ADMIN.md + docs/BACKEND.md (new item 14) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:05:21 +04:00
|
|
|
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';
|
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>
2026-07-16 00:14:46 +04:00
|
|
|
import { SkeletonComponent } from '../../../../shared/ui/skeleton/skeleton.component';
|
|
|
|
|
import { EmptyStateComponent } from '../../../../shared/ui/empty-state/empty-state.component';
|
feat(admin): users and permissions
Sprint 25.
New features/admin/users/ module, net-new /:lang/backoffice/users route +
Dashboard Quick Action.
- users: name, Telegram username, scope (marketplace vs office admin),
role (inline change), status (active/invited/suspended), last login
- 4 built-in roles (owner/admin/editor/viewer) with flat permission lists
- invitations: email + role + scope form, pending list + revoke (no email
actually sends - local record only)
- passwordless login confirmed already real (AdminAuthService Telegram QR,
docs/BACKEND.md item 1) - linked, not reimplemented
- per-user mock session list (device/IP/last-active, revoke) - flagged as
mock since the real AdminAuthService only ever tracks the current
browser's session
- per-user audit log dialog (role/status changes), same pattern as
Sprint 24's per-transaction audit, intentionally separate from the
system-wide log planned for Sprint 26
docs/ADMIN.md + docs/BACKEND.md (new item 14) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:05:21 +04:00
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'app-admin-users-page',
|
|
|
|
|
standalone: true,
|
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>
2026-07-16 00:14:46 +04:00
|
|
|
imports: [CommonModule, FormsModule, TranslatePipe, ButtonComponent, InputComponent, BadgeComponent, TableComponent, DialogComponent, SkeletonComponent, EmptyStateComponent],
|
feat(admin): users and permissions
Sprint 25.
New features/admin/users/ module, net-new /:lang/backoffice/users route +
Dashboard Quick Action.
- users: name, Telegram username, scope (marketplace vs office admin),
role (inline change), status (active/invited/suspended), last login
- 4 built-in roles (owner/admin/editor/viewer) with flat permission lists
- invitations: email + role + scope form, pending list + revoke (no email
actually sends - local record only)
- passwordless login confirmed already real (AdminAuthService Telegram QR,
docs/BACKEND.md item 1) - linked, not reimplemented
- per-user mock session list (device/IP/last-active, revoke) - flagged as
mock since the real AdminAuthService only ever tracks the current
browser's session
- per-user audit log dialog (role/status changes), same pattern as
Sprint 24's per-transaction audit, intentionally separate from the
system-wide log planned for Sprint 26
docs/ADMIN.md + docs/BACKEND.md (new item 14) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:05:21 +04:00
|
|
|
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);
|
|
|
|
|
}
|
fix(backoffice): wording quality pass across orders, moderation, transactions, users, customers
- Replaced hardcoded English audit/timeline text (order status changes,
review moderation events, user role/status changes) with proper
adminXxx.timelineEvent.*/adminUsers.audit.* i18n keys, so Recent
Activity/Timeline/Audit panels no longer mix English into ru/hy UI.
- Translated raw internal codes rendered directly to users: transaction
payment method ('card'/'qr'/'cash_on_delivery' -> adminTransactions.methodValue.*)
and user roles/permissions ('products.manage' etc -> adminUsers.roleValue.*/
adminUsers.permission.*), replacing developer-facing enum leakage with
real copy.
- Fixed wrong-noun list-footer counts: Orders/Transactions/Moderation
list pages all reused adminProducts.items ("N товаров"/"N products")
regardless of what was actually listed; each now has its own itemsCount
key ("N заказов", "N транзакций", "N отзывов").
- Fixed customer detail page's "Back" button reusing adminOrders.back
("Back to orders") instead of a customers-specific label.
- Added translation keys to en/ru/hy + translations.ts interface for all
of the above.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 21:20:37 +04:00
|
|
|
|
|
|
|
|
private static readonly PERMISSION_KEYS: Record<string, string> = {
|
|
|
|
|
'*': 'all',
|
|
|
|
|
'products.manage': 'productsManage',
|
|
|
|
|
'products.view': 'productsView',
|
|
|
|
|
'categories.manage': 'categoriesManage',
|
|
|
|
|
'orders.manage': 'ordersManage',
|
|
|
|
|
'orders.view': 'ordersView',
|
|
|
|
|
'media.manage': 'mediaManage',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
readonly roleLabel = (roleId: string): string => this.translate.t('adminUsers.roleValue.' + roleId);
|
|
|
|
|
|
|
|
|
|
readonly permissionLabel = (code: string): string => {
|
|
|
|
|
const key = AdminUsersPageComponent.PERMISSION_KEYS[code];
|
|
|
|
|
return key ? this.translate.t('adminUsers.permission.' + key) : code;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auditText(entry: { eventKey: 'roleChanged' | 'statusChanged'; roleId?: string; status?: AdminUserStatus }): string {
|
|
|
|
|
if (entry.eventKey === 'roleChanged' && entry.roleId) {
|
|
|
|
|
return this.translate.t('adminUsers.audit.roleChanged', { role: this.roleLabel(entry.roleId) });
|
|
|
|
|
}
|
|
|
|
|
if (entry.eventKey === 'statusChanged' && entry.status) {
|
|
|
|
|
return this.translate.t('adminUsers.audit.statusChanged', { status: this.translate.t('adminUsers.statusValue.' + entry.status) });
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
}
|
feat(admin): users and permissions
Sprint 25.
New features/admin/users/ module, net-new /:lang/backoffice/users route +
Dashboard Quick Action.
- users: name, Telegram username, scope (marketplace vs office admin),
role (inline change), status (active/invited/suspended), last login
- 4 built-in roles (owner/admin/editor/viewer) with flat permission lists
- invitations: email + role + scope form, pending list + revoke (no email
actually sends - local record only)
- passwordless login confirmed already real (AdminAuthService Telegram QR,
docs/BACKEND.md item 1) - linked, not reimplemented
- per-user mock session list (device/IP/last-active, revoke) - flagged as
mock since the real AdminAuthService only ever tracks the current
browser's session
- per-user audit log dialog (role/status changes), same pattern as
Sprint 24's per-transaction audit, intentionally separate from the
system-wide log planned for Sprint 26
docs/ADMIN.md + docs/BACKEND.md (new item 14) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:05:21 +04:00
|
|
|
}
|