An event card joins and leaves the event

PrivaPub lets a persona join another server's event (owner decision
2026-10-05). The card of a remote event shows the persona's participation
(pending, accepted, rejected) with Join or Leave, except for events by
invitation or taken on another site. Joining is public on the event's
server, so the first join asks the persona once, as reactions and votes do,
remembered apart from them (PublicAct).

The e2e joins the event SocialPub's Mobilizon scenario keeps, checks the
participant on Mobilizon and PrivaPub's answer, and leaves it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-05 15:19:07 +02:00
1 parent 6f8472bc9f
commit 5e8fdc43af
12 files changed
+216 -15

No files matched your search

+51
View File
@@ -492,6 +492,35 @@
{
<span class="inline-flex gap-2 items-center"><i class="ion-md-pin"></i>@(venue.Name ?? venue.Address ?? venue.Url)</span>
}
@if (CanJoin(happening))
{
<div class="flex flex-wrap gap-2 items-center" data-participation="@(happening.Participation ?? "none")">
@switch (happening.Participation)
{
case "accepted":
<span class="badge badge-success badge-sm"><i class="ion-md-checkmark"></i>@Localizer["You are going"]</span>
break;
case "pending":
<span class="badge badge-warning badge-sm"><i class="ion-md-hourglass"></i>@Localizer["Waiting for the organiser"]</span>
break;
case "rejected":
<span class="badge badge-error badge-sm"><i class="ion-md-close-circle"></i>@Localizer["The organiser said no"]</span>
break;
}
@if (happening.Participation is "accepted" or "pending")
{
<DButton Size="DSize.Xs" Disabled="@(IsDefaultDisabled || isActing)" OnClick="() => Participate(false)" data-event-leave>
@Localizer["Leave"]
</DButton>
}
else
{
<DButton Size="DSize.Xs" Color="DColor.Primary" Disabled="@(IsDefaultDisabled || isActing)" OnClick="() => Participate(true)" data-event-join>
<i class="ion-md-people"></i>@Localizer["Join"]
</DButton>
}
</div>
}
</div>
}
@@ -582,6 +611,28 @@
Status.EmojiReactions = (updated?.Reblog ?? updated)?.EmojiReactions ?? Status.EmojiReactions;
}
// a remote event the persona can take part in from here: not one by invitation, nor one that takes its participants
// on another site
bool CanJoin(PrivaPubEvent happening) => !Compact && !IsOwn && !Post.Located && happening.JoinMode is not ("invite" or "external");
async Task Participate(bool join)
{
if (join && !await Notices.Confirm(CascadingState.PersonaId, PublicAct.Participation))
return;
isActing = true;
var result = await StatusService.Participate(Status.Id, join);
isActing = false;
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
var updated = (Status)result.Data;
if (Status.Privapub?.Event is { } happening)
happening.Participation = (updated?.Reblog ?? updated)?.Privapub?.Event?.Participation;
}
HashSet<int> pollChoices = [];
bool CanVote(Poll poll) => !poll.Expired && poll.Voted != true && !IsOwn && !Compact && !Post.Located;
+8 -3
View File
@@ -1,11 +1,16 @@
@inherits LocalizableComponentBase
@implements IDisposable
@* The one-time notice before a persona's first reaction or poll vote (PublicNoticeService), in MainLayout. *@
@* The one-time notice before a persona's first reaction or poll vote, and before it first joins an event
(PublicNoticeService), in MainLayout. *@
<DModal IsOpen="Notices.IsAsking" IsOpenChanged="Closed" CloseOnBackdrop="false" CloseText="@Localizer["Close"]">
<Heading>@Localizer["Reactions and votes are public"]</Heading>
<Heading>@(Notices.Act == PublicAct.Participation ? Localizer["Joining an event is public"] : Localizer["Reactions and votes are public"])</Heading>
<ChildContent>
<p class="text-sm">@Localizer["Anyone who can see the post can see that {0} reacted to it or voted in it. Your other personas are never named.", CascadingState.Persona?.Name ?? ""]</p>
<p class="text-sm">
@(Notices.Act == PublicAct.Participation
? Localizer["The event's server shows who takes part, {0} included. Your other personas are never named.", CascadingState.Persona?.Name ?? ""]
: Localizer["Anyone who can see the post can see that {0} reacted to it or voted in it. Your other personas are never named.", CascadingState.Persona?.Name ?? ""])
</p>
</ChildContent>
<Actions>
<DButton Size="DSize.Sm" OnClick="() => Notices.Answer(false)">@Localizer["Cancel"]</DButton>