e21ee2e4fc
Two NIP-alignment fixes: Gap 1 — Report warnings now require `p` match (correctness). Previously `CommunityContentWarning` looked up reports by event id only, so any community member could publish a kind 1984 pairing a victim event's id with their own pubkey on the `p` tag to force a warning overlay onto an arbitrary event. Added `getApplicableReports` in communityUtils mirroring `hasApplicableContentBan`, and use it to require `report.targetPubkey === event.pubkey` before the warning renders. Matches NIP.md §Reports — Content Warnings: "report warnings MUST only attach to content when the target event's id matches the report's `e` tag and the target event's pubkey matches the report's `p` tag." Gap 2 — Members-only filter toggle. The NIP recommends canonical community feeds discard non-member content by default. Added a shield-icon toggle that controls this as a presentation-layer filter, defaulting on. When active, community feeds (Activities feed, per-community Comments tab, and any future community-scoped content surfaces) only show events authored by chain-validated members. When off, everything scoped to the community is shown regardless of authorship. - `useMembersOnlyFilter` — localStorage-backed hook with cross-tab sync; one preference shared across all community surfaces. - `MembersOnlyToggle` — shield / shield-off icon button with tooltip explaining current state. - Filtering is applied post-query in the consumer pages, so toggling is instant and doesn't invalidate the query cache. - Community definition events (kind 34550) are never filtered — they represent the community itself, not user-generated content. - Toggle placement: in `CommunitiesPage` header (scopes the global Activities feed); in `CommunityDetailPage` alongside the tabs (scopes every content feed in that community, now and future). - Empty-state copy hints at the filter when a list is empty only because of it.
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
import { Shield, ShieldOff } from 'lucide-react';
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
import { useMembersOnlyFilter } from '@/hooks/useMembersOnlyFilter';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface MembersOnlyToggleProps {
|
|
/** Additional classes for the trigger button. */
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* Shield-icon toggle that controls the "members only" filter for community
|
|
* surfaces. When active (default), community feeds only show content authored
|
|
* by chain-validated members — matching the NIP's canonical-author
|
|
* recommendation. When inactive, the feed shows every event scoped to the
|
|
* community regardless of author.
|
|
*
|
|
* The preference is persisted in localStorage via `useMembersOnlyFilter` and
|
|
* is global across community surfaces (Activities feed, per-community
|
|
* Comments tab, etc.).
|
|
*/
|
|
export function MembersOnlyToggle({ className }: MembersOnlyToggleProps) {
|
|
const { membersOnly, toggle } = useMembersOnlyFilter();
|
|
|
|
const label = membersOnly ? 'Showing members only' : 'Showing everyone';
|
|
const hint = membersOnly
|
|
? 'Click to show posts from anyone scoped to this community.'
|
|
: 'Click to limit posts to validated community members.';
|
|
|
|
return (
|
|
<TooltipProvider delayDuration={200}>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<button
|
|
type="button"
|
|
onClick={toggle}
|
|
aria-pressed={membersOnly}
|
|
aria-label={label}
|
|
className={cn(
|
|
'p-2 rounded-full transition-colors',
|
|
membersOnly
|
|
? 'text-primary hover:bg-primary/10'
|
|
: 'text-muted-foreground hover:bg-secondary',
|
|
className,
|
|
)}
|
|
>
|
|
{membersOnly
|
|
? <Shield className="size-5" />
|
|
: <ShieldOff className="size-5" />}
|
|
</button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom" className="max-w-[220px] text-center">
|
|
<p className="text-xs font-medium">{label}</p>
|
|
<p className="text-xs text-muted-foreground mt-0.5">{hint}</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
);
|
|
}
|