diff --git a/apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx b/apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx index 0cab5e9..1800b2c 100644 --- a/apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx +++ b/apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx @@ -1,6 +1,6 @@ import { useEffect, useState, useCallback } from 'react'; import { useTranslation, changeLanguage, applyTenantOverrides, i18n } from '@repo/core-i18n'; -import { demoIndexedDB } from '@repo/core-storage'; +import { secureIndexedDB } from '@repo/core-storage'; // Decentralized locale imports import bookingId from '../locales/id/booking.json'; @@ -45,7 +45,7 @@ export default function I18nSample() { const loadDbPayload = useCallback(async () => { try { - const data = await demoIndexedDB.getItem(MOCK_DB_KEY); + const data = await secureIndexedDB.getItem(MOCK_DB_KEY); setDbPayloadStr(data ? JSON.stringify(data, null, 2) : 'No data in DB'); setAdminHeaderTitle(data?.overrides?.header?.title || 'Daftar Pengeluaran'); setAdminModuleName(data?.overrides?.module_name || 'PENGELUARAN'); @@ -66,7 +66,7 @@ export default function I18nSample() { header: { title: adminHeaderTitle }, }, }; - await demoIndexedDB.setItem(MOCK_DB_KEY, payload); + await secureIndexedDB.setItem(MOCK_DB_KEY, payload); setSyncStatus('✅ Saved tenant config to IndexedDB!'); await loadDbPayload(); }; @@ -74,7 +74,7 @@ export default function I18nSample() { // ─── Mock API ─────────────────────────────────────────────────── const mockFetchTenantConfig = async (companyId: string): Promise => { if (companyId === 'company-a') { - const data = await demoIndexedDB.getItem(MOCK_DB_KEY); + const data = await secureIndexedDB.getItem(MOCK_DB_KEY); if (!data) { throw new Error('Company A config not found in DB. Please save via Admin Panel first.'); } diff --git a/apps/web/src/apps/showcase/example/features/storage/presentation/StorageSample.tsx b/apps/web/src/apps/showcase/example/features/storage/presentation/StorageSample.tsx index ad98a68..c399454 100644 --- a/apps/web/src/apps/showcase/example/features/storage/presentation/StorageSample.tsx +++ b/apps/web/src/apps/showcase/example/features/storage/presentation/StorageSample.tsx @@ -1,5 +1,5 @@ import { useState, useCallback } from 'react'; -import { demoSecureStorage, demoIndexedDB, StorageKey } from '@repo/core-storage'; +import { secureStorage, secureIndexedDB, StorageKey } from '@repo/core-storage'; // ─── Demo Data ────────────────────────────────────────────────── @@ -102,12 +102,12 @@ export default function StorageSample() { // ═══════════════════════════════════════════════════════════════ const lsCreate = useCallback(async () => { - await demoSecureStorage.setItem(LS_KEY, DEMO_USER); + await secureStorage.setItem(LS_KEY, DEMO_USER); pushLog(`[LS] CREATE → Stored encrypted: ${JSON.stringify(DEMO_USER)}`); }, [pushLog]); const lsRead = useCallback(async () => { - const result = await demoSecureStorage.getItem(LS_KEY); + const result = await secureStorage.getItem(LS_KEY); if (result) { setLsResult(JSON.stringify(result, null, 2)); pushLog(`[LS] READ → Decrypted: ${JSON.stringify(result)}`); @@ -118,24 +118,24 @@ export default function StorageSample() { }, [pushLog]); const lsUpdate = useCallback(async () => { - const existing = await demoSecureStorage.getItem(LS_KEY); + const existing = await secureStorage.getItem(LS_KEY); if (!existing) { pushLog('[LS] UPDATE → Failed: key does not exist. Create first.'); return; } const updated: DemoUser = { ...existing, role: 'superadmin', id: existing.id + 1 }; - await demoSecureStorage.setItem(LS_KEY, updated); + await secureStorage.setItem(LS_KEY, updated); pushLog(`[LS] UPDATE → Re-encrypted: ${JSON.stringify(updated)}`); }, [pushLog]); const lsDelete = useCallback(async () => { - await demoSecureStorage.removeItem(LS_KEY); + await secureStorage.removeItem(LS_KEY); setLsResult('(deleted)'); pushLog(`[LS] DELETE → Removed key "${LS_KEY}"`); }, [pushLog]); const lsClear = useCallback(async () => { - await demoSecureStorage.clear(); + await secureStorage.clear(); setLsResult('(cleared)'); pushLog('[LS] CLEAR → All localStorage keys removed'); }, [pushLog]); @@ -146,7 +146,7 @@ export default function StorageSample() { const idbCreate = useCallback(async () => { try { - await demoIndexedDB.setItem(IDB_KEY, DEMO_DRAFT); + await secureIndexedDB.setItem(IDB_KEY, DEMO_DRAFT); pushLog(`[IDB] CREATE → Stored: ${JSON.stringify(DEMO_DRAFT)}`); } catch (err) { pushLog(`[IDB] CREATE → ERROR: ${err instanceof Error ? err.message : String(err)}`); @@ -155,7 +155,7 @@ export default function StorageSample() { const idbRead = useCallback(async () => { try { - const result = await demoIndexedDB.getItem(IDB_KEY); + const result = await secureIndexedDB.getItem(IDB_KEY); if (result) { setIdbResult(JSON.stringify(result, null, 2)); pushLog(`[IDB] READ → Retrieved: ${JSON.stringify(result)}`); @@ -170,7 +170,7 @@ export default function StorageSample() { const idbUpdate = useCallback(async () => { try { - const existing = await demoIndexedDB.getItem(IDB_KEY); + const existing = await secureIndexedDB.getItem(IDB_KEY); if (!existing) { pushLog('[IDB] UPDATE → Failed: key does not exist. Create first.'); return; @@ -180,7 +180,7 @@ export default function StorageSample() { id: existing.id + 1, content: `Updated at ${new Date().toLocaleTimeString()}`, }; - await demoIndexedDB.setItem(IDB_KEY, updated); + await secureIndexedDB.setItem(IDB_KEY, updated); pushLog(`[IDB] UPDATE → Persisted: ${JSON.stringify(updated)}`); } catch (err) { pushLog(`[IDB] UPDATE → ERROR: ${err instanceof Error ? err.message : String(err)}`); @@ -189,7 +189,7 @@ export default function StorageSample() { const idbDelete = useCallback(async () => { try { - await demoIndexedDB.removeItem(IDB_KEY); + await secureIndexedDB.removeItem(IDB_KEY); setIdbResult('(deleted)'); pushLog(`[IDB] DELETE → Removed key "${IDB_KEY}"`); } catch (err) { @@ -199,7 +199,7 @@ export default function StorageSample() { const idbClear = useCallback(async () => { try { - await demoIndexedDB.clear(); + await secureIndexedDB.clear(); setIdbResult('(cleared)'); pushLog('[IDB] CLEAR → All IndexedDB entries removed'); } catch (err) { diff --git a/packages/core-i18n/src/manager.ts b/packages/core-i18n/src/manager.ts index d25db0d..08617ff 100644 --- a/packages/core-i18n/src/manager.ts +++ b/packages/core-i18n/src/manager.ts @@ -1,5 +1,5 @@ import i18n from 'i18next'; -import { demoSecureStorage, StorageKey } from '@repo/core-storage'; +import { secureStorage, StorageKey } from '@repo/core-storage'; /** * Changes the active language, saves the preference locally, and optionally syncs with the backend. @@ -9,14 +9,14 @@ import { demoSecureStorage, StorageKey } from '@repo/core-storage'; */ export async function changeLanguage( newLng: string, - syncCallback?: (newLng: string, prevLng: string) => Promise + syncCallback?: (newLng: string, prevLng: string) => Promise, ): Promise { const prevLng = i18n.language; - + if (prevLng === newLng) return; // 1. Update local storage & i18next optimistically - await demoSecureStorage.setItem(StorageKey.LOCALE, newLng); + await secureStorage.setItem(StorageKey.LOCALE, newLng); await i18n.changeLanguage(newLng); // 2. Trigger optional backend sync @@ -26,7 +26,7 @@ export async function changeLanguage( } catch (error) { console.error('[i18n] Backend sync failed, rolling back language', error); // Rollback on failure - await demoSecureStorage.setItem(StorageKey.LOCALE, prevLng); + await secureStorage.setItem(StorageKey.LOCALE, prevLng); await i18n.changeLanguage(prevLng); throw error; // Rethrow so the caller can show an error toast } @@ -43,13 +43,9 @@ export async function changeLanguage( * @param overrides A deeply nested object containing the overridden string keys and values. * @param lng Specific language to override. Defaults to currently active language. */ -export function applyTenantOverrides( - namespace: string, - overrides: Record, - lng?: string -): void { +export function applyTenantOverrides(namespace: string, overrides: Record, lng?: string): void { const targetLng = lng || i18n.language; - + // deep: true -> merges with existing keys rather than replacing the whole namespace // overwrite: true -> allows replacing existing specific keys i18n.addResourceBundle(targetLng, namespace, overrides, true, true); diff --git a/packages/core-i18n/src/setup.ts b/packages/core-i18n/src/setup.ts index 5ff0136..b4c1064 100644 --- a/packages/core-i18n/src/setup.ts +++ b/packages/core-i18n/src/setup.ts @@ -1,6 +1,6 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; -import { demoSecureStorage, StorageKey } from '@repo/core-storage'; +import { secureStorage, StorageKey } from '@repo/core-storage'; import commonEn from './locales/en/common.json'; import commonId from './locales/id/common.json'; @@ -16,14 +16,14 @@ export const resources = { /** * Bootstraps the central i18n engine. - * + * * This reads the preferred locale from secureStorage and initializes * i18next synchronously before React renders. */ export async function setupI18n(): Promise { let initialLng = DEFAULT_LANGUAGE; try { - const storedLng = await demoSecureStorage.getItem(StorageKey.LOCALE); + const storedLng = await secureStorage.getItem(StorageKey.LOCALE); if (storedLng && SUPPORTED_LANGUAGES.includes(storedLng as SupportedLanguage)) { initialLng = storedLng; } @@ -31,17 +31,15 @@ export async function setupI18n(): Promise { console.warn('[i18n] Failed to read locale from storage', err); } - await i18n - .use(initReactI18next) - .init({ - resources, - lng: initialLng, - fallbackLng: DEFAULT_LANGUAGE, - defaultNS: 'common', - interpolation: { - escapeValue: false, // React already escapes values - }, - }); + await i18n.use(initReactI18next).init({ + resources, + lng: initialLng, + fallbackLng: DEFAULT_LANGUAGE, + defaultNS: 'common', + interpolation: { + escapeValue: false, // React already escapes values + }, + }); // Apply initial language to the DOM for SEO/Accessibility if (typeof document !== 'undefined') { diff --git a/packages/core-storage/README.md b/packages/core-storage/README.md index 347f967..8d3b5d4 100644 --- a/packages/core-storage/README.md +++ b/packages/core-storage/README.md @@ -32,36 +32,36 @@ This package provides a unified, Promise-based interface for interacting with br ### 1. Secure Local Storage (Tokens, Profile) -Use `demoSecureStorage` (or your app's configured instance) for small payloads. If the key is in `ENCRYPTED_KEYS`, it will be encrypted at rest. +Use `secureStorage` (or your app's configured instance) for small payloads. If the key is in `ENCRYPTED_KEYS`, it will be encrypted at rest. ```typescript -import { demoSecureStorage, StorageKey } from '@repo/core-storage'; +import { secureStorage, StorageKey } from '@repo/core-storage'; import type { UserProfile } from '@/types'; // CREATE / UPDATE // If StorageKey.USER_PROFILE is in ENCRYPTED_KEYS, this is AES-encrypted automatically. -await demoSecureStorage.setItem(StorageKey.USER_PROFILE, { +await secureStorage.setItem(StorageKey.USER_PROFILE, { id: 1, name: 'Firman', role: 'admin' }); // READ -const profile = await demoSecureStorage.getItem(StorageKey.USER_PROFILE); +const profile = await secureStorage.getItem(StorageKey.USER_PROFILE); if (profile) { console.log('Welcome back,', profile.name); } // DELETE -await demoSecureStorage.removeItem(StorageKey.USER_PROFILE); +await secureStorage.removeItem(StorageKey.USER_PROFILE); ``` ### 2. IndexedDB (Offline Data, Large Payloads) -Use the pre-configured `demoIndexedDB` (or instantiate your own) for large, asynchronous data. It uses the exact same API and encryption pipeline. +Use the pre-configured `secureIndexedDB` (or instantiate your own) for large, asynchronous data. It uses the exact same API and encryption pipeline. ```typescript -import { demoIndexedDB } from '@repo/core-storage'; +import { secureIndexedDB } from '@repo/core-storage'; interface DraftData { id: string; @@ -70,14 +70,14 @@ interface DraftData { } // Save a large draft offline -await demoIndexedDB.setItem('offline_draft_123', { +await secureIndexedDB.setItem('offline_draft_123', { id: '123', content: 'Huge text content...', lastModified: Date.now() }); // Retrieve the draft -const draft = await demoIndexedDB.getItem('offline_draft_123'); +const draft = await secureIndexedDB.getItem('offline_draft_123'); ``` --- diff --git a/packages/core-storage/src/index.ts b/packages/core-storage/src/index.ts index b8c1efb..769079b 100644 --- a/packages/core-storage/src/index.ts +++ b/packages/core-storage/src/index.ts @@ -22,28 +22,27 @@ import { IndexedDBService } from './indexed-db.service'; * * @example * ```ts - * import { demoSecureStorage, StorageKey } from '@repo/core-storage'; + * import { secureStorage, StorageKey } from '@repo/core-storage'; * - * await demoSecureStorage.setItem(StorageKey.ACCESS_TOKEN, 'eyJhbGci...'); - * const token = await demoSecureStorage.getItem(StorageKey.ACCESS_TOKEN); + * await secureStorage.setItem(StorageKey.ACCESS_TOKEN, 'eyJhbGci...'); + * const token = await secureStorage.getItem(StorageKey.ACCESS_TOKEN); * ``` */ -export const demoSecureStorage = new LocalStorageService(); +export const secureStorage = new LocalStorageService(); /** * Default IndexedDB instance. * * Uses `app_db` database with a `kv_store` object store. * Sensitive keys are encrypted at rest using the same - * `EncryptionUtils` pipeline as `demoSecureStorage`. + * `EncryptionUtils` pipeline as `secureStorage`. * * @example * ```ts - * import { demoIndexedDB } from '@repo/core-storage'; + * import { secureIndexedDB } from '@repo/core-storage'; * - * await demoIndexedDB.setItem('offline_draft', { content: '...' }); - * const draft = await demoIndexedDB.getItem('offline_draft'); + * await secureIndexedDB.setItem('offline_draft', { content: '...' }); + * const draft = await secureIndexedDB.getItem('offline_draft'); * ``` */ -export const demoIndexedDB = new IndexedDBService({ dbName: 'app_db', storeName: 'kv_store' }); -export const demoIndexedDB2 = new IndexedDBService({ dbName: 'app_db', storeName: 'kv_store_2' }); +export const secureIndexedDB = new IndexedDBService({ dbName: 'app_db', storeName: 'kv_store' });