Ctrl K
ComunidadGuías…

frontend/src/hooks/useFavorites.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";

const STORAGE_KEY = "privatools_favorites";
const FAVORITES_CHANGED_EVENT = "privatools:favorites-changed";
/** Hard cap on the number of favorites. Sidebar only ever displays the first
 *  8, but we allow a larger pool so users can curate without losing entries. */
export const MAX_FAVORITES = 20;

function loadFavorites(): string[] {
    try {
        const raw = localStorage.getItem(STORAGE_KEY);
        const parsed = raw ? JSON.parse(raw) : [];
        if (!Array.isArray(parsed)) return [];
        // De-dupe and trim — guards against legacy storage formats and the
        // (rare) case where two tabs raced and wrote dupes.
        return [...new Set(parsed.filter((x): x is string => typeof x === "string" && x.length > 0))].slice(0, MAX_FAVORITES);
    } catch { return []; }
}

function saveFavorites(list: string[]) {
    try {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(list.slice(0, MAX_FAVORITES)));
        queueMicrotask(() => window.dispatchEvent(new Event(FAVORITES_CHANGED_EVENT)));
    } catch { /* quota full */ }
}

export function useFavorites() {
    const [favorites, setFavorites] = useState<string[]>(loadFavorites);

    useEffect(() => {
        const handler = (e: StorageEvent) => {
            if (e.key === STORAGE_KEY) setFavorites(loadFavorites());
        };
        const sameTabHandler = () => setFavorites(loadFavorites());
        window.addEventListener("storage", handler);
        window.addEventListener(FAVORITES_CHANGED_EVENT, sameTabHandler);
        return () => {
            window.removeEventListener("storage", handler);
            window.removeEventListener(FAVORITES_CHANGED_EVENT, sameTabHandler);
        };
    }, []);

    const toggle = useCallback((slug: string) => {
        setFavorites(prev => {
            const wasFav = prev.includes(slug);
            let next: string[];
            if (wasFav) {
                next = prev.filter(s => s !== slug);
            } else {
                if (prev.length >= MAX_FAVORITES) {
                    try { toast.warning(`You can pin up to ${MAX_FAVORITES} tools — unpin one first`, { duration: 2000 }); } catch { /* */ }
                    return prev;
                }
                next = [...prev, slug];
            }
            saveFavorites(next);
            try {
                if (wasFav) toast(`Removed from favorites`, { duration: 1500 });
                else        toast(`Added to favorites`,    { duration: 1500 });
            } catch { /* sonner may not be mounted in tests */ }
            return next;
        });
    }, []);

    const isFavorite = useCallback((slug: string) => favorites.includes(slug), [favorites]);

    /**
     * Move a favorite from one position to another. Used by drag-and-drop
     * reordering in the sidebar. Both indices are clamped to valid range;
     * out-of-range or no-op calls are silently ignored.
     */
    const reorder = useCallback((fromIndex: number, toIndex: number) => {
        setFavorites(prev => {
            if (fromIndex === toIndex) return prev;
            if (fromIndex < 0 || fromIndex >= prev.length) return prev;
            if (toIndex < 0 || toIndex >= prev.length) return prev;
            const next = prev.slice();
            const [moved] = next.splice(fromIndex, 1);
            next.splice(toIndex, 0, moved);
            saveFavorites(next);
            return next;
        });
    }, []);

    /** Move a favorite to the top (== position 0). Convenience for keyboard. */
    const pinToTop = useCallback((slug: string) => {
        setFavorites(prev => {
            const idx = prev.indexOf(slug);
            if (idx <= 0) return prev;
            const next = [slug, ...prev.filter(s => s !== slug)];
            saveFavorites(next);
            return next;
        });
    }, []);

    return { favorites, toggle, isFavorite, reorder, pinToTop, count: favorites.length, max: MAX_FAVORITES };
}