feat(sprint18): editor autosave/reset, admin auth, QR reuse, Ed25519 prep
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
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:
@@ -1,8 +1,16 @@
|
||||
<div class="project-editor-save-bar">
|
||||
@if (draftRestored()) {
|
||||
<div class="project-editor-save-bar-notice">
|
||||
<span>{{ 'builder.draftRestored' | translate }}</span>
|
||||
<button type="button" (click)="dismissDraftRestoredNotice()">{{ 'builder.dismiss' | translate }}</button>
|
||||
</div>
|
||||
}
|
||||
<div class="project-editor-save-bar-status">
|
||||
<span>{{ (status() === 'published' ? 'builder.statusPublished' : 'builder.statusDraft') | translate }}</span>
|
||||
@if (dirty()) {
|
||||
<span class="project-editor-save-bar-dirty">{{ 'builder.unsavedChanges' | translate }}</span>
|
||||
} @else if (lastSavedAt()) {
|
||||
<span class="project-editor-save-bar-saved-at">{{ 'builder.lastSaved' | translate }}: {{ formatSavedAt(lastSavedAt()!) }}</span>
|
||||
}
|
||||
@if (issues().length > 0) {
|
||||
<ul class="project-editor-save-bar-issues">
|
||||
@@ -13,6 +21,7 @@
|
||||
}
|
||||
</div>
|
||||
<div class="project-editor-save-bar-actions">
|
||||
<button type="button" class="project-editor-save-bar-reset" (click)="resetDraft()">{{ 'builder.resetDraft' | translate }}</button>
|
||||
<button type="button" (click)="save()">{{ 'builder.save' | translate }}</button>
|
||||
<button type="button" [disabled]="issues().length > 0" (click)="publish()">{{ 'builder.publish' | translate }}</button>
|
||||
</div>
|
||||
|
||||
@@ -25,3 +25,27 @@
|
||||
.project-editor-save-bar-actions button + button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.project-editor-save-bar-saved-at {
|
||||
color: var(--muted-foreground, #6b7280);
|
||||
margin-left: 0.5rem;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.project-editor-save-bar-notice {
|
||||
position: absolute;
|
||||
top: -2.5rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--info-bg, #eff6ff);
|
||||
color: var(--info, #1d4ed8);
|
||||
border-bottom: 1px solid var(--border, #ddd);
|
||||
}
|
||||
|
||||
.project-editor-save-bar-reset {
|
||||
color: var(--danger, #b91c1c);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
||||
import { TranslateService } from '../../../../i18n/translate.service';
|
||||
import { ProjectEditorFacade } from '../../facade/project-editor.facade';
|
||||
|
||||
@Component({
|
||||
@@ -12,9 +13,12 @@ import { ProjectEditorFacade } from '../../facade/project-editor.facade';
|
||||
})
|
||||
export class ProjectEditorSaveBarComponent {
|
||||
private readonly facade = inject(ProjectEditorFacade);
|
||||
private readonly translate = inject(TranslateService);
|
||||
readonly dirty = this.facade.dirty;
|
||||
readonly status = this.facade.status;
|
||||
readonly issues = this.facade.validationIssues;
|
||||
readonly lastSavedAt = this.facade.lastSavedAt;
|
||||
readonly draftRestored = this.facade.draftRestored;
|
||||
|
||||
save(): void {
|
||||
this.facade.save();
|
||||
@@ -23,4 +27,18 @@ export class ProjectEditorSaveBarComponent {
|
||||
publish(): void {
|
||||
this.facade.publish();
|
||||
}
|
||||
|
||||
resetDraft(): void {
|
||||
if (confirm(this.translate.t('builder.confirmResetDraft'))) {
|
||||
this.facade.resetDraft();
|
||||
}
|
||||
}
|
||||
|
||||
formatSavedAt(timestamp: number): string {
|
||||
return new Date(timestamp).toLocaleTimeString();
|
||||
}
|
||||
|
||||
dismissDraftRestoredNotice(): void {
|
||||
this.facade.dismissDraftRestoredNotice();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user