feat(card): organiser's issued check-in passes list

The event organiser can see who they've issued check-in passes to (holder +
revoked state) and revoke them — same affordance the park panel has. (Skipped
listing a holder's passes on their public profile: a pass is between a venue
and a holder, so publishing it would leak where someone has access.)
This commit is contained in:
SkateSpots 2026-10-02 13:52:16 +07:00
parent a4ef8218e3
commit 38ae1273ff
3 changed files with 35 additions and 0 deletions

View file

@ -6394,6 +6394,7 @@ walletUi: {
notOrganiser: 'Only the organiser can issue passes',
passIssued: 'Pass issued',
issueFailed: 'Could not issue the pass',
issuedPasses: 'Issued check-in passes',
},
studio: {
unsaved: 'Unsaved changes',

View file

@ -6394,6 +6394,7 @@ walletUi: {
notOrganiser: 'Solo el organizador puede emitir pases',
passIssued: 'Pase emitido',
issueFailed: 'No se pudo emitir el pase',
issuedPasses: 'Pases de acceso emitidos',
},
studio: {
unsaved: 'Cambios sin guardar',

View file

@ -277,6 +277,9 @@ export default function RiderCardPage() {
: memberships.some((m) => user?.pubkey && m.crew.admins.includes(user.pubkey)) ? 'admin'
: undefined;
const organisesEvent = myEvents.some((e) => e.pubkey === user?.pubkey);
const eventPasses = activeEvent
? issuedPasses.filter((p) => p.coord === `${CALENDAR_EVENT_KIND}:${activeEvent.pubkey}:${activeEvent.identifier}`)
: [];
const myVenues = useMemo(
() => (allSpots ?? []).filter((s) => s.pubkey === user?.pubkey),
[allSpots, user?.pubkey],
@ -1127,6 +1130,36 @@ export default function RiderCardPage() {
{t('cardUi.event.issuePassBtn')}
</Button>
<p className="text-[10px] text-gray-600">{t('cardUi.event.issuePassHint')}</p>
{eventPasses.length > 0 && (
<div className="space-y-2 border-t border-white/8 pt-3">
<p className="text-[10px] font-black uppercase tracking-widest text-gray-600">{t('cardUi.event.issuedPasses')}</p>
{eventPasses.slice(0, 6).map((p) => (
<div key={p.id} className="flex items-center justify-between gap-2 text-[11px]">
<span className="truncate text-gray-400">
{p.holder ? `${nip19.npubEncode(p.holder).slice(0, 12)}…` : '—'}
</span>
{p.status === 'revoked' ? (
<span className="shrink-0 font-bold uppercase text-red-400">{t('cardUi.parkRevoked')}</span>
) : (
<Button
size="sm"
variant="outline"
className="h-7 shrink-0 border-white/10 text-[10px]"
disabled={revokingPass}
onClick={() => {
void revokePass(p)
.then(() => toast({ title: t('cardUi.parkRevoked') }))
.catch(() => toast({ title: t('cardUi.parkRevokeFailed'), variant: 'destructive' }));
}}
>
{t('cardUi.parkRevoke')}
</Button>
)}
</div>
))}
</div>
)}
</div>
)}
</div>