88 lines
3 KiB
TypeScript
88 lines
3 KiB
TypeScript
import { useMutation } from '@tanstack/react-query';
|
|
import { useNostr } from '@nostrify/react';
|
|
import { useSkateRelays } from '@/hooks/useSkateRelays';
|
|
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
|
import { useToast } from '@/hooks/useToast';
|
|
import { useTranslation } from '@/contexts/language';
|
|
import { MODERATION_LABEL_NAMESPACE } from '@/lib/types';
|
|
|
|
/**
|
|
* One-tap "this is not a skate spot" report.
|
|
*
|
|
* WHY NIP-56 AND NOT A NEW KIND: kind 1984 is the ecosystem's report event and
|
|
* `other` is its catch-all reason, so the report is useful to *any* Nostr client
|
|
* (and visible on relays) instead of being a SkateSpots-only artifact. The extra
|
|
* `L`/`l`/`t` tags give us a stable taxonomy (`not-a-skate-spot`) that our own
|
|
* admin tooling can filter on without breaking anyone else's interpretation.
|
|
*
|
|
* This is for OFF-TOPIC places (test entries, an airport, a mall, private land) —
|
|
* not for disagreeing about whether a spot is good.
|
|
*/
|
|
export function useNotASpotReport() {
|
|
const { t } = useTranslation();
|
|
const { user } = useCurrentUser();
|
|
const { nostr } = useNostr();
|
|
const { getWriteRelays } = useSkateRelays();
|
|
const { toast } = useToast();
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: async ({
|
|
eventId,
|
|
pubkey,
|
|
title,
|
|
}: {
|
|
/** The spot's kind-30818 event id. */
|
|
eventId: string;
|
|
/** The spot's author. */
|
|
pubkey: string;
|
|
title?: string;
|
|
}) => {
|
|
if (!user) throw new Error(t('toastsUi.loginToReport'));
|
|
|
|
const event = await user.signer.signEvent({
|
|
kind: 1984, // NIP-56 report
|
|
content: title ? `Not a skate spot: ${title}` : 'Not a skate spot',
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
tags: [
|
|
['e', eventId, 'other'],
|
|
['p', pubkey],
|
|
['L', MODERATION_LABEL_NAMESPACE],
|
|
['l', 'not-a-skate-spot', MODERATION_LABEL_NAMESPACE],
|
|
['t', 'not-a-skate-spot'],
|
|
['alt', 'SkateSpots: report — not a skate spot'],
|
|
],
|
|
});
|
|
|
|
// Publish to the COMMUNITY relay as well as the reporter's own relays.
|
|
// Admins read reports from the community relay, so a report written only
|
|
// to the reporter's personal relays would never reach them.
|
|
const results = await Promise.allSettled(
|
|
getWriteRelays().map((url) =>
|
|
nostr.relay(url).event(event, { signal: AbortSignal.timeout(8000) }),
|
|
),
|
|
);
|
|
if (results.every((r) => r.status === 'rejected')) {
|
|
throw new Error(t('toastsUi.relayUnreachable'));
|
|
}
|
|
},
|
|
onSuccess: () => {
|
|
toast({
|
|
title: t('toastsUi.notASpotThanks'),
|
|
description: t('toastsUi.notASpotThanksDesc'),
|
|
});
|
|
},
|
|
onError: (error) => {
|
|
toast({
|
|
title: t('toastsUi.notASpotFailed'),
|
|
description: error instanceof Error ? error.message : t('toastsUi.pleaseTryAgain'),
|
|
variant: 'destructive',
|
|
});
|
|
},
|
|
});
|
|
|
|
return {
|
|
reportNotASpot: mutation.mutateAsync,
|
|
isPending: mutation.isPending,
|
|
alreadyReported: mutation.isSuccess,
|
|
};
|
|
}
|