krz/brand-bench
A dynamic brand documentation generator with Ollama integration.
clone: git clone https://gitbay.org/krz/brand-bench.git
main: src/hooks/usePackages.ts · raw
1import { useState, useCallback } from 'react';
2
3export interface PackageSlot {
4 id: string;
5 name: string;
6 createdAt: string;
7 storageKey: string;
8}
9
10interface PackagesStore {
11 activeId: string;
12 slots: PackageSlot[];
13}
14
15const STORE_KEY = 'bw:packages';
16const LEGACY_KEY = 'bw:workspace';
17
18function makeId(): string {
19 return `pkg-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
20}
21
22function makeSlot(name: string, id = makeId()): PackageSlot {
23 return { id, name, createdAt: new Date().toISOString(), storageKey: `bw:ws:${id}` };
24}
25
26function loadStore(): PackagesStore {
27 try {
28 const raw = localStorage.getItem(STORE_KEY);
29 if (raw) return JSON.parse(raw);
30 } catch { /* ignore */ }
31
32 // First run — migrate any legacy single-workspace data into the first slot
33 const firstSlot = makeSlot('Package 1');
34 try {
35 const legacy = localStorage.getItem(LEGACY_KEY);
36 if (legacy) localStorage.setItem(firstSlot.storageKey, legacy);
37 } catch { /* ignore */ }
38
39 const store: PackagesStore = { activeId: firstSlot.id, slots: [firstSlot] };
40 saveStore(store);
41 return store;
42}
43
44function saveStore(store: PackagesStore): void {
45 try { localStorage.setItem(STORE_KEY, JSON.stringify(store)); } catch { /* ignore */ }
46}
47
48export function usePackages() {
49 const [store, setStore] = useState<PackagesStore>(loadStore);
50
51 const activeSlot = store.slots.find(s => s.id === store.activeId) ?? store.slots[0];
52
53 // useWorkspace saves directly to each slot's storageKey on every change,
54 // so switching just needs to update activeId — no snapshot/copy required.
55 const switchTo = useCallback((id: string) => {
56 setStore(prev => {
57 if (prev.activeId === id) return prev;
58 const next = { ...prev, activeId: id };
59 saveStore(next);
60 return next;
61 });
62 }, []);
63
64 const createNew = useCallback(() => {
65 setStore(prev => {
66 const slot = makeSlot(`Package ${prev.slots.length + 1}`);
67 // New slot has no data in localStorage — useWorkspace will start fresh
68 const next = { activeId: slot.id, slots: [...prev.slots, slot] };
69 saveStore(next);
70 return next;
71 });
72 }, []);
73
74 const remove = useCallback((id: string) => {
75 setStore(prev => {
76 if (prev.slots.length <= 1) return prev;
77 const target = prev.slots.find(s => s.id === id);
78 try { if (target) localStorage.removeItem(target.storageKey); } catch { /* ignore */ }
79 const slots = prev.slots.filter(s => s.id !== id);
80 const activeId = prev.activeId === id ? slots[0].id : prev.activeId;
81 const next = { activeId, slots };
82 saveStore(next);
83 return next;
84 });
85 }, []);
86
87 const rename = useCallback((id: string, name: string) => {
88 setStore(prev => {
89 const slots = prev.slots.map(s => s.id === id ? { ...s, name } : s);
90 const next = { ...prev, slots };
91 saveStore(next);
92 return next;
93 });
94 }, []);
95
96 const duplicate = useCallback((id: string) => {
97 setStore(prev => {
98 const source = prev.slots.find(s => s.id === id);
99 if (!source) return prev;
100 const newSlot = makeSlot(`${source.name} copy`);
101 try {
102 const data = localStorage.getItem(source.storageKey);
103 if (data) localStorage.setItem(newSlot.storageKey, data);
104 } catch { /* ignore */ }
105 const next = { activeId: newSlot.id, slots: [...prev.slots, newSlot] };
106 saveStore(next);
107 return next;
108 });
109 }, []);
110
111 return { slots: store.slots, activeId: store.activeId, activeSlot, switchTo, createNew, remove, rename, duplicate };
112}