refactor: replace secure storage with app storage across components and hooks

This commit is contained in:
Firman Ramdhani
2026-07-27 12:23:46 +07:00
parent ca0c57277a
commit 94fc29722c
8 changed files with 52 additions and 44 deletions
@@ -4,7 +4,7 @@ import { useAppEvent } from '@repo/core-events';
import { useTranslation } from '@repo/core-i18n';
import { Bookmark, Trash2 } from 'lucide-react';
import { LAYOUT_EVENTS } from '../../../../../core/constants/events';
import { secureIndexedDB, AppStorageKey } from '../../../../../core/storage/local';
import { appDatabase, AppDatabaseKey } from '../../../../../core/storage/local';
import { Link } from 'react-router-dom';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
@@ -25,7 +25,7 @@ export function BookmarkDrawer() {
const loadData = async () => {
try {
const data = await secureIndexedDB.getItem<SavedPageItem[]>(AppStorageKey.BOOKMARK_PAGE);
const data = await appDatabase.getItem<SavedPageItem[]>(AppDatabaseKey.BOOKMARK_PAGE);
setItems(data || []);
} catch (e) {
console.error('Failed to load bookmark items', e);
@@ -44,7 +44,7 @@ export function BookmarkDrawer() {
e.stopPropagation();
try {
const newItems = items.filter((item) => item.id !== id);
await secureIndexedDB.setItem(AppStorageKey.BOOKMARK_PAGE, newItems);
await appDatabase.setItem(AppDatabaseKey.BOOKMARK_PAGE, newItems);
setItems(newItems);
} catch (err) {
console.error('Failed to remove bookmark item', err);
@@ -53,7 +53,7 @@ export function BookmarkDrawer() {
const handleClearAll = async () => {
try {
await secureIndexedDB.removeItem(AppStorageKey.BOOKMARK_PAGE);
await appDatabase.removeItem(AppDatabaseKey.BOOKMARK_PAGE);
setItems([]);
} catch (err) {
console.error('Failed to clear bookmarks', err);
@@ -13,7 +13,7 @@ import {
Box,
} from '@repo/ui/components';
import { Globe, Bell, HelpCircle, Settings, User, Briefcase, LogOut, ChevronDown, Sun, Moon } from 'lucide-react';
import { AppStorageKey, secureStorage } from '../../../../core/storage/local';
import { AppStorageKey, appStorage } from '../../../../core/storage/local';
import { useThemeStore } from '../../../../core/stores/theme.store';
import { NotificationDropdown } from './notifications/notification-dropdown';
import { Link } from 'react-router-dom';
@@ -131,7 +131,7 @@ export default function HeaderLayout() {
onChange={async (val) => {
if (!val) return;
await i18n.changeLanguage(val);
await secureStorage.setItem(AppStorageKey.LANGUAGE, val);
await appStorage.setItem(AppStorageKey.LANGUAGE, val);
}}
styles={{
input: {
@@ -15,7 +15,7 @@ import { useAppEvent } from '@repo/core-events';
import { useTranslation } from '@repo/core-i18n';
import { History, Clock, Trash2 } from 'lucide-react';
import { LAYOUT_EVENTS } from '../../../../../core/constants/events';
import { secureIndexedDB, AppStorageKey } from '../../../../../core/storage/local';
import { appDatabase, AppDatabaseKey } from '../../../../../core/storage/local';
import { Link } from 'react-router-dom';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
@@ -38,9 +38,9 @@ export function HistoryDrawer() {
const loadData = async () => {
try {
const data = await secureIndexedDB.getItem<SavedPageItem[]>(AppStorageKey.HISTORY_PAGE);
const data = await appDatabase.getItem<SavedPageItem[]>(AppDatabaseKey.HISTORY_PAGE);
const settings =
(await secureIndexedDB.getItem<SystemSettings>(AppStorageKey.SYSTEM_SETTINGS)) || DEFAULT_SYSTEM_SETTINGS;
(await appDatabase.getItem<SystemSettings>(AppDatabaseKey.SYSTEM_SETTINGS)) || DEFAULT_SYSTEM_SETTINGS;
const now = Date.now();
const unitMs = {
@@ -55,7 +55,7 @@ export function HistoryDrawer() {
// Save back if we purged some items
if (data && validItems.length !== data.length) {
await secureIndexedDB.setItem(AppStorageKey.HISTORY_PAGE, validItems);
await appDatabase.setItem(AppDatabaseKey.HISTORY_PAGE, validItems);
}
setItems(validItems);
@@ -74,7 +74,7 @@ export function HistoryDrawer() {
const handleClearRange = async (range: 'lastHour' | 'today' | 'allTime') => {
try {
if (range === 'allTime') {
await secureIndexedDB.removeItem(AppStorageKey.HISTORY_PAGE);
await appDatabase.removeItem(AppDatabaseKey.HISTORY_PAGE);
setItems([]);
return;
}
@@ -83,7 +83,7 @@ export function HistoryDrawer() {
const cutoff = range === 'lastHour' ? now - 60 * 60 * 1000 : new Date().setHours(0, 0, 0, 0); // start of today
const newItems = items.filter((item) => item.timestamp < cutoff);
await secureIndexedDB.setItem(AppStorageKey.HISTORY_PAGE, newItems);
await appDatabase.setItem(AppDatabaseKey.HISTORY_PAGE, newItems);
setItems(newItems);
} catch (e) {
console.error('Failed to clear history items', e);
@@ -95,7 +95,7 @@ export function HistoryDrawer() {
e.stopPropagation();
try {
const newItems = items.filter((item) => item.id !== id);
await secureIndexedDB.setItem(AppStorageKey.HISTORY_PAGE, newItems);
await appDatabase.setItem(AppDatabaseKey.HISTORY_PAGE, newItems);
setItems(newItems);
} catch (err) {
console.error('Failed to remove history item', err);
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { secureIndexedDB, AppStorageKey } from '../../../../core/storage/local';
import { appDatabase, AppDatabaseKey } from '../../../../core/storage/local';
import type { SavedPageItem } from '../types/saved-page.types';
import type { SystemSettings } from '../../system/setting/types/setting.types';
import { DEFAULT_SYSTEM_SETTINGS } from '../../system/setting/types/setting.types';
@@ -34,7 +34,7 @@ export async function initializeAndPurgeHistoryBackground() {
if (isHistoryReady) return;
try {
const data = (await secureIndexedDB.getItem<SavedPageItem[]>(AppStorageKey.HISTORY_PAGE)) || [];
const data = (await appDatabase.getItem<SavedPageItem[]>(AppDatabaseKey.HISTORY_PAGE)) || [];
if (data.length === 0) {
isHistoryReady = true;
@@ -42,7 +42,7 @@ export async function initializeAndPurgeHistoryBackground() {
}
const settings =
(await secureIndexedDB.getItem<SystemSettings>(AppStorageKey.SYSTEM_SETTINGS)) || DEFAULT_SYSTEM_SETTINGS;
(await appDatabase.getItem<SystemSettings>(AppDatabaseKey.SYSTEM_SETTINGS)) || DEFAULT_SYSTEM_SETTINGS;
const now = Date.now();
const unitMs: Record<string, number> = {
seconds: 1000,
@@ -58,7 +58,7 @@ export async function initializeAndPurgeHistoryBackground() {
isHistoryReady = true;
if (validItems.length !== data.length) {
await secureIndexedDB.setItem(AppStorageKey.HISTORY_PAGE, validItems);
await appDatabase.setItem(AppDatabaseKey.HISTORY_PAGE, validItems);
}
} catch (err) {
console.error('[HistoryManager] Failed to init history', err);
@@ -100,7 +100,7 @@ export function useHistoryTracker() {
historyChannel?.postMessage(newItem);
// 3. Save to IndexedDB in the background (Asynchronous)
secureIndexedDB.setItem(AppStorageKey.HISTORY_PAGE, memoryHistoryCache).catch((err) => {
appDatabase.setItem(AppDatabaseKey.HISTORY_PAGE, memoryHistoryCache).catch((err) => {
console.error('[HistoryManager] Failed to track history', err);
});
};
@@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { FieldNumberInput, FieldSelect } from '@repo/ui/form';
import { secureIndexedDB, AppStorageKey } from '../../../../../core/storage/local';
import { appDatabase, AppDatabaseKey } from '../../../../../core/storage/local';
import type { SystemSettings, RetentionUnit } from '../types/setting.types';
import { DEFAULT_SYSTEM_SETTINGS } from '../types/setting.types';
@@ -40,7 +40,7 @@ export function HistorySetting({ onDirtyChange }: { onDirtyChange: (isDirty: boo
useEffect(() => {
const loadSettings = async () => {
try {
const settings = await secureIndexedDB.getItem<SystemSettings>(AppStorageKey.SYSTEM_SETTINGS);
const settings = await appDatabase.getItem<SystemSettings>(AppDatabaseKey.SYSTEM_SETTINGS);
if (settings) {
reset({
historyRetentionValue: settings.historyRetentionValue,
@@ -61,7 +61,7 @@ export function HistorySetting({ onDirtyChange }: { onDirtyChange: (isDirty: boo
const handleReset = async () => {
try {
const settings = await secureIndexedDB.getItem<SystemSettings>(AppStorageKey.SYSTEM_SETTINGS);
const settings = await appDatabase.getItem<SystemSettings>(AppDatabaseKey.SYSTEM_SETTINGS);
if (settings) {
reset({
historyRetentionValue: settings.historyRetentionValue,
@@ -82,13 +82,13 @@ export function HistorySetting({ onDirtyChange }: { onDirtyChange: (isDirty: boo
setLoading(true);
try {
const currentSettings =
(await secureIndexedDB.getItem<SystemSettings>(AppStorageKey.SYSTEM_SETTINGS)) || DEFAULT_SYSTEM_SETTINGS;
(await appDatabase.getItem<SystemSettings>(AppDatabaseKey.SYSTEM_SETTINGS)) || DEFAULT_SYSTEM_SETTINGS;
const newSettings = {
...currentSettings,
historyRetentionValue: data.historyRetentionValue,
historyRetentionUnit: data.historyRetentionUnit as RetentionUnit,
};
await secureIndexedDB.setItem(AppStorageKey.SYSTEM_SETTINGS, newSettings);
await appDatabase.setItem(AppDatabaseKey.SYSTEM_SETTINGS, newSettings);
reset(data); // Reset form to clear dirty state
} catch (err) {
+2 -2
View File
@@ -1,7 +1,7 @@
import { createHttpClient } from '@repo/core-api/http-client';
import { faroAdapter } from '@repo/core-api/observability';
import { ENV } from '../environment';
import { AppStorageKey, secureStorage } from '../storage/local';
import { AppStorageKey, appStorage } from '../storage/local';
/**
* Enterprise HTTP client for `apps/web`.
@@ -29,7 +29,7 @@ export const apiClient = createHttpClient(
config.headers['ex-timezone-offset-minutes'] = new Date().getTimezoneOffset();
config.headers['ex-timezone-offset-hours'] = Math.floor(new Date().getTimezoneOffset() / 60);
const language = await secureStorage.getItem<string>(AppStorageKey.LANGUAGE);
const language = await appStorage.getItem<string>(AppStorageKey.LANGUAGE);
config.headers['ex-language'] = language ?? ENV.DEFAULT_LANGUAGE;
const token = localStorage.getItem('access_token');
+23 -15
View File
@@ -6,39 +6,47 @@ export const AppStorageKey = {
THEME: 'app_theme',
ACCESS_TOKEN: 'access_token',
REFRESH_TOKEN: 'refresh_token',
MOCK_DB_COMPANY_A: 'mock_db_company_a',
} as const;
export type AppStorageKeyValue = (typeof AppStorageKey)[keyof typeof AppStorageKey];
export const AppDatabaseKey = {
OFFLINE_DRAFT: 'offline_draft',
SYSTEM_SETTINGS: 'system_settings',
HISTORY_PAGE: 'history_page',
BOOKMARK_PAGE: 'bookmark_page',
} as const;
export type AppStorageKeyValue = (typeof AppStorageKey)[keyof typeof AppStorageKey];
export type AppDatabaseKeyValue = (typeof AppDatabaseKey)[keyof typeof AppDatabaseKey];
export const ENCRYPTED_KEYS = new Set<AppStorageKeyValue>([
export const APP_STORAGE_ENCRYPTED_KEYS = new Set<AppStorageKeyValue>([
AppStorageKey.USER_PROFILE,
AppStorageKey.ACCESS_TOKEN,
AppStorageKey.REFRESH_TOKEN,
]);
export const PLAIN_KEYS = new Set<AppStorageKeyValue>([
export const APP_STORAGE_PLAIN_KEYS = new Set<AppStorageKeyValue>([
AppStorageKey.LANGUAGE,
AppStorageKey.THEME,
AppStorageKey.MOCK_DB_COMPANY_A,
AppStorageKey.OFFLINE_DRAFT,
AppStorageKey.SYSTEM_SETTINGS,
AppStorageKey.HISTORY_PAGE,
AppStorageKey.BOOKMARK_PAGE,
]);
export const secureStorage = createLocalStorage<AppStorageKeyValue>({
encryptedKeys: ENCRYPTED_KEYS,
plainTextKeys: PLAIN_KEYS,
export const APP_DATABASE_ENCRYPTED_KEYS = new Set<AppDatabaseKeyValue>([]);
export const APP_DATABASE_PLAIN_KEYS = new Set<AppDatabaseKeyValue>([
AppDatabaseKey.OFFLINE_DRAFT,
AppDatabaseKey.SYSTEM_SETTINGS,
AppDatabaseKey.HISTORY_PAGE,
AppDatabaseKey.BOOKMARK_PAGE,
]);
export const appStorage = createLocalStorage<AppStorageKeyValue>({
encryptedKeys: APP_STORAGE_ENCRYPTED_KEYS,
plainTextKeys: APP_STORAGE_PLAIN_KEYS,
});
export const secureIndexedDB = createIndexedDB<AppStorageKeyValue>({
export const appDatabase = createIndexedDB<AppDatabaseKeyValue>({
dbName: 'e_apps_db',
storeName: 'web_store',
encryptedKeys: ENCRYPTED_KEYS,
plainTextKeys: PLAIN_KEYS,
encryptedKeys: APP_DATABASE_ENCRYPTED_KEYS,
plainTextKeys: APP_DATABASE_PLAIN_KEYS,
});
+4 -4
View File
@@ -16,21 +16,21 @@ import './main.css';
import { lazy, StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { setupI18n } from '@repo/core-i18n';
import { secureStorage, AppStorageKey } from './core/storage/local';
import { appStorage, AppStorageKey } from './core/storage/local';
import { ENV } from './core/environment';
const App = lazy(() => import('./apps'));
async function bootstrap() {
// Initialize i18next and load language from secureStorage
// Initialize i18next and load language from appStorage
await setupI18n(
{
storageAdapter: {
getLanguage: async () => {
return secureStorage.getItem<string>(AppStorageKey.LANGUAGE);
return appStorage.getItem<string>(AppStorageKey.LANGUAGE);
},
setLanguage: async (lng: string) => {
await secureStorage.setItem(AppStorageKey.LANGUAGE, lng);
await appStorage.setItem(AppStorageKey.LANGUAGE, lng);
},
},
},