Route event notifications to the event, not the actor (1.2.35)
Audited the RSVP / event-update notification flow end to end. The data
layer was already solid and secure — RSVPs p-tag the host, updates
p-tag every RSVPer, and useActivityNotifications guards event-update
spoofing by requiring the author to be the event's host (from the `a`
coordinate) or an admin. The gap was on the rendering side.
Fixes:
- ActivityNotificationCard hardwired every notification to link to the
ACTOR's profile. For an RSVP ("Alice is going to your event") the host
wants the event + attendee list, and for an event update ("moved to
Sunday") the attendee wants to READ the update — not visit a profile.
Both events carry the event's `a` coordinate (31923:pubkey:identifier),
so these two types now route to /event/:pubkey/:identifier. Everything
else still lands on the actor's profile. d-tags containing ':' are
reconstructed correctly.
- useRsvp only invalidated the single-event RSVP query, so the grid /
calendar "going" counts lagged behind by up to the 30s staleTime after
RSVPing. Now also invalidates the batch-event-rsvps query so counts
update promptly.
- Social-tab empty-state copy omitted RSVPs and host updates even though
they land there; made it honest.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
f253fc55fa
commit
719dd2c4a8
4 changed files with 28 additions and 5 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "skate-spots",
|
||||
"private": true,
|
||||
"version": "1.2.34",
|
||||
"version": "1.2.35",
|
||||
"type": "module",
|
||||
"homepage": "https://skate-spots.com",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,23 @@ export function ActivityNotificationCard({
|
|||
addSuffix: true,
|
||||
});
|
||||
|
||||
// Type-aware destination. For event RSVPs (a host is notified) and event
|
||||
// updates (an attendee is notified), the useful place to land is the EVENT —
|
||||
// not the actor's profile. Both carry the event's addressable coordinate in
|
||||
// an `a` tag ("31923:pubkey:identifier"), which maps to /event/:pubkey/:id.
|
||||
const eventCoordTag =
|
||||
notification.type === 'rsvp' || notification.type === 'event-update'
|
||||
? notification.event.tags.find(([t]) => t === 'a')?.[1]
|
||||
: undefined;
|
||||
|
||||
const eventPath = (() => {
|
||||
if (!eventCoordTag) return null;
|
||||
const [kind, pubkey, ...idParts] = eventCoordTag.split(':');
|
||||
const identifier = idParts.join(':'); // d-tags may contain ':'
|
||||
if (kind !== '31923' || !pubkey || !identifier) return null;
|
||||
return `/event/${pubkey}/${identifier}`;
|
||||
})();
|
||||
|
||||
const snippet =
|
||||
(notification.type === 'reply' ||
|
||||
notification.type === 'mention' ||
|
||||
|
|
@ -110,11 +127,13 @@ export function ActivityNotificationCard({
|
|||
</div>
|
||||
);
|
||||
|
||||
// Link to the actor's profile (the always-valid in-app destination).
|
||||
if (!npub) return body;
|
||||
// Prefer landing on the event for RSVP / event-update notifications; fall back
|
||||
// to the actor's profile for everything else (the always-valid destination).
|
||||
const destination = eventPath ?? (npub ? `/${npub}` : null);
|
||||
if (!destination) return body;
|
||||
|
||||
return (
|
||||
<Link to={`/${npub}`} onClick={onNavigate} className="block">
|
||||
<Link to={destination} onClick={onNavigate} className="block">
|
||||
{body}
|
||||
</Link>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@ export function NotificationBell({ className }: { className?: string }) {
|
|||
</div>
|
||||
)}
|
||||
{!activityLoading && activity.length === 0 && (
|
||||
<EmptyState label="When people zap, react, repost, mention or reply to you, it lands here." />
|
||||
<EmptyState label="Zaps, reactions, reposts, mentions, replies, event RSVPs and host updates all land here." />
|
||||
)}
|
||||
<div className="space-y-2.5">
|
||||
{activity.map((n) => (
|
||||
|
|
|
|||
|
|
@ -199,6 +199,10 @@ export function useRsvp() {
|
|||
onSuccess: (_data, variables) => {
|
||||
const coord = eventCoord(variables.event);
|
||||
queryClient.invalidateQueries({ queryKey: ['event-rsvps', coord] });
|
||||
// Refresh the grid/calendar "going" counts too (batch query keyed by the
|
||||
// full coordinate set), so numbers update right after RSVPing rather than
|
||||
// waiting out the 30s staleTime.
|
||||
queryClient.invalidateQueries({ queryKey: ['batch-event-rsvps'] });
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue