From 1916153e5b1d5d506a34e5c1ecd9980d8cc450e5 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Fri, 17 Jul 2026 22:11:53 +0400 Subject: [PATCH] fix(admin-categories): key create-draft localStorage recovery on a stable id, not the ephemeral generated one Bug: startCreate() generated a fresh id via category-${Date.now()} every call and wrote/read the autosave draft under admin-category-draft:. Since the id changes every time startCreate() runs, a draft saved during one "create category" visit can never be found by a later visit (even seconds later, same tab) - draft recovery for new (unsaved) categories was completely dead, and every abandoned attempt left an orphaned, never-cleaned localStorage entry. Fix: create-mode drafts now persist under a fixed key (admin-category-draft:new) tracked via a new draftStorageKey field, independent of the draft's own id. Edit-mode drafts are unaffected - they already keyed on the real, stable category id. Verified live via window.ng.getComponent() on /ru/backoffice/categories/create (devBypassAdmin=true): - Before fix: updateDraft({title}) -> localStorage key admin-category-draft:category-; calling startCreate() again (simulating navigate-away/back) generated category- and never recovered - draft.title reset to '', dirty=false, old key orphaned. - After fix: same sequence recovers title/dirty correctly under admin-category-draft:new; saveDraft() clears that key as expected. --- .../facade/admin-categories.facade.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/app/features/admin/categories/facade/admin-categories.facade.ts b/src/app/features/admin/categories/facade/admin-categories.facade.ts index 3fa222c..61e6c42 100644 --- a/src/app/features/admin/categories/facade/admin-categories.facade.ts +++ b/src/app/features/admin/categories/facade/admin-categories.facade.ts @@ -6,6 +6,7 @@ import { AdminCategoriesLocalGateway } from '../services/admin-categories-local. import { LocalStorageService } from '../../../../core/storage/local-storage.service'; const DRAFT_KEY_PREFIX = 'admin-category-draft:'; +const NEW_CATEGORY_DRAFT_KEY = `${DRAFT_KEY_PREFIX}new`; @Injectable({ providedIn: 'root' }) export class AdminCategoriesFacade { @@ -21,6 +22,7 @@ export class AdminCategoriesFacade { readonly dirty = signal(false); readonly slugTaken = signal(false); private savedSnapshot: string | null = null; + private draftStorageKey: string | null = null; readonly rootCategories = computed(() => this.categories().filter(category => !category.parentId)); readonly childrenByParent = computed(() => { @@ -69,22 +71,23 @@ export class AdminCategoriesFacade { startCreate(): void { this.editorMode.set('create'); - const empty = this.formFactory.createEmpty(); - const recovered = this.localStorage.getJSON(`${DRAFT_KEY_PREFIX}${empty.id}`); - this.draft.set(recovered ?? empty); + this.draftStorageKey = NEW_CATEGORY_DRAFT_KEY; + const recovered = this.localStorage.getJSON(this.draftStorageKey); + this.draft.set(recovered ?? this.formFactory.createEmpty()); this.savedSnapshot = null; this.dirty.set(!!recovered); } loadForEdit(id: string): void { this.editorMode.set('edit'); + this.draftStorageKey = `${DRAFT_KEY_PREFIX}${id}`; this.gateway.loadCategory(id).pipe(take(1)).subscribe({ next: category => { if (!category) { this.draft.set(null); return; } - const recovered = this.localStorage.getJSON(`${DRAFT_KEY_PREFIX}${id}`); + const recovered = this.localStorage.getJSON(this.draftStorageKey!); this.draft.set(recovered ?? { ...category }); this.savedSnapshot = JSON.stringify(category); this.dirty.set(!!recovered && JSON.stringify(recovered) !== this.savedSnapshot); @@ -96,7 +99,9 @@ export class AdminCategoriesFacade { this.draft.update(current => { if (!current) return current; const updated = { ...current, ...patch, updatedAt: new Date().toISOString() }; - this.localStorage.setJSON(`${DRAFT_KEY_PREFIX}${updated.id}`, updated); + if (this.draftStorageKey) { + this.localStorage.setJSON(this.draftStorageKey, updated); + } this.dirty.set(this.savedSnapshot !== JSON.stringify(updated)); return updated; }); @@ -124,7 +129,10 @@ export class AdminCategoriesFacade { request.pipe(take(1)).subscribe({ next: saved => { - this.localStorage.removeItem(`${DRAFT_KEY_PREFIX}${saved.id}`); + if (this.draftStorageKey) { + this.localStorage.removeItem(this.draftStorageKey); + } + this.draftStorageKey = `${DRAFT_KEY_PREFIX}${saved.id}`; this.savedSnapshot = JSON.stringify(saved); this.dirty.set(false); this.loadList(); @@ -133,9 +141,8 @@ export class AdminCategoriesFacade { } discardDraftRecovery(): void { - const draft = this.draft(); - if (!draft) return; - this.localStorage.removeItem(`${DRAFT_KEY_PREFIX}${draft.id}`); + if (!this.draft() || !this.draftStorageKey) return; + this.localStorage.removeItem(this.draftStorageKey); } canDelete(id: string): boolean {