Initial commit: ArchiTools modular dashboard platform
Complete Next.js 16 application with 13 fully implemented modules: Email Signature, Word XML Generator, Registratura, Dashboard, Tag Manager, IT Inventory, Address Book, Password Vault, Mini Utilities, Prompt Generator, Digital Signatures, Word Templates, and AI Chat. Includes core platform systems (module registry, feature flags, storage abstraction, i18n, theming, auth stub, tagging), 16 technical documentation files, Docker deployment config, and legacy HTML tool reference. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import type { StorageService } from '../types';
|
||||
|
||||
function nsKey(namespace: string, key: string): string {
|
||||
return `architools:${namespace}:${key}`;
|
||||
}
|
||||
|
||||
function nsPrefix(namespace: string): string {
|
||||
return `architools:${namespace}:`;
|
||||
}
|
||||
|
||||
export class LocalStorageAdapter implements StorageService {
|
||||
async get<T>(namespace: string, key: string): Promise<T | null> {
|
||||
if (typeof window === 'undefined') return null;
|
||||
try {
|
||||
const raw = window.localStorage.getItem(nsKey(namespace, key));
|
||||
if (raw === null) return null;
|
||||
return JSON.parse(raw) as T;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async set<T>(namespace: string, key: string, value: T): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
window.localStorage.setItem(nsKey(namespace, key), JSON.stringify(value));
|
||||
}
|
||||
|
||||
async delete(namespace: string, key: string): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
window.localStorage.removeItem(nsKey(namespace, key));
|
||||
}
|
||||
|
||||
async list(namespace: string): Promise<string[]> {
|
||||
if (typeof window === 'undefined') return [];
|
||||
const prefix = nsPrefix(namespace);
|
||||
const keys: string[] = [];
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
const k = window.localStorage.key(i);
|
||||
if (k && k.startsWith(prefix)) {
|
||||
keys.push(k.slice(prefix.length));
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
async query<T>(namespace: string, predicate: (item: T) => boolean): Promise<T[]> {
|
||||
const keys = await this.list(namespace);
|
||||
const results: T[] = [];
|
||||
for (const key of keys) {
|
||||
const item = await this.get<T>(namespace, key);
|
||||
if (item !== null && predicate(item)) {
|
||||
results.push(item);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
async clear(namespace: string): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
const keys = await this.list(namespace);
|
||||
for (const key of keys) {
|
||||
window.localStorage.removeItem(nsKey(namespace, key));
|
||||
}
|
||||
}
|
||||
|
||||
async export(namespace: string): Promise<Record<string, unknown>> {
|
||||
const keys = await this.list(namespace);
|
||||
const data: Record<string, unknown> = {};
|
||||
for (const key of keys) {
|
||||
data[key] = await this.get(namespace, key);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async import(namespace: string, data: Record<string, unknown>): Promise<void> {
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
await this.set(namespace, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export type { StorageService } from './types';
|
||||
export { StorageProvider, useStorageService } from './storage-provider';
|
||||
export { useStorage } from './use-storage';
|
||||
export type { NamespacedStorage } from './use-storage';
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useMemo } from 'react';
|
||||
import type { StorageService } from './types';
|
||||
import { LocalStorageAdapter } from './adapters/local-storage';
|
||||
|
||||
const StorageContext = createContext<StorageService | null>(null);
|
||||
|
||||
function createAdapter(): StorageService {
|
||||
// Future: select adapter based on environment variable
|
||||
// const adapterType = process.env.NEXT_PUBLIC_STORAGE_ADAPTER;
|
||||
return new LocalStorageAdapter();
|
||||
}
|
||||
|
||||
interface StorageProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function StorageProvider({ children }: StorageProviderProps) {
|
||||
const service = useMemo(() => createAdapter(), []);
|
||||
|
||||
return (
|
||||
<StorageContext.Provider value={service}>
|
||||
{children}
|
||||
</StorageContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useStorageService(): StorageService {
|
||||
const ctx = useContext(StorageContext);
|
||||
if (!ctx) throw new Error('useStorageService must be used within StorageProvider');
|
||||
return ctx;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface StorageService {
|
||||
get<T>(namespace: string, key: string): Promise<T | null>;
|
||||
set<T>(namespace: string, key: string, value: T): Promise<void>;
|
||||
delete(namespace: string, key: string): Promise<void>;
|
||||
list(namespace: string): Promise<string[]>;
|
||||
query<T>(namespace: string, predicate: (item: T) => boolean): Promise<T[]>;
|
||||
clear(namespace: string): Promise<void>;
|
||||
export(namespace: string): Promise<Record<string, unknown>>;
|
||||
import(namespace: string, data: Record<string, unknown>): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useStorageService } from './storage-provider';
|
||||
|
||||
export interface NamespacedStorage {
|
||||
get: <T>(key: string) => Promise<T | null>;
|
||||
set: <T>(key: string, value: T) => Promise<void>;
|
||||
delete: (key: string) => Promise<void>;
|
||||
list: () => Promise<string[]>;
|
||||
query: <T>(predicate: (item: T) => boolean) => Promise<T[]>;
|
||||
clear: () => Promise<void>;
|
||||
exportAll: () => Promise<Record<string, unknown>>;
|
||||
importAll: (data: Record<string, unknown>) => Promise<void>;
|
||||
}
|
||||
|
||||
export function useStorage(namespace: string): NamespacedStorage {
|
||||
const service = useStorageService();
|
||||
|
||||
const get = useCallback(
|
||||
<T,>(key: string) => service.get<T>(namespace, key),
|
||||
[service, namespace]
|
||||
);
|
||||
|
||||
const set = useCallback(
|
||||
<T,>(key: string, value: T) => service.set<T>(namespace, key, value),
|
||||
[service, namespace]
|
||||
);
|
||||
|
||||
const del = useCallback(
|
||||
(key: string) => service.delete(namespace, key),
|
||||
[service, namespace]
|
||||
);
|
||||
|
||||
const list = useCallback(() => service.list(namespace), [service, namespace]);
|
||||
|
||||
const query = useCallback(
|
||||
<T,>(predicate: (item: T) => boolean) => service.query<T>(namespace, predicate),
|
||||
[service, namespace]
|
||||
);
|
||||
|
||||
const clear = useCallback(() => service.clear(namespace), [service, namespace]);
|
||||
|
||||
const exportAll = useCallback(() => service.export(namespace), [service, namespace]);
|
||||
|
||||
const importAll = useCallback(
|
||||
(data: Record<string, unknown>) => service.import(namespace, data),
|
||||
[service, namespace]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({ get, set, delete: del, list, query, clear, exportAll, importAll }),
|
||||
[get, set, del, list, query, clear, exportAll, importAll]
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user