Add rich Open Graph / Twitter share metadata + canonical share link to event page
- Per-event og:title/description/image/url + twitter:summary_large_image via useSeoMeta - Share description composes date + location + blurb; falls back to site card image - handleShare now uses the canonical /#/event/... URL, richer text, and toast feedback on clipboard copy Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
87c81a63c9
commit
29d09eb6ec
1 changed files with 66 additions and 7 deletions
|
|
@ -39,6 +39,7 @@ import {
|
|||
Megaphone,
|
||||
} from 'lucide-react';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
|
||||
function formatTime(timestamp: number): string {
|
||||
const date = new Date(timestamp * 1000);
|
||||
|
|
@ -80,12 +81,53 @@ export default function EventDetailPage() {
|
|||
const { data: event, isLoading, error } = useSkateEvent(identifier || '', pubkey);
|
||||
const author = useAuthor(event?.pubkey);
|
||||
const { user } = useCurrentUser();
|
||||
const { toast } = useToast();
|
||||
|
||||
const isOwner = user && event && user.pubkey === event.pubkey;
|
||||
|
||||
// Canonical, shareable URL for this event (HashRouter → keep the /#/ prefix).
|
||||
const shareUrl = event
|
||||
? `https://skate-spots.org/#/event/${event.pubkey}/${encodeURIComponent(event.identifier)}`
|
||||
: 'https://skate-spots.org';
|
||||
|
||||
// og:image must be an absolute URL. Fall back to the site social card when the
|
||||
// event has no cover so shared links never render blank.
|
||||
const ogImage = event?.image || 'https://skate-spots.org/images/icon-1024.png';
|
||||
|
||||
// Compose a punchy share description: date + location, then the blurb.
|
||||
const shareDescription = (() => {
|
||||
if (!event) return 'View this skateboarding event on SkateSpots.';
|
||||
const bits: string[] = [];
|
||||
if (event.start) {
|
||||
bits.push(
|
||||
new Date(event.start * 1000).toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (event.location) bits.push(`📍 ${event.location}`);
|
||||
const prefix = bits.length ? `${bits.join(' · ')} — ` : '';
|
||||
const body = event.description?.trim() || 'A skateboarding event on SkateSpots.';
|
||||
return `${prefix}${body}`.slice(0, 200);
|
||||
})();
|
||||
|
||||
const shareTitle = event ? `${event.title} · SkateSpots` : 'Skate Event · SkateSpots';
|
||||
|
||||
useSeoMeta({
|
||||
title: event ? `${event.title} - SkateSpots` : 'Skate Event - SkateSpots',
|
||||
description: event?.description || 'View this skateboarding event on SkateSpots.',
|
||||
title: shareTitle,
|
||||
description: shareDescription,
|
||||
ogTitle: shareTitle,
|
||||
ogDescription: shareDescription,
|
||||
ogType: 'article',
|
||||
ogUrl: shareUrl,
|
||||
ogImage,
|
||||
ogSiteName: 'SkateSpots',
|
||||
twitterCard: 'summary_large_image',
|
||||
twitterTitle: shareTitle,
|
||||
twitterDescription: shareDescription,
|
||||
twitterImage: ogImage,
|
||||
});
|
||||
|
||||
const metadata = author.data?.metadata;
|
||||
|
|
@ -132,18 +174,35 @@ export default function EventDetailPage() {
|
|||
}, [event]);
|
||||
|
||||
const handleShare = async () => {
|
||||
const shareText = event
|
||||
? `${event.title} — a skate event on SkateSpots`
|
||||
: 'Check out this skate event on SkateSpots';
|
||||
|
||||
if (navigator.share) {
|
||||
try {
|
||||
await navigator.share({
|
||||
title: event?.title,
|
||||
text: event?.description || `Check out this skate event: ${event?.title}`,
|
||||
url: window.location.href,
|
||||
text: shareText,
|
||||
url: shareUrl,
|
||||
});
|
||||
return;
|
||||
} catch {
|
||||
// User cancelled or share failed
|
||||
// User cancelled or share failed — fall through to clipboard.
|
||||
}
|
||||
} else {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(shareUrl);
|
||||
toast({
|
||||
title: 'Link copied',
|
||||
description: 'Event link copied to your clipboard — paste it anywhere.',
|
||||
});
|
||||
} catch {
|
||||
toast({
|
||||
title: 'Copy failed',
|
||||
description: shareUrl,
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue