Files
eranos/src/components/BanConfirmDialog.tsx
T
lemon ac3cdf34b2 Rename user-facing 'Community' → 'Organization'
User-visible copy now matches the Organization rebrand. Internal
symbols, file names, query keys, routes, and storage keys are
intentionally left alone for this pass — they're still pinned to
"community" / "communities" until a dedicated rename commit.

Touched strings:

- `MobileBottomNav` and `sidebarItems` labels: "Communities" →
  "Organize", matching the existing TopNav copy.
- `CommunityDetailPage` hero fallback ("Unnamed Community" →
  "Unnamed Organization") and "About this organization" aria-label.
- `CommunityContent` thumbnail fallback name.
- `ExternalContentHeader.CommunityPreview` row label and fallback name.
- `NoteCard` kind-34550 noun "community" → "organization" (used in
  feed-card action lines like "created an organization") and the
  article switches from "a" to "an".
- `NoteMoreMenu` overflow-menu labels: "Report post to community" →
  "Report post to organization", "Remove from community" → "Remove
  from organization".
- `BanConfirmDialog` title, description, and success/failure toasts.
- `CommunityContentWarning` reporter pluralization and the
  fallback report-type label ("community guidelines" →
  "organization guidelines"); reporters are now scoped to founder /
  moderators per the commit 4 cleanup, so the wording reflects that.
- `CommunityReportDialog` description copy.
- `CreateGoalDialog` placeholder example.
- `CreateActionDialog` org-scoped description string.
- `CreateCommunityEventDialog` NIP-31 `alt` tag prefix.
- `CommentContext` kind-34550 entries in the action-noun and
  rendered-noun maps ("a community" / "community" → "an
  organization" / "organization").
- `extraKinds` kind-34550 entry: label, description, and blurb.
- `kindLabels` kinds 4550, 10004, 34550.
- `DiscoverHero` ticker stat copy.
- `GetFeedTool` error message drops "communities" since the
  Following feed no longer includes organization activity (removed
  in the badge-runtime commit).
2026-05-20 12:32:57 -07:00

120 lines
3.8 KiB
TypeScript

import { useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import { useNostrPublish } from '@/hooks/useNostrPublish';
import { toast } from '@/hooks/useToast';
import {
MODERATION_BAN_LABEL,
MODERATION_LABEL_NAMESPACE,
REPORT_KIND,
} from '@/lib/communityUtils';
// ── Props ─────────────────────────────────────────────────────────────────────
//
// Only content-level bans remain. Agora's organization trust model has no
// "member" tier any more, so banning a user wholesale is no longer
// modeled — hide each unwanted post individually instead.
interface BanConfirmDialogProps {
/** The event ID to ban. */
eventId: string;
/** The event author's pubkey. */
targetPubkey: string;
/** The community `A` tag coordinate. */
communityATag: string;
/** Display name for the dialog description. */
displayName?: string;
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function BanConfirmDialog({
eventId,
targetPubkey,
communityATag,
open,
onOpenChange,
}: BanConfirmDialogProps) {
const queryClient = useQueryClient();
const { mutateAsync: publishEvent, isPending } = useNostrPublish();
const [reason, setReason] = useState('');
const handleSubmit = async () => {
try {
const tags: string[][] = [
['e', eventId, 'other'],
['p', targetPubkey, 'other'],
['A', communityATag],
['L', MODERATION_LABEL_NAMESPACE],
['l', MODERATION_BAN_LABEL, MODERATION_LABEL_NAMESPACE],
];
await publishEvent({
kind: REPORT_KIND,
content: reason.trim(),
tags,
});
// Invalidate community queries so the moderation overlay updates
// immediately (removes banned content without a page refresh).
await queryClient.invalidateQueries({ queryKey: ['community-members', communityATag] });
toast({ title: 'Post removed from organization' });
setReason('');
onOpenChange(false);
} catch {
toast({ title: 'Failed to remove post from organization', variant: 'destructive' });
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md rounded-2xl flex flex-col overflow-hidden">
<DialogTitle>Remove from organization</DialogTitle>
<DialogDescription className="text-sm text-muted-foreground">
This will hide the post from canonical organization views.
</DialogDescription>
<div className="space-y-2">
<Label htmlFor="ban-reason" className="text-sm font-medium">
Reason <span className="text-muted-foreground font-normal">(optional)</span>
</Label>
<Textarea
id="ban-reason"
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="Reason for this action..."
className="resize-none"
rows={2}
/>
</div>
<div className="flex gap-2 justify-end pt-2">
<Button
variant="ghost"
onClick={() => onOpenChange(false)}
disabled={isPending}
>
Cancel
</Button>
<Button
onClick={handleSubmit}
disabled={isPending}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isPending ? 'Submitting...' : 'Remove'}
</Button>
</div>
</DialogContent>
</Dialog>
);
}