refactor: simplify form draft key generation
This commit is contained in:
@@ -5,7 +5,7 @@ export const AppStorageKey = {
|
|||||||
THEME: 'app_theme',
|
THEME: 'app_theme',
|
||||||
ACCESS_TOKEN: 'access_token',
|
ACCESS_TOKEN: 'access_token',
|
||||||
REFRESH_TOKEN: 'refresh_token',
|
REFRESH_TOKEN: 'refresh_token',
|
||||||
USER_ID: 'u_id',
|
USER_ID: 'uid',
|
||||||
SIDEBAR_OPEN_MENUS: 'sidebar_open_menus',
|
SIDEBAR_OPEN_MENUS: 'sidebar_open_menus',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -21,18 +21,43 @@ export const AppDatabaseKey = {
|
|||||||
|
|
||||||
export type AppDatabaseKeyValue = (typeof AppDatabaseKey)[keyof typeof AppDatabaseKey];
|
export type AppDatabaseKeyValue = (typeof AppDatabaseKey)[keyof typeof AppDatabaseKey];
|
||||||
|
|
||||||
|
function getUserId(userIdKey: string) {
|
||||||
|
const rawId = localStorage.getItem(userIdKey);
|
||||||
|
if (!rawId) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(rawId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse User ID:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const APP_STORAGE_ENCRYPTED_KEYS = new Set<AppStorageKeyValue>([
|
export const APP_STORAGE_ENCRYPTED_KEYS = new Set<AppStorageKeyValue>([
|
||||||
AppStorageKey.USER_ID,
|
|
||||||
AppStorageKey.ACCESS_TOKEN,
|
AppStorageKey.ACCESS_TOKEN,
|
||||||
AppStorageKey.REFRESH_TOKEN,
|
AppStorageKey.REFRESH_TOKEN,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const APP_STORAGE_PLAIN_KEYS = new Set<AppStorageKeyValue>([
|
export const APP_STORAGE_PLAIN_KEYS = new Set<AppStorageKeyValue>([
|
||||||
|
AppStorageKey.USER_ID,
|
||||||
AppStorageKey.LANGUAGE,
|
AppStorageKey.LANGUAGE,
|
||||||
AppStorageKey.THEME,
|
AppStorageKey.THEME,
|
||||||
AppStorageKey.SIDEBAR_OPEN_MENUS,
|
AppStorageKey.SIDEBAR_OPEN_MENUS,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const APP_STORAGE_PERSONALIZED_KEYS = new Set<AppStorageKeyValue>([
|
||||||
|
AppStorageKey.LANGUAGE,
|
||||||
|
AppStorageKey.THEME,
|
||||||
|
AppStorageKey.SIDEBAR_OPEN_MENUS,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const appStorage = createLocalStorage<AppStorageKeyValue>({
|
||||||
|
encryptedKeys: APP_STORAGE_ENCRYPTED_KEYS,
|
||||||
|
plainTextKeys: APP_STORAGE_PLAIN_KEYS,
|
||||||
|
personalizedKeys: APP_STORAGE_PERSONALIZED_KEYS,
|
||||||
|
getUserId: () => getUserId(AppStorageKey.USER_ID) || null,
|
||||||
|
});
|
||||||
|
|
||||||
export const APP_DATABASE_ENCRYPTED_KEYS = new Set<AppDatabaseKeyValue>([]);
|
export const APP_DATABASE_ENCRYPTED_KEYS = new Set<AppDatabaseKeyValue>([]);
|
||||||
|
|
||||||
export const APP_DATABASE_PLAIN_KEYS = new Set<AppDatabaseKeyValue>([
|
export const APP_DATABASE_PLAIN_KEYS = new Set<AppDatabaseKeyValue>([
|
||||||
@@ -43,14 +68,19 @@ export const APP_DATABASE_PLAIN_KEYS = new Set<AppDatabaseKeyValue>([
|
|||||||
AppDatabaseKey.BOOKMARK_PAGE,
|
AppDatabaseKey.BOOKMARK_PAGE,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const appStorage = createLocalStorage<AppStorageKeyValue>({
|
export const APP_DATABASE_PERSONALIZED_KEYS = new Set<AppDatabaseKeyValue>([
|
||||||
encryptedKeys: APP_STORAGE_ENCRYPTED_KEYS,
|
AppDatabaseKey.USER_PROFILE,
|
||||||
plainTextKeys: APP_STORAGE_PLAIN_KEYS,
|
AppDatabaseKey.OFFLINE_DRAFT,
|
||||||
});
|
AppDatabaseKey.SYSTEM_SETTINGS,
|
||||||
|
AppDatabaseKey.HISTORY_PAGE,
|
||||||
|
AppDatabaseKey.BOOKMARK_PAGE,
|
||||||
|
]);
|
||||||
|
|
||||||
export const appDatabase = createIndexedDB<AppDatabaseKeyValue>({
|
export const appDatabase = createIndexedDB<AppDatabaseKeyValue>({
|
||||||
dbName: 'e_apps_db',
|
dbName: 'e_apps_db',
|
||||||
storeName: 'web_store',
|
storeName: 'web_store',
|
||||||
encryptedKeys: APP_DATABASE_ENCRYPTED_KEYS,
|
encryptedKeys: APP_DATABASE_ENCRYPTED_KEYS,
|
||||||
plainTextKeys: APP_DATABASE_PLAIN_KEYS,
|
plainTextKeys: APP_DATABASE_PLAIN_KEYS,
|
||||||
|
personalizedKeys: APP_DATABASE_PERSONALIZED_KEYS,
|
||||||
|
getUserId: () => getUserId(AppStorageKey.USER_ID) || null,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function openDatabase(dbName: string, storeName: string, version: number): Promi
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a single IndexedDB transaction and return the result.
|
* Execute a single IndexedDB transaction and return the result.
|
||||||
* Handles open → transaction → request → close lifecycle cleanly.
|
* Resolves only when the entire transaction is completed to prevent silent failures.
|
||||||
*/
|
*/
|
||||||
function withTransaction<R>(
|
function withTransaction<R>(
|
||||||
db: IDBDatabase,
|
db: IDBDatabase,
|
||||||
@@ -48,10 +48,20 @@ function withTransaction<R>(
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tx = db.transaction(storeName, mode);
|
const tx = db.transaction(storeName, mode);
|
||||||
const store = tx.objectStore(storeName);
|
const store = tx.objectStore(storeName);
|
||||||
|
let requestResult: R;
|
||||||
|
|
||||||
const request = operation(store);
|
const request = operation(store);
|
||||||
|
|
||||||
request.onsuccess = () => resolve(request.result);
|
request.onsuccess = () => {
|
||||||
|
requestResult = request.result;
|
||||||
|
};
|
||||||
|
|
||||||
request.onerror = () => reject(request.error);
|
request.onerror = () => reject(request.error);
|
||||||
|
|
||||||
|
// Resolve promise ONLY after transaction is successfully committed
|
||||||
|
tx.oncomplete = () => resolve(requestResult);
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
tx.onabort = () => reject(tx.error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +77,8 @@ export class IndexedDBService<TKey extends string> implements IStorageService<TK
|
|||||||
private readonly version: number;
|
private readonly version: number;
|
||||||
private readonly encryptedKeys: Set<TKey>;
|
private readonly encryptedKeys: Set<TKey>;
|
||||||
private readonly plainTextKeys: Set<TKey>;
|
private readonly plainTextKeys: Set<TKey>;
|
||||||
|
private readonly personalizedKeys?: Set<TKey>;
|
||||||
|
private readonly getUserId?: () => string | null | undefined;
|
||||||
private dbPromise: Promise<IDBDatabase> | null = null;
|
private dbPromise: Promise<IDBDatabase> | null = null;
|
||||||
|
|
||||||
constructor(config?: IndexedDBConfig<TKey>, encryptionUtils?: EncryptionUtils) {
|
constructor(config?: IndexedDBConfig<TKey>, encryptionUtils?: EncryptionUtils) {
|
||||||
@@ -76,6 +88,8 @@ export class IndexedDBService<TKey extends string> implements IStorageService<TK
|
|||||||
this.version = config?.version ?? 1;
|
this.version = config?.version ?? 1;
|
||||||
this.encryptedKeys = config?.encryptedKeys ?? new Set();
|
this.encryptedKeys = config?.encryptedKeys ?? new Set();
|
||||||
this.plainTextKeys = config?.plainTextKeys ?? new Set();
|
this.plainTextKeys = config?.plainTextKeys ?? new Set();
|
||||||
|
this.personalizedKeys = config?.personalizedKeys;
|
||||||
|
this.getUserId = config?.getUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Lazy-open the database connection (cached). */
|
/** Lazy-open the database connection (cached). */
|
||||||
@@ -96,37 +110,46 @@ export class IndexedDBService<TKey extends string> implements IStorageService<TK
|
|||||||
return this.encryptedKeys.has(key);
|
return this.encryptedKeys.has(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resolveKey(key: TKey): string {
|
||||||
|
if (this.personalizedKeys?.has(key)) {
|
||||||
|
const userId = this.getUserId?.() || 'guest';
|
||||||
|
return `${String(key)}_${userId}`;
|
||||||
|
}
|
||||||
|
return String(key);
|
||||||
|
}
|
||||||
|
|
||||||
async setItem<T>(key: TKey, value: T): Promise<void> {
|
async setItem<T>(key: TKey, value: T): Promise<void> {
|
||||||
this.validateKey(key);
|
this.validateKey(key);
|
||||||
const db = await this.getDB();
|
const db = await this.getDB();
|
||||||
|
const resolvedKey = this.resolveKey(key);
|
||||||
|
|
||||||
const serialized = JSON.stringify(value);
|
const serialized = JSON.stringify(value);
|
||||||
const payload = this.shouldEncrypt(key) ? this.encryption.encrypt(serialized) : serialized;
|
const payload = this.shouldEncrypt(key) ? this.encryption.encrypt(serialized) : serialized;
|
||||||
|
|
||||||
await withTransaction(db, this.storeName, 'readwrite', (store) => store.put(payload, key as string));
|
await withTransaction(db, this.storeName, 'readwrite', (store) => store.put(payload, resolvedKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getItem<T>(key: TKey): Promise<T | null> {
|
async getItem<T>(key: TKey): Promise<T | null> {
|
||||||
this.validateKey(key);
|
this.validateKey(key);
|
||||||
const db = await this.getDB();
|
const db = await this.getDB();
|
||||||
|
const resolvedKey = this.resolveKey(key);
|
||||||
|
|
||||||
const raw = await withTransaction<string | undefined>(
|
const raw = await withTransaction<string | undefined>(
|
||||||
db,
|
db,
|
||||||
this.storeName,
|
this.storeName,
|
||||||
'readonly',
|
'readonly',
|
||||||
(store) => store.get(key as string) as IDBRequest<string | undefined>,
|
(store) => store.get(resolvedKey) as IDBRequest<string | undefined>,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (raw === undefined || raw === null) return null;
|
if (raw === undefined || raw === null) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.shouldEncrypt(key)) {
|
const decrypted = this.shouldEncrypt(key) ? this.encryption.decrypt(raw) : raw;
|
||||||
const decrypted = this.encryption.decrypt(raw);
|
|
||||||
if (!decrypted) return null;
|
if (!decrypted) return null;
|
||||||
|
|
||||||
return JSON.parse(decrypted) as T;
|
return JSON.parse(decrypted) as T;
|
||||||
}
|
|
||||||
return JSON.parse(raw) as T;
|
|
||||||
} catch {
|
} catch {
|
||||||
console.warn(`[core-storage] Failed to parse IndexedDB key "${key}". Removing corrupt entry.`);
|
console.warn(`[core-storage] Failed to parse IndexedDB key "${String(key)}". Removing corrupt entry.`);
|
||||||
await this.removeItem(key);
|
await this.removeItem(key);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -135,12 +158,16 @@ export class IndexedDBService<TKey extends string> implements IStorageService<TK
|
|||||||
async removeItem(key: TKey): Promise<void> {
|
async removeItem(key: TKey): Promise<void> {
|
||||||
this.validateKey(key);
|
this.validateKey(key);
|
||||||
const db = await this.getDB();
|
const db = await this.getDB();
|
||||||
await withTransaction(db, this.storeName, 'readwrite', (store) => store.delete(key as string));
|
const resolvedKey = this.resolveKey(key);
|
||||||
|
|
||||||
|
await withTransaction(db, this.storeName, 'readwrite', (store) => store.delete(resolvedKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
async clear(): Promise<void> {
|
async clear(): Promise<void> {
|
||||||
const db = await this.getDB();
|
const allValidKeys = await this.keys();
|
||||||
await withTransaction(db, this.storeName, 'readwrite', (store) => store.clear());
|
for (const key of allValidKeys) {
|
||||||
|
await this.removeItem(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasItem(key: TKey): Promise<boolean> {
|
async hasItem(key: TKey): Promise<boolean> {
|
||||||
@@ -150,13 +177,34 @@ export class IndexedDBService<TKey extends string> implements IStorageService<TK
|
|||||||
|
|
||||||
async keys(): Promise<TKey[]> {
|
async keys(): Promise<TKey[]> {
|
||||||
const db = await this.getDB();
|
const db = await this.getDB();
|
||||||
const allKeys = await withTransaction<string[]>(
|
const rawKeys = await withTransaction<string[]>(
|
||||||
db,
|
db,
|
||||||
this.storeName,
|
this.storeName,
|
||||||
'readonly',
|
'readonly',
|
||||||
(store) => store.getAllKeys() as IDBRequest<string[]>,
|
(store) => store.getAllKeys() as IDBRequest<string[]>,
|
||||||
);
|
);
|
||||||
return allKeys as TKey[];
|
|
||||||
|
const resultSet = new Set<TKey>();
|
||||||
|
const currentUserId = this.getUserId?.() || 'guest';
|
||||||
|
|
||||||
|
for (const rawKey of rawKeys) {
|
||||||
|
const isGlobalKey = this.encryptedKeys.has(rawKey as TKey) || this.plainTextKeys.has(rawKey as TKey);
|
||||||
|
|
||||||
|
if (isGlobalKey && !this.personalizedKeys?.has(rawKey as TKey)) {
|
||||||
|
resultSet.add(rawKey as TKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.personalizedKeys) {
|
||||||
|
for (const pKey of this.personalizedKeys) {
|
||||||
|
if (rawKey === `${String(pKey)}_${currentUserId}`) {
|
||||||
|
resultSet.add(pKey);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(resultSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import type { IStorageService } from '../types/storage.interface';
|
|||||||
export interface StorageOptions<TKey extends string> {
|
export interface StorageOptions<TKey extends string> {
|
||||||
encryptedKeys?: Set<TKey>;
|
encryptedKeys?: Set<TKey>;
|
||||||
plainTextKeys?: Set<TKey>;
|
plainTextKeys?: Set<TKey>;
|
||||||
|
personalizedKeys?: Set<TKey>;
|
||||||
|
getUserId?: () => string | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,11 +15,15 @@ export class LocalStorageService<TKey extends string> implements IStorageService
|
|||||||
private readonly encryption: EncryptionUtils;
|
private readonly encryption: EncryptionUtils;
|
||||||
private readonly encryptedKeys: Set<TKey>;
|
private readonly encryptedKeys: Set<TKey>;
|
||||||
private readonly plainTextKeys: Set<TKey>;
|
private readonly plainTextKeys: Set<TKey>;
|
||||||
|
private readonly personalizedKeys?: Set<TKey>;
|
||||||
|
private readonly getUserId?: () => string | null | undefined;
|
||||||
|
|
||||||
constructor(options?: StorageOptions<TKey>, encryptionUtils?: EncryptionUtils) {
|
constructor(options?: StorageOptions<TKey>, encryptionUtils?: EncryptionUtils) {
|
||||||
this.encryption = encryptionUtils ?? EncryptionUtils.getInstance();
|
this.encryption = encryptionUtils ?? EncryptionUtils.getInstance();
|
||||||
this.encryptedKeys = options?.encryptedKeys ?? new Set();
|
this.encryptedKeys = options?.encryptedKeys ?? new Set();
|
||||||
this.plainTextKeys = options?.plainTextKeys ?? new Set();
|
this.plainTextKeys = options?.plainTextKeys ?? new Set();
|
||||||
|
this.personalizedKeys = options?.personalizedKeys;
|
||||||
|
this.getUserId = options?.getUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private validateKey(key: TKey): void {
|
private validateKey(key: TKey): void {
|
||||||
@@ -26,25 +32,35 @@ export class LocalStorageService<TKey extends string> implements IStorageService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resolveKey(key: TKey): string {
|
||||||
|
if (this.personalizedKeys?.has(key)) {
|
||||||
|
const userId = this.getUserId?.() || 'guest';
|
||||||
|
return `${String(key)}_${userId}`;
|
||||||
|
}
|
||||||
|
return String(key);
|
||||||
|
}
|
||||||
|
|
||||||
private shouldEncrypt(key: TKey): boolean {
|
private shouldEncrypt(key: TKey): boolean {
|
||||||
return this.encryptedKeys.has(key);
|
return this.encryptedKeys.has(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setItem<T>(key: TKey, value: T): Promise<void> {
|
async setItem<T>(key: TKey, value: T): Promise<void> {
|
||||||
this.validateKey(key);
|
this.validateKey(key);
|
||||||
|
const resolvedKey = this.resolveKey(key);
|
||||||
const serialized = JSON.stringify(value);
|
const serialized = JSON.stringify(value);
|
||||||
|
|
||||||
if (this.shouldEncrypt(key)) {
|
if (this.shouldEncrypt(key)) {
|
||||||
const encrypted = this.encryption.encrypt(serialized);
|
const encrypted = this.encryption.encrypt(serialized);
|
||||||
localStorage.setItem(key as string, encrypted);
|
localStorage.setItem(resolvedKey, encrypted);
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem(key as string, serialized);
|
localStorage.setItem(resolvedKey, serialized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getItem<T>(key: TKey): Promise<T | null> {
|
async getItem<T>(key: TKey): Promise<T | null> {
|
||||||
this.validateKey(key);
|
this.validateKey(key);
|
||||||
const raw = localStorage.getItem(key as string);
|
const resolvedKey = this.resolveKey(key);
|
||||||
|
const raw = localStorage.getItem(resolvedKey);
|
||||||
if (raw === null) return null;
|
if (raw === null) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -55,15 +71,16 @@ export class LocalStorageService<TKey extends string> implements IStorageService
|
|||||||
}
|
}
|
||||||
return JSON.parse(raw) as T;
|
return JSON.parse(raw) as T;
|
||||||
} catch {
|
} catch {
|
||||||
console.warn(`[core-storage] Failed to parse key "${key}". Removing corrupt entry.`);
|
console.warn(`[core-storage] Failed to parse key "${String(key)}". Removing corrupt entry.`);
|
||||||
localStorage.removeItem(key as string);
|
localStorage.removeItem(resolvedKey);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeItem(key: TKey): Promise<void> {
|
async removeItem(key: TKey): Promise<void> {
|
||||||
this.validateKey(key);
|
this.validateKey(key);
|
||||||
localStorage.removeItem(key as string);
|
const resolvedKey = this.resolveKey(key);
|
||||||
|
localStorage.removeItem(resolvedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clear(): Promise<void> {
|
async clear(): Promise<void> {
|
||||||
@@ -71,7 +88,8 @@ export class LocalStorageService<TKey extends string> implements IStorageService
|
|||||||
}
|
}
|
||||||
|
|
||||||
async hasItem(key: TKey): Promise<boolean> {
|
async hasItem(key: TKey): Promise<boolean> {
|
||||||
return localStorage.getItem(key as string) !== null;
|
const resolvedKey = this.resolveKey(key);
|
||||||
|
return localStorage.getItem(resolvedKey) !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async keys(): Promise<TKey[]> {
|
async keys(): Promise<TKey[]> {
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { useState, useEffect, useCallback } from 'react';
|
|||||||
import { useEnterpriseModuleConfigContext } from './use-module.context';
|
import { useEnterpriseModuleConfigContext } from './use-module.context';
|
||||||
import { DraftConfig, FormPageType } from '../entities/entity';
|
import { DraftConfig, FormPageType } from '../entities/entity';
|
||||||
import {
|
import {
|
||||||
appStorage,
|
|
||||||
AppStorageKey,
|
|
||||||
appDatabase,
|
appDatabase,
|
||||||
AppDatabaseKey,
|
AppDatabaseKey,
|
||||||
} from '../../../../../../apps/web/src/core/storage/local';
|
} from '../../../../../../apps/web/src/core/storage/local';
|
||||||
@@ -19,36 +17,28 @@ export function useFormDraftContext({ config }: FormDraftContextProps) {
|
|||||||
const { config: moduleConfig } = useEnterpriseModuleConfigContext();
|
const { config: moduleConfig } = useEnterpriseModuleConfigContext();
|
||||||
const [hasDraft, setHasDraft] = useState(false);
|
const [hasDraft, setHasDraft] = useState(false);
|
||||||
const [draftData, setDraftData] = useState<any>(null);
|
const [draftData, setDraftData] = useState<any>(null);
|
||||||
const [userID, setUserID] = useState<string>('UNRESOLVED_PRINCIPAL');
|
|
||||||
|
|
||||||
const isEnabled = config?.enableDraft === true;
|
const isEnabled = config?.enableDraft === true;
|
||||||
// Use a generic 'CREATE' key for both CREATE and DUPLICATE so that drafts saved
|
// Use a generic 'CREATE' key for both CREATE and DUPLICATE so that drafts saved
|
||||||
// during DUPLICATE can be restored when entering a new CREATE form.
|
// during DUPLICATE can be restored when entering a new CREATE form.
|
||||||
const draftKey = `${userID}:draft:${moduleConfig.moduleKey}:CREATE`;
|
const draftKey = `draft:${moduleConfig.moduleKey}:CREATE`;
|
||||||
|
|
||||||
// Check for existing draft on mount
|
// Check for existing draft on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isEnabled) return;
|
if (!isEnabled) return;
|
||||||
// Fetch user ID asynchronously using appStorage
|
|
||||||
appStorage.getItem(AppStorageKey.USER_ID).then((id: any) => {
|
|
||||||
const resolvedUserId = id || 'UNRESOLVED_PRINCIPAL';
|
|
||||||
setUserID(resolvedUserId);
|
|
||||||
|
|
||||||
const resolvedDraftKey = `${resolvedUserId}:draft:${moduleConfig.moduleKey}:CREATE`;
|
|
||||||
appDatabase
|
appDatabase
|
||||||
.getItem(AppDatabaseKey.OFFLINE_DRAFT)
|
.getItem(AppDatabaseKey.OFFLINE_DRAFT)
|
||||||
.then((allDrafts: any) => {
|
.then((allDrafts: any) => {
|
||||||
const drafts = allDrafts || {};
|
const drafts = allDrafts || {};
|
||||||
if (drafts[resolvedDraftKey]) {
|
if (drafts[draftKey]) {
|
||||||
setDraftData(drafts[resolvedDraftKey]);
|
setDraftData(drafts[draftKey]);
|
||||||
setHasDraft(true);
|
setHasDraft(true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('[Draft Recovery] Failed to read from appDatabase:', err);
|
console.error('[Draft Recovery] Failed to read from appDatabase:', err);
|
||||||
});
|
});
|
||||||
});
|
}, [isEnabled, draftKey]);
|
||||||
}, [isEnabled, moduleConfig.moduleKey]);
|
|
||||||
|
|
||||||
const saveDraft = useCallback(
|
const saveDraft = useCallback(
|
||||||
(data: any) => {
|
(data: any) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user