File size: 3,035 Bytes
191b322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import { useState, useEffect } from 'react';

/**
 * Helper to dynamically grab auth token if available.
 */
const getAuthHeaders = () => {
    const token = localStorage.getItem('auth_token');
    return {
        'Content-Type': 'application/json',
        ...(token ? { 'Authorization': `Bearer ${token}` } : {})
    };
};

/**
 * A hook to manage a generic collection via our Express MongoDB API.
 * Uses optimistic updates for a snappy UX without websockets.
 */
export function useLocalCollection<T extends { id: string }>(collectionName: string, refreshKey = '') {
    const [data, setData] = useState<T[]>([]);

    useEffect(() => {
        let mounted = true;
        const token = localStorage.getItem('auth_token');
        fetch(`/api/collections/${collectionName}`, {
            headers: token ? { 'Authorization': `Bearer ${token}` } : {}
        })
            .then(res => {
                if (res.status === 401 || res.status === 403) return []; // Not logged in
                if (!res.ok) throw new Error("Fetch failed");
                return res.json();
            })
            .then(result => {
                if (mounted) {
                    setData(Array.isArray(result) ? result : []);
                    if (!Array.isArray(result)) {
                        console.error(`Expected array for ${collectionName}, got:`, result);
                    }
                }
            })
            .catch(err => console.error(`Error fetching ${collectionName}:`, err));
        
        return () => { mounted = false; };
    }, [collectionName, refreshKey]);

    const add = async (item: T) => {
        setData(prev => [...prev, item]); // Optimistic
        try {
            await fetch(`/api/collections/${collectionName}`, {
                method: 'POST',
                headers: getAuthHeaders(),
                body: JSON.stringify(item)
            });
        } catch (e) {
            console.error(`Error adding to ${collectionName}:`, e);
        }
    };

    const update = async (id: string, updates: Partial<T>) => {
        setData(prev => prev.map(d => d.id === id ? { ...d, ...updates } : d)); // Optimistic
        try {
            await fetch(`/api/collections/${collectionName}/${id}`, {
                method: 'PUT',
                headers: getAuthHeaders(),
                body: JSON.stringify(updates)
            });
        } catch (e) {
            console.error(`Error updating ${collectionName}:`, e);
        }
    };

    const remove = async (id: string) => {
        setData(prev => prev.filter(d => d.id !== id)); // Optimistic
        try {
            const token = localStorage.getItem('auth_token');
            await fetch(`/api/collections/${collectionName}/${id}`, {
                method: 'DELETE',
                headers: token ? { 'Authorization': `Bearer ${token}` } : {}
            });
        } catch (e) {
            console.error(`Error deleting from ${collectionName}:`, e);
        }
    };

    return { data, add, update, remove };
}