From 665d875df1836c9390d86448867e8e5f36fdd3b1 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Tue, 29 Sep 2026 14:27:20 +0700 Subject: [PATCH] ui(settings): give Edit Profile a real preview, honest reload, and a finish checklist The profile screen was a bare 'view my public profile' link, a banner that wrongly said 'Profile not loaded' whenever the user simply had no name yet, a directory card, then the form. - ProfilePreviewCard: banner + avatar + handle + bio + badges, so you can see how you look to everyone else without leaving the page. The public-profile link moves into it. - The reload banner now only shows when we have NO metadata at all, and says so honestly instead of keying off a missing name. - ProfileChecklist: a compact nudge listing only genuinely empty essentials (name, photo, bio, lightning address, handle); hidden once complete. --- src/components/ProfilePreviewCard.tsx | 86 +++++++++++++++++++++++++++ src/pages/SettingsPage.tsx | 60 ++++++++++++++----- 2 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 src/components/ProfilePreviewCard.tsx diff --git a/src/components/ProfilePreviewCard.tsx b/src/components/ProfilePreviewCard.tsx new file mode 100644 index 0000000..f623a96 --- /dev/null +++ b/src/components/ProfilePreviewCard.tsx @@ -0,0 +1,86 @@ +import { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { nip19 } from 'nostr-tools'; +import { ExternalLink, ShieldCheck } from 'lucide-react'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { ProfileBadges } from '@/components/ProfileBadges'; +import { useCurrentUser } from '@/hooks/useCurrentUser'; +import { isSkateSpotsNip05, profileBadges } from '@/lib/badges'; +import { cn } from '@/lib/utils'; + +/** + * A live preview of how the signed-in user appears to everyone else — banner, + * avatar, handle, bio and badges — shown at the top of the Edit Profile screen + * so "how do I look?" has an answer without leaving the page. + * + * Reads the same kind-0 metadata the form edits, so it reflects the last SAVED + * profile (the form's unsaved edits aren't shown; the sticky Save button commits). + */ +export function ProfilePreviewCard({ className }: { className?: string }) { + const { user, metadata, isLoadingProfile } = useCurrentUser(); + const [bannerOk, setBannerOk] = useState(true); + + if (!user) return null; + + const name = metadata?.display_name || metadata?.name || ''; + const initials = (name || user.pubkey.slice(0, 2)).slice(0, 2).toUpperCase(); + const nip05 = metadata?.nip05; + const about = metadata?.about?.trim(); + const badges = profileBadges(metadata); + + return ( +
+ {/* Banner (falls back to the brand gradient) */} +
+ {metadata?.banner && bannerOk && ( + setBannerOk(false)} + /> + )} +
+ + View public + + +
+ + {/* Identity */} +
+
+ + {metadata?.picture && } + {initials} + +
+

+ {name || (isLoadingProfile ? 'Loading…' : 'Unnamed skater')} +

+ {nip05 ? ( + + + {nip05} + + ) : ( + No handle yet + )} +
+
+ + {about &&

{about}

} + + {badges.length > 0 && } +
+
+ ); +} diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index f659b4e..c66fe53 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -2,6 +2,7 @@ import { useSeoMeta } from '@unhead/react'; import { Link, useSearchParams } from 'react-router-dom'; import { EditProfileForm } from '@/components/EditProfileForm'; import { SkaterDirectoryCard } from '@/components/SkaterDirectoryCard'; +import { ProfilePreviewCard } from '@/components/ProfilePreviewCard'; import { RelayListManager } from '@/components/RelayListManager'; import { WalletModal } from '@/components/WalletModal'; @@ -17,8 +18,7 @@ import { DesktopNavManager } from '@/components/DesktopNavManager'; import { MyDataTab } from '@/components/MyDataTab'; import { useQueryClient } from '@tanstack/react-query'; import { useNostr } from '@nostrify/react'; -import { NSchema as n } from '@nostrify/nostrify'; -import { nip19 } from 'nostr-tools'; +import { NSchema as n, type NostrMetadata } from '@nostrify/nostrify'; import { useState } from 'react'; import { LoginArea } from '@/components/auth/LoginArea'; import { Button } from '@/components/ui/button'; @@ -128,8 +128,38 @@ function MenuGroup({ title, children }: { title: string; children: React.ReactNo const PROFILE_FALLBACK_RELAYS = ['wss://purplepag.es', 'wss://relay.damus.io', 'wss://nos.lol']; +/** + * A short "what's missing" nudge. Deliberately only lists genuinely empty + * essentials — a new skater sees it, a finished profile never does — so it can't + * read as nagging. + */ +function ProfileChecklist({ metadata }: { metadata?: NostrMetadata }) { + const items: string[] = []; + if (!metadata?.name && !metadata?.display_name) items.push('a display name'); + if (!metadata?.picture) items.push('a profile photo'); + if (!metadata?.about?.trim()) items.push('a short bio'); + if (!metadata?.lud16 && !metadata?.lud06) items.push('a Lightning address so people can zap you'); + if (!metadata?.nip05) items.push('a free @skate-spots.org handle'); + + if (items.length === 0) return null; + + return ( +
+

Finish your profile

+
    + {items.map((item) => ( +
  • + + Add {item} +
  • + ))} +
+
+ ); +} + function ProfileScreen({ onBack }: { onBack: () => void }) { - const { user, metadata } = useCurrentUser(); + const { user, metadata, isLoadingProfile } = useCurrentUser(); const { nostr } = useNostr(); const queryClient = useQueryClient(); const [isRefreshing, setIsRefreshing] = useState(false); @@ -169,26 +199,24 @@ function ProfileScreen({ onBack }: { onBack: () => void }) {
) : (
- {/* Quick link to the user's own public profile page */} - - - - View my public profile - - - - {!metadata?.name && ( + {/* How everyone else sees you (last saved profile). */} + + + {/* Only warn when we truly have NOTHING — an account with a name but no + bio is not "not loaded", and the checklist below covers that case. */} + {!isLoadingProfile && !metadata && (
-

Profile not loaded from relays.

+

+ We couldn't find a profile on the relays. If you've set one before, try refreshing. +

)} + +