diff --git a/src/components/AddMembersDialog.tsx b/src/components/AddMembersDialog.tsx index b171be24..f8aa0b42 100644 --- a/src/components/AddMembersDialog.tsx +++ b/src/components/AddMembersDialog.tsx @@ -9,7 +9,6 @@ import { Search, Loader2, Plus, UserPlus, Check } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Button } from '@/components/ui/button'; import { useSearchProfiles } from '@/hooks/useSearchProfiles'; import { useUserLists } from '@/hooks/useUserLists'; @@ -156,7 +155,7 @@ export function AddMembersDialog({ open, onOpenChange, listId, listPubkeys }: Ad onClick={() => handleAdd(profile)} onMouseEnter={() => setSelectedIdx(idx)} > - + {name[0]?.toUpperCase()} diff --git a/src/components/AudioVisualizer.tsx b/src/components/AudioVisualizer.tsx index beda5e50..6a34e770 100644 --- a/src/components/AudioVisualizer.tsx +++ b/src/components/AudioVisualizer.tsx @@ -1,7 +1,6 @@ import { useRef, useState, useEffect, useCallback } from 'react'; import { Play, Pause, Volume1, Volume2, VolumeX } from 'lucide-react'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import type { AvatarShape } from '@/lib/avatarShape'; import { cn } from '@/lib/utils'; import { usePlayerControls } from '@/hooks/usePlayerControls'; import { formatTime } from '@/lib/formatTime'; @@ -13,8 +12,6 @@ interface AudioVisualizerProps { avatarUrl?: string; /** Fallback display letter for the avatar */ avatarFallback?: string; - /** Avatar mask shape, forwarded from the author's profile metadata */ - avatarShape?: AvatarShape; className?: string; } @@ -29,7 +26,6 @@ export function AudioVisualizer({ mime, avatarUrl, avatarFallback = '?', - avatarShape, className, }: AudioVisualizerProps) { const audioRef = useRef(null); @@ -266,7 +262,7 @@ export function AudioVisualizer({ : 'ring-border', )} > - + {avatarFallback} diff --git a/src/components/BadgeDetailContent.tsx b/src/components/BadgeDetailContent.tsx index 37abe499..210ac77d 100644 --- a/src/components/BadgeDetailContent.tsx +++ b/src/components/BadgeDetailContent.tsx @@ -8,7 +8,6 @@ import { useQuery, useInfiniteQuery } from '@tanstack/react-query'; import { useInView } from 'react-intersection-observer'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { ComposeBox } from '@/components/ComposeBox'; @@ -55,7 +54,6 @@ export function BadgeDetailContent({ event }: { event: NostrEvent }) { const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name || genUserName(event.pubkey); const npub = useMemo(() => nip19.npubEncode(event.pubkey), [event.pubkey]); @@ -141,7 +139,7 @@ export function BadgeDetailContent({ event }: { event: NostrEvent }) { {/* Issuer row */}
- + {displayName[0]?.toUpperCase()} @@ -452,7 +450,6 @@ function CommentsTab({ event, orderedReplies, commentsLoading }: { function AwardeeCard({ pubkey, metadata }: { pubkey: string; metadata?: NostrMetadata }) { const displayName = metadata?.name || metadata?.display_name || genUserName(pubkey); const about = metadata?.about; - const avatarShape = getAvatarShape(metadata); const profileUrl = useProfileUrl(pubkey, metadata); return ( @@ -460,7 +457,7 @@ function AwardeeCard({ pubkey, metadata }: { pubkey: string; metadata?: NostrMet to={profileUrl} className="flex items-center gap-3 px-4 py-3 hover:bg-secondary/30 transition-colors" > - + {displayName[0]?.toUpperCase()} diff --git a/src/components/BookFeedItem.tsx b/src/components/BookFeedItem.tsx index 21b3b37b..13d955bf 100644 --- a/src/components/BookFeedItem.tsx +++ b/src/components/BookFeedItem.tsx @@ -4,7 +4,6 @@ import { BookOpen, MessageCircle, MessageSquare, MoreHorizontal, Star, Zap, Aler import { nip19 } from 'nostr-tools'; import { RepostIcon } from '@/components/icons/RepostIcon'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; @@ -53,7 +52,6 @@ export function BookFeedItem({ event, className }: BookFeedItemProps) { const { user } = useCurrentUser(); const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = getDisplayName(metadata, event.pubkey); const profileUrl = useProfileUrl(event.pubkey, metadata); const { data: stats } = useEventStats(event.id, event); @@ -126,7 +124,7 @@ export function BookFeedItem({ event, className }: BookFeedItemProps) { ) : ( e.stopPropagation()}> - + {displayName[0]?.toUpperCase()} diff --git a/src/components/CalendarEventDetailPage.tsx b/src/components/CalendarEventDetailPage.tsx index 506055d8..34e239ab 100644 --- a/src/components/CalendarEventDetailPage.tsx +++ b/src/components/CalendarEventDetailPage.tsx @@ -18,7 +18,6 @@ import { import type { NostrEvent, NostrMetadata } from '@nostrify/nostrify'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Separator } from '@/components/ui/separator'; @@ -122,7 +121,6 @@ function roleSort(a: string, b: string): number { function PersonRow({ pubkey, label, size = 'md' }: { pubkey: string; label?: string; size?: 'sm' | 'md' }) { const { data } = useAuthor(pubkey); const metadata: NostrMetadata | undefined = data?.metadata; - const avatarShape = getAvatarShape(metadata); const name = metadata?.display_name || metadata?.name || genUserName(pubkey); const profileUrl = useProfileUrl(pubkey, metadata); const avatarCls = size === 'sm' ? 'size-8' : 'size-11'; @@ -130,7 +128,7 @@ function PersonRow({ pubkey, label, size = 'md' }: { pubkey: string; label?: str return ( - + {name.charAt(0).toUpperCase()} diff --git a/src/components/CommentsSheet.tsx b/src/components/CommentsSheet.tsx index 6111c34b..a6379d18 100644 --- a/src/components/CommentsSheet.tsx +++ b/src/components/CommentsSheet.tsx @@ -15,7 +15,6 @@ import { useProfileUrl } from '@/hooks/useProfileUrl'; import { getDisplayName } from '@/lib/getDisplayName'; import { timeAgo } from '@/lib/timeAgo'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Skeleton } from '@/components/ui/skeleton'; import { ProfileHoverCard } from '@/components/ProfileHoverCard'; import { ComposeBox } from '@/components/ComposeBox'; @@ -64,7 +63,6 @@ function useEventComments(event: NostrEvent | undefined) { function CommentRow({ event }: { event: NostrEvent }) { const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = getDisplayName(metadata, event.pubkey); const profileUrl = useProfileUrl(event.pubkey, metadata); @@ -75,7 +73,7 @@ function CommentRow({ event }: { event: NostrEvent }) { {author.isLoading ? ( ) : ( - + {displayName[0]?.toUpperCase()} diff --git a/src/components/CommunityCard.tsx b/src/components/CommunityCard.tsx index 5b38b620..bd7c0668 100644 --- a/src/components/CommunityCard.tsx +++ b/src/components/CommunityCard.tsx @@ -9,7 +9,6 @@ import { Badge } from '@/components/ui/badge'; import { useAuthor } from '@/hooks/useAuthor'; import { useProfileUrl } from '@/hooks/useProfileUrl'; import { genUserName } from '@/lib/genUserName'; -import { getAvatarShape } from '@/lib/avatarShape'; import { parseCommunityEvent, COMMUNITY_DEFINITION_KIND } from '@/lib/communityUtils'; import { cn } from '@/lib/utils'; @@ -30,7 +29,6 @@ export function CommunityCard({ event, isFounded, className }: CommunityCardProp const founderAuthor = useAuthor(event.pubkey); const founderMeta = founderAuthor.data?.metadata; - const founderAvatarShape = getAvatarShape(founderMeta); const founderName = founderMeta?.display_name || founderMeta?.name || genUserName(event.pubkey); const founderProfileUrl = useProfileUrl(event.pubkey, founderMeta); @@ -97,7 +95,7 @@ export function CommunityCard({ event, isFounded, className }: CommunityCardProp onClick={(e) => e.stopPropagation()} className="flex items-center gap-1.5 min-w-0" > - + {founderName.charAt(0).toUpperCase()} diff --git a/src/components/CommunityContent.tsx b/src/components/CommunityContent.tsx index b2b92709..ae8c8d39 100644 --- a/src/components/CommunityContent.tsx +++ b/src/components/CommunityContent.tsx @@ -5,7 +5,6 @@ import { nip19 } from 'nostr-tools'; import type { NostrEvent } from '@nostrify/nostrify'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Button } from '@/components/ui/button'; import { useAuthor } from '@/hooks/useAuthor'; import { useProfileUrl } from '@/hooks/useProfileUrl'; @@ -52,7 +51,6 @@ export function CommunityContent({ event }: { event: NostrEvent }) { // Owner const ownerAuthor = useAuthor(event.pubkey); const ownerMetadata = ownerAuthor.data?.metadata; - const ownerAvatarShape = getAvatarShape(ownerMetadata); const ownerName = ownerMetadata?.display_name || ownerMetadata?.name || genUserName(event.pubkey); const ownerProfileUrl = useProfileUrl(event.pubkey, ownerMetadata); @@ -139,7 +137,7 @@ export function CommunityContent({ event }: { event: NostrEvent }) {

Created by

- + {ownerName.charAt(0).toUpperCase()} diff --git a/src/components/CommunityDetailPage.tsx b/src/components/CommunityDetailPage.tsx index 26a16e80..42c4e1a7 100644 --- a/src/components/CommunityDetailPage.tsx +++ b/src/components/CommunityDetailPage.tsx @@ -14,7 +14,6 @@ import { import type { NostrEvent, NostrMetadata } from '@nostrify/nostrify'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; @@ -47,7 +46,6 @@ import { cn } from '@/lib/utils'; function PersonRow({ pubkey, label, size = 'md', onBan }: { pubkey: string; label?: string; size?: 'sm' | 'md'; onBan?: () => void }) { const { data } = useAuthor(pubkey); const metadata: NostrMetadata | undefined = data?.metadata; - const avatarShape = getAvatarShape(metadata); const name = metadata?.display_name || metadata?.name || genUserName(pubkey); const profileUrl = useProfileUrl(pubkey, metadata); const avatarCls = size === 'sm' ? 'size-8' : 'size-10'; @@ -56,7 +54,7 @@ function PersonRow({ pubkey, label, size = 'md', onBan }: { pubkey: string; labe return (
- + {name.charAt(0).toUpperCase()} diff --git a/src/components/ComposeBox.tsx b/src/components/ComposeBox.tsx index d4cdfc66..57ce5320 100644 --- a/src/components/ComposeBox.tsx +++ b/src/components/ComposeBox.tsx @@ -6,7 +6,6 @@ import { encode as blurhashEncode } from 'blurhash'; import type { NostrEvent } from '@nostrify/nostrify'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { Input } from '@/components/ui/input'; @@ -178,7 +177,6 @@ export function ComposeBox({ initialMode = 'post', }: ComposeBoxProps) { const { user, metadata, isLoading: isProfileLoading } = useCurrentUser(); - const avatarShape = getAvatarShape(metadata); const userProfileUrl = useProfileUrl(user?.pubkey ?? '', metadata); const { mutateAsync: createEvent, isPending, isPending: isPollPending } = useNostrPublish(); const { mutateAsync: postComment, isPending: isCommentPending } = usePostComment(); @@ -1082,7 +1080,7 @@ export function ComposeBox({ ) : ( - + {(metadata?.display_name || metadata?.name || genUserName(user?.pubkey))[0]?.toUpperCase() ?? '?'} diff --git a/src/components/ContentSettings.tsx b/src/components/ContentSettings.tsx index e3e666dd..899a4d33 100644 --- a/src/components/ContentSettings.tsx +++ b/src/components/ContentSettings.tsx @@ -12,7 +12,6 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Skeleton } from '@/components/ui/skeleton'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Link } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { useToast } from '@/hooks/useToast'; @@ -1144,7 +1143,6 @@ export function MuteSettingsInternals() { function MutedUserProfile({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name ?? genUserName(pubkey); if (author.isLoading) { @@ -1158,7 +1156,7 @@ function MutedUserProfile({ pubkey }: { pubkey: string }) { return (
- + {displayName[0]?.toUpperCase() ?? '?'} diff --git a/src/components/EditProfileForm.tsx b/src/components/EditProfileForm.tsx index 0926f2ac..334f23c0 100644 --- a/src/components/EditProfileForm.tsx +++ b/src/components/EditProfileForm.tsx @@ -31,15 +31,13 @@ import { import { z } from 'zod'; import { IntroImage } from '@/components/IntroImage'; import { ImageCropDialog } from '@/components/ImageCropDialog'; -import { isValidAvatarShape } from '@/lib/avatarShape'; -// Extended form schema that includes custom fields and avatar shape +// Extended form schema that includes custom fields const formSchema = n.metadata().extend({ fields: z.array(z.object({ label: z.string(), value: z.string(), })).optional(), - shape: z.string().optional(), }); type ExtendedMetadata = z.infer; @@ -83,16 +81,6 @@ export const EditProfileForm: React.FC = ({ onValuesChange return []; }; - // Parse existing shape from raw event content - const parseShape = (): string => { - if (!event) return ''; - try { - const parsed = JSON.parse(event.content); - if (isValidAvatarShape(parsed.shape)) return parsed.shape; - } catch { /* ignore */ } - return ''; - }; - // Initialize the form with default values const form = useForm({ resolver: zodResolver(formSchema), @@ -106,7 +94,6 @@ export const EditProfileForm: React.FC = ({ onValuesChange lud16: '', bot: false, fields: [], - shape: '', }, }); @@ -127,7 +114,6 @@ export const EditProfileForm: React.FC = ({ onValuesChange lud16: metadata.lud16 || '', bot: metadata.bot || false, fields: existingFields, - shape: parseShape(), }); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -147,7 +133,6 @@ export const EditProfileForm: React.FC = ({ onValuesChange nip05: v.nip05, lud16: v.lud16, bot: v.bot, - shape: v.shape, } as Partial); }, [form, onValuesChange]); @@ -213,18 +198,13 @@ export const EditProfileForm: React.FC = ({ onValuesChange } try { - // Extract fields, shape, and other metadata - const { fields: customFields, shape, ...standardMetadata } = values; + const { fields: customFields, ...standardMetadata } = values; // Combine existing metadata with new values const data: Record = { ...metadata, ...standardMetadata }; - // Add shape only if set (an emoji string) - if (shape && isValidAvatarShape(shape)) { - data.shape = shape; - } else { - delete data.shape; - } + // Strip any legacy avatar shape data from old Ditto-style profiles + delete data.shape; // Clean up empty values in standard metadata for (const key in data) { diff --git a/src/components/EmbeddedCardShell.tsx b/src/components/EmbeddedCardShell.tsx index 1f2ba4a8..66160e36 100644 --- a/src/components/EmbeddedCardShell.tsx +++ b/src/components/EmbeddedCardShell.tsx @@ -1,7 +1,6 @@ import { type ReactNode } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Skeleton } from '@/components/ui/skeleton'; import { EmojifiedText } from '@/components/CustomEmoji'; import { ProfileHoverCard } from '@/components/ProfileHoverCard'; @@ -43,7 +42,6 @@ export function EmbeddedCardShell({ const navigate = useNavigate(); const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name || genUserName(pubkey); const profileUrl = useProfileUrl(pubkey, metadata); @@ -84,7 +82,7 @@ export function EmbeddedCardShell({ className="shrink-0" onClick={(e) => e.stopPropagation()} > - + {displayName[0]?.toUpperCase()} diff --git a/src/components/EmbeddedNaddr.tsx b/src/components/EmbeddedNaddr.tsx index 90d052ba..5e389fa0 100644 --- a/src/components/EmbeddedNaddr.tsx +++ b/src/components/EmbeddedNaddr.tsx @@ -7,7 +7,6 @@ import { Award, Image, MessageSquareOff } from 'lucide-react'; import type { NostrEvent } from '@nostrify/nostrify'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Skeleton } from '@/components/ui/skeleton'; import { EmojifiedText } from '@/components/CustomEmoji'; import { EmbeddedCardShell } from '@/components/EmbeddedCardShell'; @@ -194,7 +193,6 @@ export function EmbeddedProfileBadgesCard({ event, className }: { event: NostrEv const navigate = useNavigate(); const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name || genUserName(event.pubkey); const profileUrl = useProfileUrl(event.pubkey, metadata); @@ -272,7 +270,7 @@ export function EmbeddedProfileBadgesCard({ event, className }: { event: NostrEv className="shrink-0" onClick={(e) => e.stopPropagation()} > - + {displayName[0]?.toUpperCase()} diff --git a/src/components/EmbeddedNote.tsx b/src/components/EmbeddedNote.tsx index 13e06ace..e3d6ed21 100644 --- a/src/components/EmbeddedNote.tsx +++ b/src/components/EmbeddedNote.tsx @@ -18,7 +18,6 @@ import { useAuthor } from '@/hooks/useAuthor'; import { useProfileUrl } from '@/hooks/useProfileUrl'; import { isProfileBadgesKind } from '@/lib/badgeUtils'; import { extractZapAmount, extractZapSender, extractZapMessage } from '@/hooks/useEventInteractions'; -import { getAvatarShape } from '@/lib/avatarShape'; import { genUserName } from '@/lib/genUserName'; import { formatNumber } from '@/lib/formatNumber'; import { timeAgo } from '@/lib/timeAgo'; @@ -101,7 +100,6 @@ function EmbeddedZapCard({ event, className, disableHoverCards }: { event: Nostr const sender = useAuthor(senderPubkey || undefined); const senderMeta = sender.data?.metadata; const senderName = senderMeta?.name || (senderPubkey ? genUserName(senderPubkey) : 'Someone'); - const senderShape = getAvatarShape(senderMeta); const senderProfileUrl = useProfileUrl(senderPubkey, senderMeta); return ( @@ -135,7 +133,7 @@ function EmbeddedZapCard({ event, className, disableHoverCards }: { event: Nostr {senderPubkey && ( e.stopPropagation()}> - + {senderName[0]?.toUpperCase()} diff --git a/src/components/EncryptedLetterContent.tsx b/src/components/EncryptedLetterContent.tsx index e36568b5..131af614 100644 --- a/src/components/EncryptedLetterContent.tsx +++ b/src/components/EncryptedLetterContent.tsx @@ -27,7 +27,6 @@ import { ProfileHoverCard } from '@/components/ProfileHoverCard'; import { useAuthor } from '@/hooks/useAuthor'; import { useProfileUrl } from '@/hooks/useProfileUrl'; import { useCardTilt } from '@/hooks/useCardTilt'; -import { getAvatarShape } from '@/lib/avatarShape'; import { getDisplayName } from '@/lib/getDisplayName'; import { genUserName } from '@/lib/genUserName'; import { Skeleton } from '@/components/ui/skeleton'; @@ -107,13 +106,12 @@ function SealAvatar({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; const displayName = getDisplayName(metadata, pubkey); - const avatarShape = getAvatarShape(metadata); const profileUrl = useProfileUrl(pubkey, metadata); return ( e.stopPropagation()}> - + {displayName[0]?.toUpperCase()} @@ -456,7 +454,6 @@ export function EncryptedLetterCompact({ event, className }: EncryptedLetterComp const navigate = useNavigate(); const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name || genUserName(event.pubkey); const recipientPubkey = event.tags.find(([n]) => n === 'p')?.[1]; const recipientAuthor = useAuthor(recipientPubkey ?? ''); @@ -507,7 +504,7 @@ export function EncryptedLetterCompact({ event, className }: EncryptedLetterComp className="shrink-0" onClick={(e) => e.stopPropagation()} > - + {displayName[0]?.toUpperCase()} diff --git a/src/components/EncryptedMessageContent.tsx b/src/components/EncryptedMessageContent.tsx index eb916469..ba69a099 100644 --- a/src/components/EncryptedMessageContent.tsx +++ b/src/components/EncryptedMessageContent.tsx @@ -8,7 +8,6 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { ProfileHoverCard } from '@/components/ProfileHoverCard'; import { useAuthor } from '@/hooks/useAuthor'; import { useProfileUrl } from '@/hooks/useProfileUrl'; -import { getAvatarShape } from '@/lib/avatarShape'; import { getDisplayName } from '@/lib/getDisplayName'; import { genUserName } from '@/lib/genUserName'; import { Skeleton } from '@/components/ui/skeleton'; @@ -26,7 +25,6 @@ interface EncryptedMessageContentProps { function Participant({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = getDisplayName(metadata, pubkey); const profileUrl = useProfileUrl(pubkey, metadata); @@ -43,7 +41,7 @@ function Participant({ pubkey }: { pubkey: string }) {
e.stopPropagation()}> - + {displayName[0]?.toUpperCase()} @@ -129,7 +127,6 @@ export function EncryptedMessageCompact({ event, className }: EncryptedMessageCo const navigate = useNavigate(); const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name || genUserName(event.pubkey); const recipientPubkey = event.tags.find(([n]) => n === 'p')?.[1]; const recipientAuthor = useAuthor(recipientPubkey ?? ''); @@ -181,7 +178,7 @@ export function EncryptedMessageCompact({ event, className }: EncryptedMessageCo className="shrink-0" onClick={(e) => e.stopPropagation()} > - + {displayName[0]?.toUpperCase()} diff --git a/src/components/ExternalContentHeader.tsx b/src/components/ExternalContentHeader.tsx index 85f66d89..ab493327 100644 --- a/src/components/ExternalContentHeader.tsx +++ b/src/components/ExternalContentHeader.tsx @@ -3,7 +3,6 @@ import { Link } from 'react-router-dom'; import { BookOpen, Droplets, ExternalLink, FileText, Globe, MapPin, MessageCircle, Package, Play, Repeat2, Share2, User, Users, Wind, Zap } from 'lucide-react'; import { nip19 } from 'nostr-tools'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Skeleton } from '@/components/ui/skeleton'; import { ExternalFavicon } from '@/components/ExternalFavicon'; import { ExternalReactionButton } from '@/components/ExternalReactionButton'; @@ -995,7 +994,6 @@ export function CommunityPreview({ addr }: { addr: { kind: number; pubkey: strin export function ProfilePreview({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name ?? genUserName(pubkey); const profileUrl = useProfileUrl(pubkey, metadata); @@ -1018,7 +1016,7 @@ export function ProfilePreview({ pubkey }: { pubkey: string }) { to={profileUrl} className="flex items-center gap-3 px-4 py-3 border-b border-border hover:bg-secondary/30 transition-colors" > - + diff --git a/src/components/FabButton.tsx b/src/components/FabButton.tsx index 772cd6b7..416d42ce 100644 --- a/src/components/FabButton.tsx +++ b/src/components/FabButton.tsx @@ -1,7 +1,3 @@ -import { useMemo } from 'react'; -import { useCurrentUser } from '@/hooks/useCurrentUser'; -import { getAvatarShape, getEmojiMaskUrl } from '@/lib/avatarShape'; - interface FabButtonProps { onClick: () => void; icon: React.ReactNode; @@ -11,29 +7,9 @@ interface FabButtonProps { } /** - * Reusable FAB that inherits the current user's avatar shape (emoji mask or - * circle fallback), matching the FloatingComposeButton style exactly. + * Reusable circular FAB. */ export function FabButton({ onClick, icon, disabled, className = '', title }: FabButtonProps) { - const { metadata } = useCurrentUser(); - const avatarShape = getAvatarShape(metadata); - - const shapeMaskStyle = useMemo(() => { - if (!avatarShape) return undefined; - const maskUrl = getEmojiMaskUrl(avatarShape); - if (!maskUrl) return undefined; - return { - WebkitMaskImage: `url(${maskUrl})`, - maskImage: `url(${maskUrl})`, - WebkitMaskSize: 'contain', - maskSize: 'contain' as string, - WebkitMaskRepeat: 'no-repeat', - maskRepeat: 'no-repeat' as string, - WebkitMaskPosition: 'center', - maskPosition: 'center' as string, - }; - }, [avatarShape]); - return ( - - - onPickImage?.('picture')}> - - Change avatar - - setEmojiPickerOpen(true)}> - - Set avatar shape - + + + +
+
)} - - - + +
+ + onPickImage?.('picture')}> + + Change avatar + + {metadata.picture && ( + onRemoveAvatar?.()} className="text-destructive focus:text-destructive"> + + Remove avatar + + )} + +
) : ( -
- +
+ {initial} diff --git a/src/components/ProfileHoverCard.tsx b/src/components/ProfileHoverCard.tsx index fdb13bd1..2a02db49 100644 --- a/src/components/ProfileHoverCard.tsx +++ b/src/components/ProfileHoverCard.tsx @@ -4,7 +4,6 @@ import { nip19 } from 'nostr-tools'; import { useQueryClient } from '@tanstack/react-query'; import { HoverCard, HoverCardTrigger, HoverCardContent } from '@/components/ui/hover-card'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { ExternalFavicon } from '@/components/ExternalFavicon'; import { EmojifiedText } from '@/components/CustomEmoji'; import { BioContent } from '@/components/BioContent'; @@ -35,7 +34,6 @@ function ProfileHoverCardBody({ pubkey }: { pubkey: string }) { const queryClient = useQueryClient(); const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name ?? genUserName(pubkey); const profileUrl = useProfileUrl(pubkey, metadata); const nip05 = metadata?.nip05; @@ -74,7 +72,7 @@ function ProfileHoverCardBody({ pubkey }: { pubkey: string }) { {/* Avatar overlapping the banner */}
e.stopPropagation()}> - + {displayName[0]?.toUpperCase()} diff --git a/src/components/ProfileRecoveryDialog.tsx b/src/components/ProfileRecoveryDialog.tsx index cd38b3f4..eca8b8b9 100644 --- a/src/components/ProfileRecoveryDialog.tsx +++ b/src/components/ProfileRecoveryDialog.tsx @@ -7,7 +7,6 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { Separator } from '@/components/ui/separator'; @@ -86,8 +85,6 @@ function ProfileSnapshotCard({ }) { const metadata = useMemo(() => parseMetadata(event.content), [event.content]); const displayName = metadata?.display_name || metadata?.name || genUserName(event.pubkey); - const avatarShape = getAvatarShape(metadata); - return (
{/* Avatar */} - + {metadata?.picture ? ( ) : null} diff --git a/src/components/ProfileSearchDropdown.tsx b/src/components/ProfileSearchDropdown.tsx index fcdd26d9..3f940fcf 100644 --- a/src/components/ProfileSearchDropdown.tsx +++ b/src/components/ProfileSearchDropdown.tsx @@ -4,7 +4,6 @@ import { Search, UserRoundCheck, MessageSquare, FileText, Hash, Archive } from ' import { nip19 } from 'nostr-tools'; import { Input } from '@/components/ui/input'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { EmojifiedText } from '@/components/CustomEmoji'; import { useSearchProfiles, type SearchProfile } from '@/hooks/useSearchProfiles'; import { genUserName } from '@/lib/genUserName'; @@ -567,7 +566,7 @@ function Nip05IdentifierItem({ onClick={() => onNavigate(`/${identifier}`)} onMouseDown={(e) => e.preventDefault()} > - + {displayName[0]?.toUpperCase() || '?'} @@ -611,7 +610,7 @@ function PubkeyIdentifierItem({ onClick={() => onNavigate(`/${raw}`)} onMouseDown={(e) => e.preventDefault()} > - + {displayName[0]?.toUpperCase() || '?'} @@ -905,7 +904,7 @@ function ProfileItem({ onMouseDown={(e) => e.preventDefault()} // Prevent input blur >
- + {displayName[0]?.toUpperCase() || '?'} diff --git a/src/components/RSVPAvatars.tsx b/src/components/RSVPAvatars.tsx index 1e8757d0..06e5f73b 100644 --- a/src/components/RSVPAvatars.tsx +++ b/src/components/RSVPAvatars.tsx @@ -1,7 +1,6 @@ import type { NostrMetadata } from '@nostrify/nostrify'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { useAuthor } from '@/hooks/useAuthor'; import { genUserName } from '@/lib/genUserName'; @@ -23,15 +22,13 @@ const spacingClasses: Record = { function RSVPAvatar({ pubkey, size = 'sm' }: { pubkey: string; size?: AvatarSize }) { const { data } = useAuthor(pubkey); const metadata: NostrMetadata | undefined = data?.metadata; - const avatarShape = getAvatarShape(metadata); - const displayName = metadata?.display_name || metadata?.name || genUserName(pubkey); const initial = displayName.charAt(0).toUpperCase(); return ( - + {initial} diff --git a/src/components/RightSidebar.tsx b/src/components/RightSidebar.tsx index 3a93921c..b6e92e8e 100644 --- a/src/components/RightSidebar.tsx +++ b/src/components/RightSidebar.tsx @@ -3,7 +3,6 @@ import { LinkFooter } from '@/components/LinkFooter'; import { useOpenPost } from '@/hooks/useOpenPost'; import { X } from 'lucide-react'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Skeleton } from '@/components/ui/skeleton'; import { EmojifiedText } from '@/components/CustomEmoji'; import { useTrendingTags, useLatestAccounts, useSortedPosts, useTagSparklines } from '@/hooks/useTrending'; @@ -187,7 +186,6 @@ export function RightSidebar() { function HotPostCard({ event }: { event: NostrEvent }) { const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name || genUserName(event.pubkey); const encodedId = useMemo(() => nip19.neventEncode({ id: event.id, author: event.pubkey }), [event]); const { onClick: openPost, onAuxClick } = useOpenPost(`/${encodedId}`); @@ -207,7 +205,7 @@ function HotPostCard({ event }: { event: NostrEvent }) { className="block w-full text-left hover:bg-secondary/40 -mx-2 px-2 py-2 rounded-lg transition-colors" >
- + {displayName[0]?.toUpperCase()} @@ -234,13 +232,12 @@ function LatestAccountCard({ event, onDismiss }: { event: NostrEvent; onDismiss: } const displayName = metadata.name || genUserName(event.pubkey); - const latestAvatarShape = getAvatarShape(metadata); const npub = useMemo(() => nip19.npubEncode(event.pubkey), [event.pubkey]); return (
- + {displayName[0].toUpperCase()} diff --git a/src/components/TeamSoapboxCard.tsx b/src/components/TeamSoapboxCard.tsx index df01ac0c..cebc6c4c 100644 --- a/src/components/TeamSoapboxCard.tsx +++ b/src/components/TeamSoapboxCard.tsx @@ -5,7 +5,6 @@ import { nip19 } from 'nostr-tools'; import type { NostrEvent } from '@nostrify/nostrify'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { useNostr } from '@nostrify/react'; @@ -158,9 +157,8 @@ export function TeamSoapboxCard({ className }: { className?: string }) { {previewPubkeys.map((pk) => { const member = membersMap?.get(pk); const name = member?.metadata?.name || genUserName(pk); - const shape = getAvatarShape(member?.metadata); return ( - + {name[0]?.toUpperCase()} diff --git a/src/components/VoiceMessagePlayer.tsx b/src/components/VoiceMessagePlayer.tsx index 74669978..26482a61 100644 --- a/src/components/VoiceMessagePlayer.tsx +++ b/src/components/VoiceMessagePlayer.tsx @@ -6,8 +6,6 @@ import { AudioVisualizer } from '@/components/AudioVisualizer'; import type { NostrEvent } from '@nostrify/nostrify'; import { useAuthor } from '@/hooks/useAuthor'; import { genUserName } from '@/lib/genUserName'; -import { getAvatarShape } from '@/lib/avatarShape'; - /** Parse NIP-A0 imeta fields from an event's tags. */ function parseVoiceImeta(tags: string[][]): { waveform?: number[]; duration?: number } { for (const tag of tags) { @@ -61,7 +59,6 @@ export function VoiceMessagePlayer({ event, className }: VoiceMessagePlayerProps src={audioUrl} avatarUrl={avatarUrl} avatarFallback={displayName[0]?.toUpperCase() ?? '?'} - avatarShape={getAvatarShape(metadata)} className={className} /> ); diff --git a/src/components/auth/AccountSwitcher.tsx b/src/components/auth/AccountSwitcher.tsx index 2ad40c8d..eaa8b3dd 100644 --- a/src/components/auth/AccountSwitcher.tsx +++ b/src/components/auth/AccountSwitcher.tsx @@ -11,7 +11,6 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu.tsx'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar.tsx'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Skeleton } from '@/components/ui/skeleton.tsx'; import { useLoggedInAccounts, type Account } from '@/hooks/useLoggedInAccounts'; import { genUserName } from '@/lib/genUserName'; @@ -46,7 +45,7 @@ export function AccountSwitcher({ onAddAccountClick }: AccountSwitcherProps) { {isLoading ? ( ) : ( - + {getDisplayName(currentUser).charAt(0)} @@ -69,7 +68,7 @@ export function AccountSwitcher({ onAddAccountClick }: AccountSwitcherProps) { onClick={() => setLogin(user.id)} className='flex items-center gap-2 cursor-pointer p-2 rounded-md' > - + {getDisplayName(user)?.charAt(0) || } diff --git a/src/components/auth/SignupDialog.tsx b/src/components/auth/SignupDialog.tsx index 3a974999..59d8a5ef 100644 --- a/src/components/auth/SignupDialog.tsx +++ b/src/components/auth/SignupDialog.tsx @@ -14,7 +14,6 @@ import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools'; import { saveNsec } from '@/lib/credentialManager'; import { ProfileCard } from '@/components/ProfileCard'; import { ImageCropDialog } from '@/components/ImageCropDialog'; -import { isValidAvatarShape } from '@/lib/avatarShape'; import type { NostrMetadata } from '@nostrify/nostrify'; interface SignupDialogProps { @@ -31,7 +30,6 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { about: '', picture: '', banner: '', - shape: '', }); const [cropState, setCropState] = useState<{ imageSrc: string; aspect: number; field: 'picture' | 'banner' } | null>(null); const pickInputRef = useRef(null); @@ -110,12 +108,7 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { const finishSignup = async (skipProfile = false) => { try { if (!skipProfile && (profileData.name || profileData.about || profileData.picture)) { - // Build the outgoing metadata, stripping empty strings and validating shape. - const { shape, ...rest } = profileData; - const data: Record = { ...rest }; - if (shape && isValidAvatarShape(shape)) { - data.shape = shape; - } + const data: Record = { ...profileData }; for (const key in data) { if (data[key] === '') delete data[key]; } @@ -148,7 +141,7 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { setStep('generate'); setNsec(''); setShowKey(false); - setProfileData({ name: '', about: '', picture: '', banner: '', shape: '' }); + setProfileData({ name: '', about: '', picture: '', banner: '' }); } }, [isOpen]); @@ -246,7 +239,6 @@ const SignupDialog: React.FC = ({ isOpen, onClose }) => { metadata={profileData} onChange={(patch) => setProfileData(prev => ({ ...prev, ...patch }))} onPickImage={handlePickImage} - onAvatarShape={(shape) => setProfileData(prev => ({ ...prev, shape }))} />
diff --git a/src/components/icons/PlanetButton.tsx b/src/components/icons/PlanetButton.tsx deleted file mode 100644 index c231384f..00000000 --- a/src/components/icons/PlanetButton.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useId } from 'react'; -import { cn } from '@/lib/utils'; - -interface PlanetButtonProps { - className?: string; -} - -/** - * Filled planet-with-ring SVG shape used as the FAB background. - * - * Uses `useId()` to scope mask IDs so multiple instances can coexist - * without ID collisions. - */ -export function PlanetButton({ className }: PlanetButtonProps) { - const uid = useId(); - const maskId = `${uid}-planet-body-mask`; - - return ( - - - {/* Mask: white = visible, black = cut out. - The middle arc (crossing through the circle) is stroked black - so the ring appears to pass in front there. */} - - - - - - {/* Planet body with solid fill, front-arc gap cut out */} - - {/* Full ring as one continuous path */} - - - ); -} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx index 539a5f42..5d8357e8 100644 --- a/src/components/ui/avatar.tsx +++ b/src/components/ui/avatar.tsx @@ -1,7 +1,6 @@ import * as React from "react" import { cn } from "@/lib/utils" -import { type AvatarShape, isEmoji, getAvatarMaskUrl, isValidAvatarShape } from "@/lib/avatarShape" /** * Shared ref so AvatarFallback can check if a sibling AvatarImage @@ -10,63 +9,26 @@ import { type AvatarShape, isEmoji, getAvatarMaskUrl, isValidAvatarShape } from */ const AvatarHasSrcContext = React.createContext>({ current: false }) -/** Context so children can inherit the shape for their own styling. */ -const AvatarShapeContext = React.createContext(undefined) - -export interface AvatarProps extends React.HTMLAttributes { - /** Avatar mask shape. Defaults to "circle" (the standard rounded-full). */ - shape?: AvatarShape; -} +export type AvatarProps = React.HTMLAttributes const Avatar = React.forwardRef( - ({ className, children, shape, style, ...props }, ref) => { + ({ className, children, ...props }, ref) => { const hasSrcRef = React.useRef(false) // Reset per render so stale values don't persist hasSrcRef.current = false - // Check if shape is valid (emoji) - const hasValidShape = !!shape && isValidAvatarShape(shape) - const isEmojiShape = hasValidShape && isEmoji(shape) - const hasCustomShape = isEmojiShape - - // Compute mask URL synchronously — getAvatarMaskUrl renders the emoji - // to a canvas and caches the data-URL, so subsequent calls are instant. - // This avoids a flash of the unmasked square avatar on first paint. - const maskUrl = hasCustomShape && shape ? getAvatarMaskUrl(shape) : '' - - const mergedStyle = React.useMemo(() => { - if (maskUrl) { - return { - ...style, - WebkitMaskImage: `url(${maskUrl})`, - maskImage: `url(${maskUrl})`, - WebkitMaskSize: 'contain', - maskSize: 'contain' as string, - WebkitMaskRepeat: 'no-repeat', - maskRepeat: 'no-repeat' as string, - WebkitMaskPosition: 'center', - maskPosition: 'center' as string, - } - } - return style ?? {} - }, [maskUrl, style]) - return ( - -
- {children} -
-
+
+ {children} +
) } @@ -127,9 +89,6 @@ const AvatarFallback = React.forwardRef< React.HTMLAttributes >(({ className, ...props }, ref) => { const hasSrcRef = React.useContext(AvatarHasSrcContext) - const shape = React.useContext(AvatarShapeContext) - - const hasCustomShape = !!shape && isValidAvatarShape(shape) // AvatarImage renders before AvatarFallback (DOM order), so hasSrcRef // is already set by the time we read it here in the same render frame. @@ -139,8 +98,7 @@ const AvatarFallback = React.forwardRef<
nip19.neventEncode({ id: event.id, author: event.pubkey }), [event]); @@ -116,7 +114,7 @@ function CompactEventCard({ event }: { event: NostrEvent }) { className="block hover:bg-secondary/40 px-2 py-2 rounded-lg transition-colors" >
- + {displayName[0]?.toUpperCase()} diff --git a/src/components/widgets/HotPostsWidget.tsx b/src/components/widgets/HotPostsWidget.tsx index 5b3934fa..99268818 100644 --- a/src/components/widgets/HotPostsWidget.tsx +++ b/src/components/widgets/HotPostsWidget.tsx @@ -6,7 +6,6 @@ import type { NostrEvent } from '@nostrify/nostrify'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { Skeleton } from '@/components/ui/skeleton'; import { EmojifiedText } from '@/components/CustomEmoji'; -import { getAvatarShape } from '@/lib/avatarShape'; import { genUserName } from '@/lib/genUserName'; import { timeAgo } from '@/lib/timeAgo'; import { isEventMuted } from '@/lib/muteHelpers'; @@ -62,7 +61,6 @@ export function HotPostsWidget() { function HotPostCard({ event }: { event: NostrEvent }) { const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name || genUserName(event.pubkey); const encodedId = useMemo(() => nip19.neventEncode({ id: event.id, author: event.pubkey }), [event]); const { onClick: openPost, onAuxClick } = useOpenPost(`/${encodedId}`); @@ -80,7 +78,7 @@ function HotPostCard({ event }: { event: NostrEvent }) { className="block w-full text-left hover:bg-secondary/40 px-2 py-2 rounded-lg transition-colors" >
- + {displayName[0]?.toUpperCase()} diff --git a/src/components/widgets/MusicWidget.tsx b/src/components/widgets/MusicWidget.tsx index 696c1da3..6b615a44 100644 --- a/src/components/widgets/MusicWidget.tsx +++ b/src/components/widgets/MusicWidget.tsx @@ -16,7 +16,6 @@ import { useFollowList } from '@/hooks/useFollowActions'; import { useCuratorFollowList } from '@/hooks/useCuratorFollowList'; import { parseMusicTrack, toAudioTrack } from '@/lib/musicHelpers'; import { genUserName } from '@/lib/genUserName'; -import { getAvatarShape } from '@/lib/avatarShape'; import { timeAgo } from '@/lib/timeAgo'; import { formatTime } from '@/lib/formatTime'; import { cn } from '@/lib/utils'; @@ -67,7 +66,6 @@ function MusicCard({ event }: { event: NostrEvent }) { const player = useAudioPlayer(); const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name || genUserName(event.pubkey); const parsed = useMemo(() => parseMusicTrack(event), [event]); @@ -150,7 +148,7 @@ function MusicCard({ event }: { event: NostrEvent }) { {/* Author row */}
- + {displayName[0]?.toUpperCase()} diff --git a/src/components/widgets/PhotoWidget.tsx b/src/components/widgets/PhotoWidget.tsx index ae7e80c7..b1d5738e 100644 --- a/src/components/widgets/PhotoWidget.tsx +++ b/src/components/widgets/PhotoWidget.tsx @@ -13,7 +13,6 @@ import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useFollowList } from '@/hooks/useFollowActions'; import { useCuratorFollowList } from '@/hooks/useCuratorFollowList'; import { genUserName } from '@/lib/genUserName'; -import { getAvatarShape } from '@/lib/avatarShape'; import { timeAgo } from '@/lib/timeAgo'; import { sanitizeUrl } from '@/lib/sanitizeUrl'; @@ -76,7 +75,6 @@ export function PhotoWidget() { function PhotoCard({ event }: { event: NostrEvent }) { const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name || genUserName(event.pubkey); const encodedId = useMemo(() => nip19.neventEncode({ id: event.id, author: event.pubkey }), [event]); @@ -100,7 +98,7 @@ function PhotoCard({ event }: { event: NostrEvent }) { {/* Author + caption */}
- + {displayName[0]?.toUpperCase()} diff --git a/src/hooks/useGoalDisplay.ts b/src/hooks/useGoalDisplay.ts index 2c12cec5..e010d1d9 100644 --- a/src/hooks/useGoalDisplay.ts +++ b/src/hooks/useGoalDisplay.ts @@ -2,7 +2,6 @@ import { useMemo } from 'react'; import { nip19 } from 'nostr-tools'; import type { NostrEvent, NostrMetadata } from '@nostrify/nostrify'; -import { getAvatarShape } from '@/lib/avatarShape'; import { isGoalExpired, parseCommunityATag, type ParsedGoal } from '@/lib/goalUtils'; import { genUserName } from '@/lib/genUserName'; import { useAddrEvent } from '@/hooks/useEvent'; @@ -21,7 +20,6 @@ export interface GoalDisplayData { progressIsPartial: boolean; metadata: NostrMetadata | undefined; displayName: string; - avatarShape: string | undefined; profileUrl: string; lightningAddress: string | undefined; deadlineLabel: string | null; @@ -46,7 +44,6 @@ export function useGoalDisplay(event: NostrEvent, goal: ParsedGoal): GoalDisplay const author = useAuthor(goal.beneficiary); const metadata: NostrMetadata | undefined = author.data?.metadata; const displayName = metadata?.display_name || metadata?.name || genUserName(goal.beneficiary); - const avatarShape = getAvatarShape(metadata); const profileUrl = useProfileUrl(goal.beneficiary, metadata); const lightningAddress = metadata?.lud16 || metadata?.lud06 || undefined; @@ -93,7 +90,6 @@ export function useGoalDisplay(event: NostrEvent, goal: ParsedGoal): GoalDisplay progressIsPartial, metadata, displayName, - avatarShape, profileUrl, lightningAddress, deadlineLabel, diff --git a/src/lib/avatarShape.ts b/src/lib/avatarShape.ts deleted file mode 100644 index 3c86e246..00000000 --- a/src/lib/avatarShape.ts +++ /dev/null @@ -1,212 +0,0 @@ -import type React from 'react'; - -/** - * An avatar shape is stored in kind-0 metadata as the `shape` property. - * Supported formats: - * - Emoji string (e.g., "🐱", "⭐") - uses emoji glyph as mask - * - * When absent or invalid, avatars render as circles (the default). - */ -export type AvatarShape = string; - -// ── Emoji detection ────────────────────────────────────────────────────────── - -/** - * Checks whether a string could be an emoji shape value. - * - * Rather than trying to match specific Unicode emoji patterns (which is - * fragile and excludes valid emoji like keycap sequences, flags, and - * complex ZWJ families), we simply check that the value is a short - * non-ASCII string. - */ -export function isEmoji(value: string): boolean { - if (!value || value.length === 0) return false; - // Emoji are short (even complex ZWJ sequences are under ~20 JS chars) - // and contain non-ASCII characters. Reject long strings and pure ASCII - // to avoid treating arbitrary text as emoji. - if (value.length > 20) return false; - // Must contain at least one non-ASCII character - // eslint-disable-next-line no-control-regex - return /[^\x00-\x7F]/.test(value); -} - -/** - * Type guard for valid avatar shape values. - * Valid shapes are: - * - Emoji strings (non-ASCII, short) - */ -export function isValidAvatarShape(value: unknown): value is AvatarShape { - if (typeof value !== 'string' || value.length === 0) return false; - - // Must be an emoji - return isEmoji(value); -} - -/** - * Extracts a valid AvatarShape from a metadata object (or any object with a `shape` property). - * Accepts `NostrMetadata` directly — no type cast needed at call sites. - * Returns `undefined` if the shape is missing or invalid (which means "circle" / default). - */ -export function getAvatarShape(metadata: { [key: string]: unknown } | undefined): AvatarShape | undefined { - const raw = metadata?.shape; - return isValidAvatarShape(raw) ? raw : undefined; -} - -// ── Shaped avatar border style ─────────────────────────────────────────── - -/** - * CSS filter that creates a crisp, solid outline around a shaped avatar - * (emoji), mimicking the appearance of `border-4 border-background` - * without clipping the mask shape. Apply this to a **wrapper** around the - * masked ``. - */ -export const shapedAvatarBorderStyle: React.CSSProperties = { - filter: - 'drop-shadow(3px 0 0 hsl(var(--background)))' + - ' drop-shadow(-3px 0 0 hsl(var(--background)))' + - ' drop-shadow(0 3px 0 hsl(var(--background)))' + - ' drop-shadow(0 -3px 0 hsl(var(--background)))', -}; - -/** @deprecated Use shapedAvatarBorderStyle instead */ -export const emojiAvatarBorderStyle = shapedAvatarBorderStyle; - -// ── Emoji mask generation ────────────────────────────────────────────────── - -/** In-memory cache: emoji string → data-URL. */ -const emojiMaskCache = new Map(); - -// ── Unified mask URL getter ────────────────────────────────────────────── - -/** - * Get mask URL for emoji avatar shapes. - * Returns empty string if shape is invalid or mask generation fails. - */ -export function getAvatarMaskUrl(shape: string): string { - if (isEmoji(shape)) { - return getEmojiMaskUrl(shape); - } - - return ''; -} - -/** - * Async version of getAvatarMaskUrl. - * For emoji, this is equivalent to the sync version. - */ -export async function getAvatarMaskUrlAsync(shape: string): Promise { - if (isEmoji(shape)) { - return getEmojiMaskUrl(shape); - } - - return ''; -} - -/** - * Renders the user's native OS emoji onto a canvas and produces a PNG - * data-URL alpha mask suitable for use as a CSS `mask-image`. - * - * ### Algorithm - * - * 1. **Draw large.** Render the emoji at 512 px via `fillText` on an - * oversized (768 × 768) scratch canvas so the entire glyph is captured - * even if the OS renders it off-centre or larger than the em-box. - * - * 2. **Measure.** Scan every pixel to find the tight axis-aligned bounding - * box of non-transparent pixels. - * - * 3. **Square the crop.** Expand the shorter axis of the bounding box so the - * crop region is square (centred). This prevents non-square emoji from - * being stretched when applied to a square avatar. - * - * 4. **Redraw.** Draw the squared crop onto a 256 × 256 output canvas so the - * emoji fills it edge-to-edge. - * - * 5. **Convert to alpha mask.** Set every pixel to white; keep the original - * alpha channel. Export as PNG data-URL. - * - * If `mask-image` is unsupported the avatar renders as a plain square - * (the emoji mask is simply ignored by the browser). - */ -export function getEmojiMaskUrl(emoji: string): string { - const cached = emojiMaskCache.get(emoji); - if (cached) return cached; - - // ── Pass 1: draw emoji on oversized scratch canvas ────────────────── - const fontSize = 512; - const scratch = fontSize * 1.5; // 768 – generous room - const c1 = document.createElement('canvas'); - c1.width = scratch; - c1.height = scratch; - const ctx1 = c1.getContext('2d'); - if (!ctx1) return ''; - - ctx1.textAlign = 'center'; - ctx1.textBaseline = 'middle'; - ctx1.font = `${fontSize}px serif`; - ctx1.fillText(emoji, scratch / 2, scratch / 2); - - // ── Pass 2: find tight bounding box ───────────────────────────────── - // Use an alpha threshold to ignore semi-transparent shadows, glows, and - // anti-aliasing fringes that many emoji renderers add. Without this, - // faint pixels (e.g. a drop shadow) inflate the bounding box and push - // the actual emoji shape off-centre when the crop is squared. - const ALPHA_THRESHOLD = 25; // ~10% opacity - const { data: px, width: sw, height: sh } = ctx1.getImageData(0, 0, scratch, scratch); - let t = sh, b = 0, l = sw, r = 0; - for (let y = 0; y < sh; y++) { - for (let x = 0; x < sw; x++) { - if (px[(y * sw + x) * 4 + 3] > ALPHA_THRESHOLD) { - if (y < t) t = y; - if (y > b) b = y; - if (x < l) l = x; - if (x > r) r = x; - } - } - } - if (r < l || b < t) return ''; // nothing drawn - - // ── Pass 3: square the bounding box ───────────────────────────────── - let cropW = r - l + 1; - let cropH = b - t + 1; - if (cropW > cropH) { - const diff = cropW - cropH; - t -= Math.floor(diff / 2); - b = t + cropW - 1; - cropH = cropW; - } else if (cropH > cropW) { - const diff = cropH - cropW; - l -= Math.floor(diff / 2); - r = l + cropH - 1; - cropW = cropH; - } - // Clamp to canvas bounds (shouldn't be needed with oversized scratch, - // but be safe). - if (t < 0) t = 0; - if (l < 0) l = 0; - - // ── Pass 4: redraw cropped region onto output canvas ──────────────── - const out = 256; - const c2 = document.createElement('canvas'); - c2.width = out; - c2.height = out; - const ctx2 = c2.getContext('2d'); - if (!ctx2) return ''; - - ctx2.drawImage(c1, l, t, cropW, cropH, 0, 0, out, out); - - // ── Pass 5: convert to alpha mask (white + original alpha) ────────── - const img = ctx2.getImageData(0, 0, out, out); - const d = img.data; - for (let i = 0; i < d.length; i += 4) { - d[i] = 255; // R - d[i + 1] = 255; // G - d[i + 2] = 255; // B - // d[i+3] (alpha) kept as-is - } - ctx2.putImageData(img, 0, 0); - - const url = c2.toDataURL('image/png'); - emojiMaskCache.set(emoji, url); - return url; -} diff --git a/src/pages/ExternalContentPage.tsx b/src/pages/ExternalContentPage.tsx index bdfd2c34..e59efbdb 100644 --- a/src/pages/ExternalContentPage.tsx +++ b/src/pages/ExternalContentPage.tsx @@ -7,7 +7,6 @@ import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; @@ -538,7 +537,6 @@ function BookContentTabs({ isbn, commentRoot, orderedReplies, commentsLoading }: function BookReviewCard({ event, review }: { event: NostrEvent; review: BookReview }) { const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = getDisplayName(metadata, event.pubkey); const profileUrl = useProfileUrl(event.pubkey, metadata); const [showSpoiler, setShowSpoiler] = useState(false); @@ -555,7 +553,7 @@ function BookReviewCard({ event, review }: { event: NostrEvent; review: BookRevi ) : ( - + {displayName[0]?.toUpperCase()} diff --git a/src/pages/FollowPage.tsx b/src/pages/FollowPage.tsx index beab4b80..a30ced3c 100644 --- a/src/pages/FollowPage.tsx +++ b/src/pages/FollowPage.tsx @@ -9,8 +9,6 @@ import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { NoteCard } from '@/components/NoteCard'; -import { getAvatarShape, isEmoji, emojiAvatarBorderStyle } from '@/lib/avatarShape'; -import { cn } from '@/lib/utils'; import { useAuthor } from '@/hooks/useAuthor'; import { useAuthors } from '@/hooks/useAuthors'; import { useCurrentUser } from '@/hooks/useCurrentUser'; @@ -180,33 +178,19 @@ function FollowView({ pubkey }: { pubkey: string }) {
{/* Avatar — matches ProfilePage border treatment */} - {(() => { - const avatarShape = getAvatarShape(metadata); - const isEmojiShape = !!avatarShape && isEmoji(avatarShape); - return ( -
-
- - - - {displayName.charAt(0).toUpperCase()} - - -
- {(followDone || isAlreadyFollowing) && ( -
- -
- )} +
+ + + + {displayName.charAt(0).toUpperCase()} + + + {(followDone || isAlreadyFollowing) && ( +
+
- ); - })()} + )} +
{/* Name + NIP-05 */}
@@ -430,9 +414,8 @@ function FollowPackView({ addr, relays }: { addr: AddrCoords; relays?: string[] {pubkeys.slice(0, 5).map((pk) => { const member = membersMap?.get(pk); const name = member?.metadata?.name || genUserName(pk); - const shape = getAvatarShape(member?.metadata); return ( - + {name[0]?.toUpperCase()} @@ -452,7 +435,7 @@ function FollowPackView({ addr, relays }: { addr: AddrCoords; relays?: string[] {/* Author attribution */} - + {authorName[0]?.toUpperCase()} diff --git a/src/pages/ListDetailPage.tsx b/src/pages/ListDetailPage.tsx index ad6f1cba..00a9ea80 100644 --- a/src/pages/ListDetailPage.tsx +++ b/src/pages/ListDetailPage.tsx @@ -15,7 +15,6 @@ import { } from 'lucide-react'; import { RepostIcon } from '@/components/icons/RepostIcon'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { @@ -72,7 +71,6 @@ function MemberCard({ pubkey, isOwner, listId, onRemoved }: { }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.display_name || metadata?.name || genUserName(pubkey); const profileUrl = useProfileUrl(pubkey, metadata); const { data: followData } = useFollowList(); @@ -113,7 +111,7 @@ function MemberCard({ pubkey, isOwner, listId, onRemoved }: { ) : ( <> - + {displayName[0]?.toUpperCase()} @@ -369,7 +367,6 @@ export function ListDetailPage() { const listAuthor = useAuthor(decoded?.pubkey ?? ''); const listAuthorMetadata = listAuthor.data?.metadata; const listAuthorName = listAuthorMetadata?.name || listAuthorMetadata?.display_name || (decoded ? genUserName(decoded.pubkey) : ''); - const listAuthorAvatarShape = getAvatarShape(listAuthorMetadata); const listAuthorProfileUrl = useProfileUrl(decoded?.pubkey ?? '', listAuthorMetadata); // Fetch preview avatars for the member stack @@ -484,7 +481,7 @@ export function ListDetailPage() {

{list.title}

{decoded && ( - + {listAuthorName[0]?.toUpperCase()} @@ -585,9 +582,8 @@ export function ListDetailPage() { {previewPubkeys.map((pk) => { const member = previewMembersMap?.get(pk); const name = member?.metadata?.name || genUserName(pk); - const shape = getAvatarShape(member?.metadata); return ( - + {name[0]?.toUpperCase()} diff --git a/src/pages/NotificationsPage.tsx b/src/pages/NotificationsPage.tsx index 6d45562d..598a2066 100644 --- a/src/pages/NotificationsPage.tsx +++ b/src/pages/NotificationsPage.tsx @@ -23,7 +23,6 @@ import { isEventMuted } from '@/lib/muteHelpers'; import { genUserName } from '@/lib/genUserName'; import { nip19 } from 'nostr-tools'; import { isReplyEvent } from '@/lib/nostrEvents'; -import { getAvatarShape, emojiAvatarBorderStyle } from '@/lib/avatarShape'; import { useProfileUrl } from '@/hooks/useProfileUrl'; import { formatNumber } from '@/lib/formatNumber'; import { cn } from '@/lib/utils'; @@ -368,18 +367,11 @@ function ActorAvatar({ pubkey }: { pubkey: string }) { const metadata = author.data?.metadata; const name = metadata?.name ?? genUserName(pubkey); const profileUrl = useProfileUrl(pubkey, metadata); - const shape = getAvatarShape(metadata); - const isEmojiShape = !!shape; return ( - - + + {metadata?.picture && } {name.slice(0, 2).toUpperCase()} diff --git a/src/pages/PostDetailPage.tsx b/src/pages/PostDetailPage.tsx index cb91e965..cf5c64bb 100644 --- a/src/pages/PostDetailPage.tsx +++ b/src/pages/PostDetailPage.tsx @@ -70,7 +70,6 @@ import { ReplyComposeModal } from "@/components/ReplyComposeModal"; import { RepostMenu } from "@/components/RepostMenu"; import { ThreadedReplyList, FlatThreadedReplyList, type ReplyNode } from "@/components/ThreadedReplyList"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { getAvatarShape } from "@/lib/avatarShape"; import { Button } from "@/components/ui/button"; import { Collapsible, @@ -527,7 +526,6 @@ function CopyableHex({ value }: { value: string }) { function AuthorHintRow({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = getDisplayName(metadata, pubkey); const profileUrl = useProfileUrl(pubkey, metadata); @@ -544,7 +542,7 @@ function AuthorHintRow({ pubkey }: { pubkey: string }) { ) : ( <> - + {displayName[0]?.toUpperCase()} @@ -943,7 +941,6 @@ function PostDetailContent({ event }: { event: NostrEvent }) { const queryClient = useQueryClient(); const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = getDisplayName(metadata, event.pubkey); // Refetch the author's profile whenever we navigate to a post by this author. @@ -957,7 +954,6 @@ function PostDetailContent({ event }: { event: NostrEvent }) { const zapSenderPubkeyRaw = useMemo(() => event.kind === 9735 ? extractZapSender(event) : '', [event]); const zapSenderAuthor = useAuthor(zapSenderPubkeyRaw || undefined); const zapSenderMeta = zapSenderAuthor.data?.metadata; - const zapSenderShape = getAvatarShape(zapSenderMeta); const zapSenderDisplayName = getDisplayName(zapSenderMeta, zapSenderPubkeyRaw); const zapSenderProfileUrl = useProfileUrl(zapSenderPubkeyRaw, zapSenderMeta); @@ -1479,7 +1475,7 @@ function PostDetailContent({ event }: { event: NostrEvent }) { <> - + - + - + {zapSenderDisplayName[0]?.toUpperCase()} @@ -1960,7 +1956,7 @@ function PostDetailContent({ event }: { event: NostrEvent }) { icon={ - + {displayName[0]?.toUpperCase()} @@ -2026,7 +2022,7 @@ function PostDetailContent({ event }: { event: NostrEvent }) { <> - + {displayName[0].toUpperCase()} diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 5fb19404..03db82ca 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -9,7 +9,6 @@ import { nip19 } from 'nostr-tools'; import { Zap, Flame, MoreHorizontal, ClipboardCopy, ExternalLink, VolumeX, Flag, Bitcoin, Pin, X, QrCode, Check, Copy, Loader2, Download, Pencil, Trash2, RotateCcw, MessageSquare, Globe, Mail, Plus, GripVertical, ListPlus, Award, PanelLeft } from 'lucide-react'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape, isEmoji, emojiAvatarBorderStyle } from '@/lib/avatarShape'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; @@ -392,7 +391,6 @@ function MenuRow({ icon, label, onClick, destructive }: { icon: React.ReactNode; function FollowingUserRow({ pubkey, onNavigate }: { pubkey: string; onNavigate?: () => void }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name || genUserName(pubkey); const npubEncoded = useMemo(() => nip19.npubEncode(pubkey), [pubkey]); @@ -412,7 +410,7 @@ function FollowingUserRow({ pubkey, onNavigate }: { pubkey: string; onNavigate?: ) : ( <> - + {displayName[0]?.toUpperCase()} @@ -1271,8 +1269,6 @@ type EditableTab = { label: string; isCore: boolean; tab?: ProfileTab }; // Kind 0 — resolved from the author cache (seeded by the feed query above). const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); - const isEmojiShape = !!avatarShape && isEmoji(avatarShape); const profileStatus = useUserStatus(pubkey); // Refetch the author's profile whenever we navigate to this profile page. @@ -1695,14 +1691,12 @@ type EditableTab = { label: string; isCore: boolean; tab?: ProfileTab }; onClick={() => metadata?.picture && setLightboxImage(metadata.picture)} disabled={!metadata?.picture} > -
- - - - {displayName[0].toUpperCase()} - - -
+ + + + {displayName[0].toUpperCase()} + + {/* NIP-38 thought bubble — floats beside the avatar over the banner */} diff --git a/src/pages/ProfileSettings.tsx b/src/pages/ProfileSettings.tsx index 22d30e9d..a84ba138 100644 --- a/src/pages/ProfileSettings.tsx +++ b/src/pages/ProfileSettings.tsx @@ -56,7 +56,6 @@ import { CollapsibleTrigger, } from '@/components/ui/collapsible'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; -import { isValidAvatarShape } from '@/lib/avatarShape'; // ── Constants ───────────────────────────────────────────────────────────────── @@ -264,7 +263,6 @@ const formSchema = n.metadata().extend({ /** Client-side only — placeholder text for the value input (not persisted). */ placeholder: z.string().optional(), })).optional(), - shape: z.string().optional(), }); type FormValues = z.infer; @@ -459,21 +457,11 @@ export function ProfileSettings() { return []; }; - const parseShape = (): string => { - if (!event) return ''; - try { - const parsed = JSON.parse(event.content); - if (isValidAvatarShape(parsed.shape)) return parsed.shape; - } catch { /* ignore */ } - return ''; - }; - const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { name: '', about: '', picture: '', banner: '', website: '', nip05: '', lud16: '', bot: false, fields: [], - shape: '', }, }); @@ -531,7 +519,6 @@ export function ProfileSettings() { lud16: metadata.lud16 ?? '', bot: metadata.bot ?? false, fields: parseFields(), - shape: parseShape(), }); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -539,7 +526,7 @@ export function ProfileSettings() { // Live values for the card preview const watched = form.watch(); - const cardMetadata: Partial & { shape?: string } = { + const cardMetadata: Partial = { name: watched.name, about: watched.about, picture: watched.picture, @@ -548,7 +535,6 @@ export function ProfileSettings() { nip05: watched.nip05, lud16: watched.lud16, bot: watched.bot, - shape: watched.shape, }; // Live sidebar preview fields — computed from watched form values @@ -632,15 +618,11 @@ export function ProfileSettings() { const onSubmit = async (values: FormValues) => { if (!user) return; try { - const { fields: customFields, shape, ...standardMetadata } = values; + const { fields: customFields, ...standardMetadata } = values; const data: Record = { ...metadata, ...standardMetadata }; - // Add shape only if set (an emoji string) - if (shape && isValidAvatarShape(shape)) { - data.shape = shape; - } else { - delete data.shape; - } + // Strip any legacy avatar shape from old Ditto-style profiles + delete data.shape; for (const key in data) { if (data[key] === '') delete data[key]; @@ -735,7 +717,6 @@ export function ProfileSettings() { metadata={cardMetadata} onChange={handleCardChange} onPickImage={handlePickImage} - onAvatarShape={(shape) => form.setValue('shape', shape, { shouldDirty: true })} onRemoveAvatar={() => form.setValue('picture', '', { shouldDirty: true })} /> diff --git a/src/pages/SearchPage.tsx b/src/pages/SearchPage.tsx index 7603f4d4..efab5d23 100644 --- a/src/pages/SearchPage.tsx +++ b/src/pages/SearchPage.tsx @@ -18,7 +18,6 @@ import { Link, useSearchParams } from 'react-router-dom'; import { NoteCard } from '@/components/NoteCard'; import { PullToRefresh } from '@/components/PullToRefresh'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; @@ -867,7 +866,6 @@ function AccountItem({ profile, isFollowed }: { profile: { pubkey: string; metad const npub = useMemo(() => nip19.npubEncode(profile.pubkey), [profile.pubkey]); const metadata = profile.metadata as { name?: string; nip05?: string; picture?: string; about?: string; bot?: boolean }; const displayName = metadata?.name || genUserName(profile.pubkey); - const profileAvatarShape = getAvatarShape(metadata); const tags = profile.event?.tags ?? []; return ( @@ -876,7 +874,7 @@ function AccountItem({ profile, isFollowed }: { profile: { pubkey: string; metad className="flex items-center gap-3 px-4 py-3 hover:bg-secondary/30 transition-colors" >
- + {displayName[0]?.toUpperCase() || '?'} @@ -951,7 +949,6 @@ function FollowsList() { function FollowItem({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const npub = useMemo(() => nip19.npubEncode(pubkey), [pubkey]); const displayName = metadata?.name || genUserName(pubkey); const tags = author.data?.event?.tags ?? []; @@ -966,7 +963,7 @@ function FollowItem({ pubkey }: { pubkey: string }) { className="flex items-center gap-3 px-4 py-3 hover:bg-secondary/30 transition-colors" >
- + {displayName[0]?.toUpperCase() || '?'} diff --git a/src/pages/StreamsFeedPage.tsx b/src/pages/StreamsFeedPage.tsx index 21bd5c94..ceb7a0e1 100644 --- a/src/pages/StreamsFeedPage.tsx +++ b/src/pages/StreamsFeedPage.tsx @@ -8,7 +8,6 @@ import type { NostrEvent } from '@nostrify/nostrify'; import { useAppContext } from '@/hooks/useAppContext'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Card, CardContent } from '@/components/ui/card'; @@ -193,7 +192,6 @@ function StreamCard({ event }: { event: NostrEvent }) { function StreamCardAuthor({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = getDisplayName(metadata, pubkey); const profileUrl = useProfileUrl(pubkey, metadata); @@ -203,7 +201,7 @@ function StreamCardAuthor({ pubkey }: { pubkey: string }) { return ( e.stopPropagation()}> - + {displayName[0]?.toUpperCase()} diff --git a/src/pages/UserListsPage.tsx b/src/pages/UserListsPage.tsx index 8b71697b..49014e1a 100644 --- a/src/pages/UserListsPage.tsx +++ b/src/pages/UserListsPage.tsx @@ -12,7 +12,6 @@ import { Check, X, } from 'lucide-react'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { getAvatarShape } from '@/lib/avatarShape'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Skeleton } from '@/components/ui/skeleton'; @@ -41,10 +40,9 @@ import type { UserList } from '@/hooks/useUserLists'; function MiniAvatar({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = metadata?.name ?? genUserName(pubkey); return ( - + {displayName[0]?.toUpperCase()} diff --git a/src/pages/VideosFeedPage.tsx b/src/pages/VideosFeedPage.tsx index 297e6347..b55b1eff 100644 --- a/src/pages/VideosFeedPage.tsx +++ b/src/pages/VideosFeedPage.tsx @@ -44,7 +44,6 @@ import { useOpenPost } from "@/hooks/useOpenPost"; import { useProfileUrl } from "@/hooks/useProfileUrl"; import { usePageRefresh } from "@/hooks/usePageRefresh"; import { useInfiniteHotFeed } from "@/hooks/useTrending"; -import { getAvatarShape } from "@/lib/avatarShape"; import { getExtraKindDef } from "@/lib/extraKinds"; import type { FeedItem } from "@/lib/feedUtils"; import { getDisplayName } from "@/lib/getDisplayName"; @@ -229,7 +228,6 @@ function VideoGridCard({ event }: { event: NostrEvent }) { const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = getDisplayName(metadata, event.pubkey); const profileUrl = useProfileUrl(event.pubkey, metadata); @@ -288,7 +286,7 @@ function VideoGridCard({ event }: { event: NostrEvent }) { {author.isLoading ? ( ) : ( - + {displayName[0]?.toUpperCase()} diff --git a/src/pages/VinesFeedPage.tsx b/src/pages/VinesFeedPage.tsx index d4ccee75..39888f85 100644 --- a/src/pages/VinesFeedPage.tsx +++ b/src/pages/VinesFeedPage.tsx @@ -40,7 +40,6 @@ import { useStreamKind } from "@/hooks/useStreamKind"; import { type EventStats, useEventStats } from "@/hooks/useTrending"; import { useUserReaction } from "@/hooks/useUserReaction"; import { DITTO_RELAY } from "@/lib/appRelays"; -import { getAvatarShape } from "@/lib/avatarShape"; import { canZap } from "@/lib/canZap"; import { EXTRA_KINDS } from "@/lib/extraKinds"; import { getRepostKind } from "@/lib/feedUtils"; @@ -334,7 +333,6 @@ export function VineCard({ const { user } = useCurrentUser(); const author = useAuthor(event.pubkey); const metadata = author.data?.metadata; - const avatarShape = getAvatarShape(metadata); const displayName = getDisplayName(metadata, event.pubkey); const profileUrl = useProfileUrl(event.pubkey, metadata); const { data: stats } = useEventStats(event.id, event); @@ -555,10 +553,7 @@ export function VineCard({ {author.isLoading ? ( ) : ( - + {displayName[0]?.toUpperCase()}