From a23225cad44af38649d925765685d9c32442e574 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Thu, 24 Sep 2026 18:50:25 +0700 Subject: [PATCH] ux: migrate more empty states + fill in missing page titles (U1/U2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empty states: - LabPage and NotificationBell now use the shared component. - LabPage's logged-out "Log in to create a profile" prompt preserved via the hint slot; removed now-unused cn import. Page titles (U2): - Audited all page routes for document titles. Only 3 were missing: - EditTourPage: now sets "Edit - SkateSpots". - AmberCallbackPage: now sets "Signing in with Amber - SkateSpots". - NIP19Page ProfilePage: sets " - SkateSpots" with about as description; other NIP-19 branches already delegate to NotFound / NewsletterIssueView which set their own titles. ScrollToTop (U1): verified already correct — resets window scroll on every pathname change, wired in AppRouter. No change needed. Co-authored-by: shakespeare.diy --- src/components/NotificationBell.tsx | 14 ++++----- src/pages/AmberCallbackPage.tsx | 6 ++++ src/pages/EditTourPage.tsx | 6 ++++ src/pages/LabPage.tsx | 47 +++++++++++++++-------------- src/pages/NIP19Page.tsx | 6 ++++ 5 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/components/NotificationBell.tsx b/src/components/NotificationBell.tsx index 45bf115..3fb435a 100644 --- a/src/components/NotificationBell.tsx +++ b/src/components/NotificationBell.tsx @@ -12,6 +12,7 @@ import { JoinRequestCard } from '@/components/JoinRequestCard'; import { CollectiveJoinRequestCard } from '@/components/CollectiveJoinRequestCard'; import { ActivityNotificationCard } from '@/components/ActivityNotificationCard'; import { ImageWithFallback } from '@/components/ImageWithFallback'; +import { EmptyState as SharedEmptyState } from '@/components/EmptyState'; import { useCrewNotifications, useIsPendingMember } from '@/hooks/useCrewNotifications'; import { useActivityNotifications } from '@/hooks/useActivityNotifications'; import { useNotificationsRead } from '@/hooks/useNotificationsRead'; @@ -180,13 +181,12 @@ function NewsletterItem({ function EmptyState({ label }: { label: string }) { return ( -
-
- -
-

You're all caught up

-

{label}

-
+ ); } diff --git a/src/pages/AmberCallbackPage.tsx b/src/pages/AmberCallbackPage.tsx index 6c2fa9f..a41b488 100644 --- a/src/pages/AmberCallbackPage.tsx +++ b/src/pages/AmberCallbackPage.tsx @@ -11,6 +11,7 @@ import { useEffect, useRef, useState } from 'react'; import { useNavigate, Link } from 'react-router-dom'; +import { useSeoMeta } from '@unhead/react'; import { useLoginActions } from '@/hooks/useLoginActions'; import { useToast } from '@/hooks/useToast'; import { @@ -34,6 +35,11 @@ export default function AmberCallbackPage() { const [status, setStatus] = useState('processing'); const [message, setMessage] = useState('Completing login with Amber...'); + useSeoMeta({ + title: 'Signing in with Amber - SkateSpots', + description: 'Completing your Amber (NIP-55) login.', + }); + useEffect(() => { if (hasProcessed.current) return; hasProcessed.current = true; diff --git a/src/pages/EditTourPage.tsx b/src/pages/EditTourPage.tsx index 29fba52..0978b70 100644 --- a/src/pages/EditTourPage.tsx +++ b/src/pages/EditTourPage.tsx @@ -1,4 +1,5 @@ import { useParams } from 'react-router-dom'; +import { useSeoMeta } from '@unhead/react'; import { Layout } from '@/components/Layout'; import { Card, CardContent } from '@/components/ui/card'; import { Skeleton } from '@/components/ui/skeleton'; @@ -20,6 +21,11 @@ export default function EditTourPage() { const { user } = useCurrentUser(); const { data: tour, isLoading, error } = useTour(identifier || '', pubkey); + useSeoMeta({ + title: tour?.title ? `Edit ${tour.title} - SkateSpots` : 'Edit Tour - SkateSpots', + description: 'Edit your skate tour — update stops, dates and details.', + }); + if (isLoading) { return ( diff --git a/src/pages/LabPage.tsx b/src/pages/LabPage.tsx index e19740e..2bfb28e 100644 --- a/src/pages/LabPage.tsx +++ b/src/pages/LabPage.tsx @@ -71,7 +71,7 @@ import { PersonStanding, ArrowRight, } from 'lucide-react'; -import { cn } from '@/lib/utils'; +import { EmptyState as SharedEmptyState } from '@/components/EmptyState'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { ProBadge } from '@/components/ProBadge'; import { LabCrewsStrip } from '@/components/LabCrewsStrip'; @@ -1096,28 +1096,29 @@ function EmptyState({ serviceType, user }: { serviceType: ServiceType; user: boo const Icon = c.icon; return ( -
-
- -
-

{c.title}

-

{c.body}

- {user ? ( - - - - ) : ( -

- Log in to create a profile -

- )} - - Have questions? Check the FAQ → - -
+ + {c.body}{' '} + + Log in + {' '} + to create a profile. + + ) + } + actions={ + user + ? [{ label: c.cta, to: c.to, icon: Plus, className: c.color }] + : [] + } + footer={{ label: 'Have questions? Check the FAQ →', to: '/faq' }} + /> ); } diff --git a/src/pages/NIP19Page.tsx b/src/pages/NIP19Page.tsx index 2c064c0..62defdd 100644 --- a/src/pages/NIP19Page.tsx +++ b/src/pages/NIP19Page.tsx @@ -1,5 +1,6 @@ import { nip19 } from 'nostr-tools'; import { useParams, Link } from 'react-router-dom'; +import { useSeoMeta } from '@unhead/react'; import { Layout } from '@/components/Layout'; import { ImageWithFallback } from '@/components/ImageWithFallback'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -164,6 +165,11 @@ function ProfilePage({ pubkey }: { pubkey: string }) { const npub = nip19.npubEncode(pubkey); const isOwnProfile = user?.pubkey === pubkey; + useSeoMeta({ + title: `${displayName} - SkateSpots`, + description: about ? about.slice(0, 160) : `${displayName}'s profile on SkateSpots.`, + }); + // At-a-glance skater stats for the summary strip. const { data: riderStats } = useRiderStats(pubkey); const memberSince = profileEventCreatedAt(author.data?.event?.created_at);