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}