feat: add useDecisions hook with optimistic updates
This commit is contained in:
parent
813f048e46
commit
2fdeebbae1
1 changed files with 100 additions and 0 deletions
100
frontend/src/hooks/useDecisions.ts
Normal file
100
frontend/src/hooks/useDecisions.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import type { AuthUser } from '@/auth/types';
|
||||
import type { DecisionType } from '@/types';
|
||||
import { fetchDecisions, setDecision as apiSetDecision, clearDecision as apiClearDecision } from '@/services';
|
||||
|
||||
function decisionKey(listingId: number, listingType: string): string {
|
||||
return `${listingId}-${listingType}`;
|
||||
}
|
||||
|
||||
export function useDecisions(user: AuthUser | null) {
|
||||
const [decisions, setDecisions] = useState<Map<string, DecisionType>>(new Map());
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
// Load decisions on mount
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
fetchDecisions(user)
|
||||
.then((list) => {
|
||||
const map = new Map<string, DecisionType>();
|
||||
for (const d of list) {
|
||||
map.set(decisionKey(d.listing_id, d.listing_type), d.decision);
|
||||
}
|
||||
setDecisions(map);
|
||||
setIsLoaded(true);
|
||||
})
|
||||
.catch(() => setIsLoaded(true));
|
||||
}, [user]);
|
||||
|
||||
const decide = useCallback(
|
||||
async (listingId: number, decision: DecisionType, listingType: 'RENT' | 'BUY' = 'RENT') => {
|
||||
if (!user) return;
|
||||
const key = decisionKey(listingId, listingType);
|
||||
|
||||
// Optimistic update
|
||||
setDecisions((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(key, decision);
|
||||
return next;
|
||||
});
|
||||
|
||||
try {
|
||||
await apiSetDecision(user, listingId, decision, listingType);
|
||||
} catch {
|
||||
// Revert on failure
|
||||
setDecisions((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.delete(key);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
},
|
||||
[user],
|
||||
);
|
||||
|
||||
const clear = useCallback(
|
||||
async (listingId: number, listingType: 'RENT' | 'BUY' = 'RENT') => {
|
||||
if (!user) return;
|
||||
const key = decisionKey(listingId, listingType);
|
||||
const previous = decisions.get(key);
|
||||
|
||||
setDecisions((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.delete(key);
|
||||
return next;
|
||||
});
|
||||
|
||||
try {
|
||||
await apiClearDecision(user, listingId, listingType);
|
||||
} catch {
|
||||
if (previous) {
|
||||
setDecisions((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(key, previous);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[user, decisions],
|
||||
);
|
||||
|
||||
const getDecision = useCallback(
|
||||
(listingId: number, listingType: string = 'RENT'): DecisionType | undefined => {
|
||||
return decisions.get(decisionKey(listingId, listingType));
|
||||
},
|
||||
[decisions],
|
||||
);
|
||||
|
||||
const likedCount = useMemo(
|
||||
() => Array.from(decisions.values()).filter((d) => d === 'liked').length,
|
||||
[decisions],
|
||||
);
|
||||
|
||||
const dislikedCount = useMemo(
|
||||
() => Array.from(decisions.values()).filter((d) => d === 'disliked').length,
|
||||
[decisions],
|
||||
);
|
||||
|
||||
return { decisions, isLoaded, decide, clear, getDecision, likedCount, dislikedCount };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue