feat(sprint18): editor autosave/reset, admin auth, QR reuse, Ed25519 prep
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

- Project editor: persist draft to localStorage, restore on reload,
  last-saved/draft-restored status indicators, section/whole-draft reset
  with confirmation.
- Extract shared QR/polling/expiry engine from TelegramLoginComponent
  (shared/qr-login) and reuse it for a new admin login flow.
- Admin authentication kept fully separate from customer session:
  own cookie/localStorage keys, signals, guard, and header interceptor
  (core/admin-auth).
- ?login=true / ?adminLogin=true open the respective login dialog for
  manual testing.
- Ed25519 challenge/verify interfaces (fail-closed no-op binding) ready
  for backend delivery.
- Document autosave/reset/admin-auth/QR-reuse/Ed25519 model and the
  remaining full-field-coverage gap in docs/Project-Editor.md.
This commit is contained in:
sdarbinyan
2026-07-14 09:50:03 +04:00
parent c6482f0037
commit 3877b70fdf
33 changed files with 1423 additions and 171 deletions

View File

@@ -0,0 +1,51 @@
import { Injectable } from '@angular/core';
import { BootstrapConfig } from '../../../shared/models/config';
const DRAFT_STORAGE_KEY = 'projectEditor.draftBootstrap.v1';
const DRAFT_SAVED_AT_KEY = 'projectEditor.draftSavedAt.v1';
interface StoredDraft {
tenantId: string;
bootstrap: BootstrapConfig;
savedAt: number;
}
@Injectable({ providedIn: 'root' })
export class ProjectEditorDraftStorageService {
save(bootstrap: BootstrapConfig): number {
const savedAt = Date.now();
const payload: StoredDraft = { tenantId: bootstrap.tenant.id, bootstrap, savedAt };
try {
localStorage.setItem(DRAFT_STORAGE_KEY, JSON.stringify(payload));
localStorage.setItem(DRAFT_SAVED_AT_KEY, String(savedAt));
} catch {
// storage unavailable (private mode / quota) - draft simply won't persist
}
return savedAt;
}
load(tenantId: string): { bootstrap: BootstrapConfig; savedAt: number } | null {
try {
const raw = localStorage.getItem(DRAFT_STORAGE_KEY);
if (!raw) {
return null;
}
const parsed = JSON.parse(raw) as StoredDraft;
if (parsed.tenantId !== tenantId) {
return null;
}
return { bootstrap: parsed.bootstrap, savedAt: parsed.savedAt };
} catch {
return null;
}
}
clear(): void {
try {
localStorage.removeItem(DRAFT_STORAGE_KEY);
localStorage.removeItem(DRAFT_SAVED_AT_KEY);
} catch {
// ignore
}
}
}