From 1dc36ddb45a88b2fbf72f8e9ee87fa370331f184 Mon Sep 17 00:00:00 2001 From: Denis Lugowski Date: Sat, 21 Feb 2026 09:07:36 +0100 Subject: [PATCH] feat(#38): Add periodic local storage backup for application form --- .../app/composables/useLocalStorageBackup.ts | 97 +++++++++++++++++++ .../application-forms/[id]/[sectionIndex].vue | 87 +++++++++++++++++ legalconsenthub/app/pages/create.vue | 73 ++++++++++++++ legalconsenthub/eslint.config.mjs | 1 + legalconsenthub/i18n/locales/de.json | 9 +- legalconsenthub/i18n/locales/en.json | 9 +- 6 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 legalconsenthub/app/composables/useLocalStorageBackup.ts diff --git a/legalconsenthub/app/composables/useLocalStorageBackup.ts b/legalconsenthub/app/composables/useLocalStorageBackup.ts new file mode 100644 index 0000000..8ebd838 --- /dev/null +++ b/legalconsenthub/app/composables/useLocalStorageBackup.ts @@ -0,0 +1,97 @@ +import type { ApplicationFormDto } from '~~/.api-client' +import { useLogger } from '~/composables/useLogger' + +interface LocalStorageBackup { + formId: string + sectionIndex: number + timestamp: number + formData: ApplicationFormDto +} + +const STORAGE_KEY_PREFIX = 'lch-form-backup-' + +export function useLocalStorageBackup(formId: string) { + const storageKey = `${STORAGE_KEY_PREFIX}${formId}` + const logger = useLogger().withTag('localStorageBackup') + + const hasBackup = ref(false) + const backupTimestamp = ref(null) + const backupSectionIndex = ref(null) + + function checkForBackup(): boolean { + if (import.meta.server) return false + + try { + const storedApplicationForm = localStorage.getItem(storageKey) + if (storedApplicationForm) { + const backup = JSON.parse(storedApplicationForm) as LocalStorageBackup + hasBackup.value = true + backupTimestamp.value = new Date(backup.timestamp) + backupSectionIndex.value = backup.sectionIndex + return true + } + } catch { + clearBackup() + } + return false + } + + function saveBackup(formData: ApplicationFormDto, currentSectionIndex: number): void { + if (import.meta.server) return + + try { + const backup: LocalStorageBackup = { + formId, + sectionIndex: currentSectionIndex, + timestamp: Date.now(), + formData + } + localStorage.setItem(storageKey, JSON.stringify(backup)) + hasBackup.value = true + backupTimestamp.value = new Date(backup.timestamp) + backupSectionIndex.value = currentSectionIndex + } catch (e) { + // localStorage might be full or unavailable + logger.error('Error saving backup', e) + } + } + + function loadBackup(): ApplicationFormDto | null { + if (import.meta.server) return null + + try { + const stored = localStorage.getItem(storageKey) + if (stored) { + const backup = JSON.parse(stored) as LocalStorageBackup + return backup.formData + } + } catch { + clearBackup() + } + return null + } + + function clearBackup(): void { + if (import.meta.server) return + + localStorage.removeItem(storageKey) + hasBackup.value = false + backupTimestamp.value = null + backupSectionIndex.value = null + } + + // Call synchronously - import.meta.server guard handles SSR safety. + // Do NOT use onMounted here: this composable is called after `await` in async setup(), + // which means Vue's active component instance is gone and lifecycle hooks cannot be registered. + checkForBackup() + + return { + hasBackup: readonly(hasBackup), + backupTimestamp: readonly(backupTimestamp), + backupSectionIndex: readonly(backupSectionIndex), + saveBackup, + loadBackup, + clearBackup, + checkForBackup + } +} diff --git a/legalconsenthub/app/pages/application-forms/[id]/[sectionIndex].vue b/legalconsenthub/app/pages/application-forms/[id]/[sectionIndex].vue index 43fea57..6477dc2 100644 --- a/legalconsenthub/app/pages/application-forms/[id]/[sectionIndex].vue +++ b/legalconsenthub/app/pages/application-forms/[id]/[sectionIndex].vue @@ -43,9 +43,45 @@ + + + + + +