Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/BannedUserCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { ExternalLink, FileText, ChevronDown, Copy, Check, Trash2 } from "lucide-react";
import { useState } from "react";
import type { NostrMetadata } from "@nostrify/nostrify";
import { getDivineProfileUrl } from "@/lib/constants";
import { getProfileUrl } from "@/lib/constants";

interface BannedUserCardProps {
pubkey: string;
Expand Down Expand Up @@ -83,7 +83,7 @@ export function BannedUserCard({ pubkey: rawPubkey, reason, onUnban }: BannedUse

const displayName = profile?.name || profile?.display_name || shortNpub;
const njumpUrl = `https://njump.me/${npub}`;
const profileUrl = getDivineProfileUrl(npub);
const profileUrl = getProfileUrl(npub, false);

const copyNpub = () => {
navigator.clipboard.writeText(npub);
Expand Down
9 changes: 6 additions & 3 deletions src/components/EventsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import { Textarea } from "@/components/ui/textarea";
import { EventDetail } from "@/components/EventDetail";
import { BulkDeleteByKind } from "@/components/BulkDeleteByKind";
import { getDivineProfileUrl } from "@/lib/constants";
import { getProfileUrl } from "@/lib/constants";
import { Checkbox } from "@/components/ui/checkbox";
import {
FileText,
Expand All @@ -46,6 +46,7 @@
UserPlus,
Loader2,
XCircle,
Globe,
} from "lucide-react";
import type { NostrEvent } from "@nostrify/nostrify";

Expand Down Expand Up @@ -121,11 +122,12 @@
const [moderationReason, setModerationReason] = useState('');

const metadata = author.data?.metadata;
const isFunnelcakeUser = author.data?.isFunnelcakeUser ?? false;
const displayName = metadata?.name || nip19.npubEncode(event.pubkey).slice(0, 12) + '...';
const profileImage = metadata?.picture;
const profileUrl = (() => {
try {
return getDivineProfileUrl(nip19.npubEncode(event.pubkey));
return getProfileUrl(nip19.npubEncode(event.pubkey), isFunnelcakeUser);
} catch {
return undefined;
}
Expand Down Expand Up @@ -179,8 +181,9 @@
)}
</a>
<div className="min-w-0">
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="hover:opacity-80" onClick={(e) => e.stopPropagation()}>
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 hover:opacity-80" onClick={(e) => e.stopPropagation()}>
<p className="font-medium text-sm truncate">{displayName}</p>
{!isFunnelcakeUser && <Globe className="h-3 w-3 text-purple-500 shrink-0" />}
</a>
<p className="text-xs text-muted-foreground font-mono">
{(() => {
Expand Down Expand Up @@ -532,7 +535,7 @@
}, [directEventLookup, searchMode]);

// Flatten all pages into a single events array
const events = eventsData?.pages.flat() ?? [];

Check warning on line 538 in src/components/EventsList.tsx

View workflow job for this annotation

GitHub Actions / test

The 'events' logical expression could make the dependencies of useMemo Hook (at line 619) change on every render. To fix this, wrap the initialization of 'events' in its own useMemo() Hook

// Auto-load more when scrolling near bottom of the scroll area
useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions src/components/ReportDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,7 @@ export function ReportDetail({ report, allReportsForTarget, allReports = [], onD
pubkey={context.reportedUser.pubkey}
stats={context.userStats}
isLoading={false}
isFunnelcakeUser={context.reportedUser.isFunnelcakeUser}
onDeleteEvent={(eventId) => {
deleteMutation.mutate({ eventId, reason: 'Deleted from report review' }, {
onSuccess: (deletedId) => {
Expand Down
32 changes: 25 additions & 7 deletions src/components/ReporterCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { FileText, Copy, Check, Flag } from "lucide-react";
import { FileText, Copy, Check, Flag, Globe } from "lucide-react";
import { useState } from "react";
import { nip19 } from "nostr-tools";
import type { NostrEvent } from "@nostrify/nostrify";
import { getDivineProfileUrl } from "@/lib/constants";
import { getProfileUrl } from "@/lib/constants";

interface ReporterCardProps {
pubkey: string;
Expand Down Expand Up @@ -43,9 +43,10 @@ export function ReporterCard({
npub = pubkey;
}

const isFunnelcakeUser = author.data?.isFunnelcakeUser ?? false;
const displayName = profile?.display_name || profile?.name || `${npub.slice(0, 12)}...`;
const npubDisplay = `${npub.slice(0, 12)}...${npub.slice(-6)}`;
const profileUrl = getDivineProfileUrl(npub);
const profileUrl = getProfileUrl(npub, isFunnelcakeUser);

const copyPubkey = async () => {
try {
Expand Down Expand Up @@ -82,8 +83,9 @@ export function ReporterCard({
</Avatar>
</a>
<div className="flex-1 min-w-0">
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="hover:opacity-80">
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 hover:opacity-80">
<p className="text-sm font-medium truncate">{displayName}</p>
{!isFunnelcakeUser && <Globe className="h-3 w-3 text-purple-500 shrink-0" />}
</a>
<p className="text-xs text-muted-foreground font-mono truncate">{npubDisplay}</p>
</div>
Expand Down Expand Up @@ -111,8 +113,9 @@ export function ReporterCard({

<div className="flex-1 min-w-0 space-y-1">
<div className="flex items-center gap-2">
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="hover:opacity-80">
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 hover:opacity-80">
<p className="font-medium truncate">{displayName}</p>
{!isFunnelcakeUser && <Globe className="h-3 w-3 text-purple-500 shrink-0" />}
</a>
{category && (
<Badge variant="outline" className="text-xs shrink-0">{category}</Badge>
Expand Down Expand Up @@ -295,7 +298,8 @@ export function ReporterInline({ pubkey, onViewProfile }: ReporterInlineProps) {
);
}

const profileUrl = getDivineProfileUrl(npub);
const isFunnelcakeUser = author.data?.isFunnelcakeUser ?? false;
const profileUrl = getProfileUrl(npub, isFunnelcakeUser);

return (
<div className="flex items-center gap-2 flex-wrap">
Expand All @@ -304,7 +308,7 @@ export function ReporterInline({ pubkey, onViewProfile }: ReporterInlineProps) {
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 hover:bg-muted rounded-full pr-2 transition-colors"
title="View reporter's profile on diVine"
title={isFunnelcakeUser ? "View profile on Divine" : "View profile on njump.me"}
>
<Avatar className="h-5 w-5">
<AvatarImage src={profile?.picture} />
Expand All @@ -319,6 +323,20 @@ export function ReporterInline({ pubkey, onViewProfile }: ReporterInlineProps) {
<Badge variant="outline" className={`text-xs ${trust.color}`}>
{trust.level}
</Badge>
{isFunnelcakeUser ? (
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="hover:opacity-80">
<Badge variant="outline" className="text-xs text-green-600 border-green-300 bg-green-50 gap-1">
Divine
</Badge>
</a>
) : (
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="hover:opacity-80">
<Badge variant="outline" className="text-xs text-purple-600 border-purple-300 bg-purple-50 gap-1">
<Globe className="h-3 w-3" />
Nostr
</Badge>
</a>
)}
{onViewProfile && (
<Button
type="button"
Expand Down
13 changes: 9 additions & 4 deletions src/components/ReporterInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
import { Flag, Star } from "lucide-react";
import { Flag, Star, Globe } from "lucide-react";
import { useAuthor } from "@/hooks/useAuthor";
import type { NostrMetadata } from "@nostrify/nostrify";
import { getDivineProfileUrl } from "@/lib/constants";
import { getProfileUrl } from "@/lib/constants";

interface ReporterInfoProps {
profile?: NostrMetadata;
Expand Down Expand Up @@ -44,9 +44,10 @@ export function ReportedBy({ pubkey, timestamp }: ReportedByProps) {
npub = pubkey;
}

const isFunnelcakeUser = author.data?.isFunnelcakeUser ?? false;
const displayName = metadata?.display_name || metadata?.name || `${npub.slice(0, 12)}...`;
const date = new Date(timestamp * 1000);
const profileUrl = getDivineProfileUrl(npub);
const profileUrl = getProfileUrl(npub, isFunnelcakeUser);

return (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
Expand All @@ -56,6 +57,7 @@ export function ReportedBy({ pubkey, timestamp }: ReportedByProps) {
<AvatarFallback className="text-xs">{displayName.slice(0, 2).toUpperCase()}</AvatarFallback>
</Avatar>
<span className={metadata?.name ? "font-medium" : "font-mono"}>{displayName}</span>
{!isFunnelcakeUser && <Globe className="h-3 w-3 text-purple-500 shrink-0" />}
</a>
<span>•</span>
<span>{date.toLocaleDateString()}</span>
Expand All @@ -64,6 +66,8 @@ export function ReportedBy({ pubkey, timestamp }: ReportedByProps) {
}

export function ReporterInfo({ profile, pubkey, reportCount, isLoading }: ReporterInfoProps) {
const author = useAuthor(pubkey);

if (isLoading) {
return (
<Card>
Expand Down Expand Up @@ -92,9 +96,10 @@ export function ReporterInfo({ profile, pubkey, reportCount, isLoading }: Report
npub = pubkey;
}

const isFunnelcakeUser = author.data?.isFunnelcakeUser ?? false;
const displayName = profile?.display_name || profile?.name || `${npub.slice(0, 12)}...`;
const trust = getTrustLevel(reportCount);
const profileUrl = getDivineProfileUrl(npub);
const profileUrl = getProfileUrl(npub, isFunnelcakeUser);

return (
<Card>
Expand Down
16 changes: 7 additions & 9 deletions src/components/ThreadContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from "lucide-react";
import { useState } from "react";
import type { NostrEvent } from "@nostrify/nostrify";
import { getDivineProfileUrl } from "@/lib/constants";
import { getProfileUrl, getPublicEventUrl } from "@/lib/constants";
import type { FetchSource } from "@/hooks/useThread";

interface ThreadContextProps {
Expand Down Expand Up @@ -44,10 +44,6 @@ interface ThreadContextProps {
isRechecking?: boolean;
}

// NIP-71 video kinds. Divine primarily uses 34235 (addressable video),
// but we check all video kinds to handle edge cases.
const VIDEO_KINDS = [21, 22, 34235, 34236];

function PostCard({
event,
isReported = false,
Expand All @@ -68,12 +64,13 @@ function PostCard({
return event.pubkey.slice(0, 8) + '...';
}
})();
const isFunnelcakeUser = author.data?.isFunnelcakeUser ?? false;
const displayName = author.data?.metadata?.display_name || author.data?.metadata?.name || npubFallback;
const avatar = author.data?.metadata?.picture;
const date = new Date(event.created_at * 1000);
const profileUrl = (() => {
try {
return getDivineProfileUrl(nip19.npubEncode(event.pubkey));
return getProfileUrl(nip19.npubEncode(event.pubkey), isFunnelcakeUser);
} catch {
return undefined;
}
Expand All @@ -95,8 +92,9 @@ function PostCard({
</a>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="hover:opacity-80">
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 hover:opacity-80">
<span className="font-medium text-sm">{displayName}</span>
{!isFunnelcakeUser && <Globe className="h-3 w-3 text-purple-500 shrink-0" />}
</a>
<span className="text-xs text-muted-foreground">
{date.toLocaleDateString()} {date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
Expand Down Expand Up @@ -136,12 +134,12 @@ function PostCard({
</div>
{!isReported && (
<a
href={`https://divine.video/${nip19.neventEncode({ id: event.id })}`}
href={getPublicEventUrl(nip19.neventEncode({ id: event.id }), apiUrl)}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-muted-foreground hover:text-foreground inline-flex items-center gap-1 mt-1"
>
{VIDEO_KINDS.includes(event.kind) ? 'View video' : 'View'} on Divine
Open event
<ExternalLink className="h-3 w-3" />
</a>
)}
Expand Down
10 changes: 6 additions & 4 deletions src/components/ThreadModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
import { useAuthor } from "@/hooks/useAuthor";
import { useAppContext } from "@/hooks/useAppContext";
import { MessageSquare } from "lucide-react";
import { MessageSquare, Globe } from "lucide-react";
import type { NostrEvent } from "@nostrify/nostrify";
import { getDivineProfileUrl } from "@/lib/constants";
import { getProfileUrl } from "@/lib/constants";

interface ThreadModalProps {
eventId: string;
Expand Down Expand Up @@ -72,13 +72,14 @@ function ThreadPost({
return node.event.pubkey.slice(0, 8) + '...';
}
})();
const isFunnelcakeUser = author.data?.isFunnelcakeUser ?? false;
const displayName = author.data?.metadata?.display_name || author.data?.metadata?.name || npubFallback;
const avatar = author.data?.metadata?.picture;
const date = new Date(node.event.created_at * 1000);
const isHighlighted = node.event.id === highlightId;
const profileUrl = (() => {
try {
return getDivineProfileUrl(nip19.npubEncode(node.event.pubkey));
return getProfileUrl(nip19.npubEncode(node.event.pubkey), isFunnelcakeUser);
} catch {
return undefined;
}
Expand All @@ -104,8 +105,9 @@ function ThreadPost({
</a>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="hover:opacity-80">
<a href={profileUrl} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 hover:opacity-80">
<span className="text-sm font-medium">{displayName}</span>
{!isFunnelcakeUser && <Globe className="h-3 w-3 text-purple-500 shrink-0" />}
</a>
<span className="text-xs text-muted-foreground">
{date.toLocaleString()}
Expand Down
18 changes: 12 additions & 6 deletions src/components/UserIdentifier.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { useState } from "react";
import { nip19 } from "nostr-tools";
import { Copy, Check } from "lucide-react";
import { Copy, Check, Globe } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
Expand All @@ -14,7 +14,7 @@ import {
} from "@/components/ui/tooltip";
import { useAuthor } from "@/hooks/useAuthor";
import { cn } from "@/lib/utils";
import { getDivineProfileUrl } from "@/lib/constants";
import { getProfileUrl } from "@/lib/constants";

interface UserIdentifierProps {
pubkey: string;
Expand Down Expand Up @@ -49,6 +49,7 @@ export function UserIdentifier({
}

const metadata = author.data?.metadata;
const isFunnelcakeUser = author.data?.isFunnelcakeUser ?? false;
const displayName = metadata?.display_name || metadata?.name;
const nip05 = metadata?.nip05;
const picture = metadata?.picture;
Expand Down Expand Up @@ -83,7 +84,7 @@ export function UserIdentifier({
// Full npub -- CSS truncation handles overflow based on available space
const displayNpub = npub;

const profileUrl = getDivineProfileUrl(npub);
const profileUrl = getProfileUrl(npub, isFunnelcakeUser);

if (variant === "compact") {
const content = (
Expand Down Expand Up @@ -117,6 +118,7 @@ export function UserIdentifier({
)}
>
{content}
{!isFunnelcakeUser && <Globe className="h-3 w-3 text-purple-500 shrink-0" />}
</a>
) : (
<span
Expand Down Expand Up @@ -184,9 +186,10 @@ export function UserIdentifier({
href={profileUrl}
target="_blank"
rel="noopener noreferrer"
className="hover:opacity-80"
className="inline-flex items-center gap-1 hover:opacity-80"
>
{nameElement}
{!isFunnelcakeUser && <Globe className="h-3 w-3 text-purple-500 shrink-0" />}
</a>
) : (
nameElement
Expand Down Expand Up @@ -267,6 +270,7 @@ export function UserIdentifier({
className="inline-flex items-center gap-1 hover:opacity-80"
>
{inlineContent}
{!isFunnelcakeUser && <Globe className="h-3 w-3 text-purple-500 shrink-0" />}
</a>
) : (
inlineContent
Expand Down Expand Up @@ -305,6 +309,7 @@ export function UserDisplayName({
}: UserDisplayNameProps) {
const author = useAuthor(pubkey);
const metadata = author.data?.metadata;
const isFunnelcakeUser = author.data?.isFunnelcakeUser ?? false;
const displayName = metadata?.display_name || metadata?.name;

let npub = "";
Expand All @@ -325,12 +330,13 @@ export function UserDisplayName({
if (linkToProfile) {
return (
<a
href={getDivineProfileUrl(npub)}
href={getProfileUrl(npub, isFunnelcakeUser)}
target="_blank"
rel="noopener noreferrer"
className="block min-w-0 overflow-hidden hover:opacity-80"
className="inline-flex items-center gap-1 min-w-0 overflow-hidden hover:opacity-80"
>
{content}
{!isFunnelcakeUser && <Globe className="h-3 w-3 text-purple-500 shrink-0" />}
</a>
);
}
Expand Down
Loading
Loading