Fifth slice of TRUST_SCORE.md. useNetworkAlerts reads the viewer's NIP-85 assertions for the accounts they follow and surfaces those trusted people reported or muted. New /alerts page lists them with Ignore / Unfollow / Mute / Report (reusing useFollowActions, useMuteUser, ReportDialog) — inform, never auto-act. Settings -> Trust links to it with a live count; ignored accounts are device-local. /alerts added to the smoke list.
63 lines
2 KiB
TypeScript
63 lines
2 KiB
TypeScript
import { useMemo } from 'react';
|
|
|
|
import { useFollowing } from './useFollowing';
|
|
import { useTrustAssertions } from './useTrustAssertions';
|
|
import { useLocalStorage } from './useLocalStorage';
|
|
import { FLAGGED_REPORTERS_THRESHOLD } from '@/lib/trustLadder';
|
|
|
|
/** A muted-by-trusted-people count at/above this also raises an alert. */
|
|
const MUTERS_THRESHOLD = FLAGGED_REPORTERS_THRESHOLD;
|
|
|
|
export interface NetworkAlert {
|
|
pubkey: string;
|
|
reporters: number;
|
|
muters: number;
|
|
followers?: number;
|
|
hops?: number;
|
|
}
|
|
|
|
/**
|
|
* Accounts the viewer follows that trusted people have reported or muted,
|
|
* according to the active NIP-85 provider.
|
|
*
|
|
* This is a safety notice, not an automatic action: the page offers
|
|
* ignore / unfollow / mute / report, and the user decides. Dismissed accounts
|
|
* are remembered device-locally.
|
|
*/
|
|
export function useNetworkAlerts() {
|
|
const { data: following } = useFollowing();
|
|
const { data: assertions, isLoading } = useTrustAssertions(following ?? []);
|
|
const [ignored, setIgnored] = useLocalStorage<string[]>('skatespots:trust:alerts-ignored', []);
|
|
|
|
const alerts = useMemo(() => {
|
|
const out: NetworkAlert[] = [];
|
|
for (const pubkey of following ?? []) {
|
|
if (ignored.includes(pubkey)) continue;
|
|
const assertion = assertions?.get(pubkey);
|
|
if (!assertion) continue;
|
|
|
|
const reporters = assertion.reporters ?? 0;
|
|
const muters = assertion.muters ?? 0;
|
|
if (reporters >= FLAGGED_REPORTERS_THRESHOLD || muters >= MUTERS_THRESHOLD) {
|
|
out.push({
|
|
pubkey,
|
|
reporters,
|
|
muters,
|
|
followers: assertion.followers,
|
|
hops: assertion.hops,
|
|
});
|
|
}
|
|
}
|
|
// Strongest negative signal first.
|
|
out.sort((a, b) => b.reporters + b.muters - (a.reporters + a.muters));
|
|
return out;
|
|
}, [following, assertions, ignored]);
|
|
|
|
return {
|
|
alerts,
|
|
alertCount: alerts.length,
|
|
isLoading,
|
|
ignore: (pubkey: string) =>
|
|
setIgnored((prev) => (prev.includes(pubkey) ? prev : [...prev, pubkey])),
|
|
};
|
|
}
|