feat(activities): restructure event detail page layout
- Move bookmark heart from top bar to the right of the title. - Replace the When/Where info cards with caption-style lines directly under the title (calendar + map-pin icons + muted text). - Move description above the organizer so it sits right under the title/info separator; push the organizer card to the bottom. - Promote the "you own N tickets" CTA (filled primary "View" button) and demote "Buy another ticket" to outline when the user already owns tickets, so the My-Tickets path is what jumps out. - Tighten ticket availability against the buy button: standalone strip removed, count rendered as an xs muted caption directly under the buy CTA.
This commit is contained in:
parent
b0ee932e77
commit
6885b64ef2
1 changed files with 56 additions and 67 deletions
|
|
@ -199,11 +199,6 @@ function goToMyTickets() {
|
||||||
<Pencil class="w-4 h-4" />
|
<Pencil class="w-4 h-4" />
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
<BookmarkButton
|
|
||||||
v-if="event"
|
|
||||||
:pubkey="event.organizer.pubkey"
|
|
||||||
:d-tag="event.id"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -233,23 +228,23 @@ function goToMyTickets() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Title + Category -->
|
<!-- Title + bookmark + captions -->
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-start gap-2 mb-2">
|
<div class="flex flex-wrap items-start gap-2 mb-2">
|
||||||
<Badge v-if="categoryLabel" variant="secondary" class="shrink-0 mt-1">
|
<Badge v-if="categoryLabel" variant="secondary" class="shrink-0">
|
||||||
{{ categoryLabel }}
|
{{ categoryLabel }}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge
|
<Badge
|
||||||
v-if="event.lnbitsStatus && event.lnbitsStatus !== 'approved'"
|
v-if="event.lnbitsStatus && event.lnbitsStatus !== 'approved'"
|
||||||
:variant="event.lnbitsStatus === 'rejected' ? 'destructive' : 'secondary'"
|
:variant="event.lnbitsStatus === 'rejected' ? 'destructive' : 'secondary'"
|
||||||
class="shrink-0 mt-1 capitalize"
|
class="shrink-0 capitalize"
|
||||||
>
|
>
|
||||||
{{ event.lnbitsStatus === 'rejected' ? 'Rejected' : 'Pending review' }}
|
{{ event.lnbitsStatus === 'rejected' ? 'Rejected' : 'Pending review' }}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge
|
<Badge
|
||||||
v-if="event.isMine"
|
v-if="event.isMine"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="shrink-0 mt-1"
|
class="shrink-0"
|
||||||
>
|
>
|
||||||
Yours
|
Yours
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
@ -257,38 +252,41 @@ function goToMyTickets() {
|
||||||
<Badge variant="outline" class="text-xs">{{ tag }}</Badge>
|
<Badge variant="outline" class="text-xs">{{ tag }}</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold text-foreground">
|
<div class="flex items-start justify-between gap-3">
|
||||||
{{ event.title }}
|
<h1 class="text-2xl sm:text-3xl font-bold text-foreground">
|
||||||
</h1>
|
{{ event.title }}
|
||||||
|
</h1>
|
||||||
|
<BookmarkButton
|
||||||
|
:pubkey="event.organizer.pubkey"
|
||||||
|
:d-tag="event.id"
|
||||||
|
class="shrink-0 mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<p v-if="event.summary" class="text-muted-foreground mt-2">
|
<p v-if="event.summary" class="text-muted-foreground mt-2">
|
||||||
{{ event.summary }}
|
{{ event.summary }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<!-- When + Where captions -->
|
||||||
|
<div class="mt-3 space-y-1 text-sm text-muted-foreground">
|
||||||
|
<div class="flex items-start gap-1.5">
|
||||||
|
<Calendar class="w-4 h-4 shrink-0 mt-0.5" />
|
||||||
|
<span>
|
||||||
|
{{ dateDisplay }}
|
||||||
|
<span v-if="event.timezone" class="opacity-70">({{ event.timezone }})</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="event.location" class="flex items-start gap-1.5">
|
||||||
|
<MapPin class="w-4 h-4 shrink-0 mt-0.5" />
|
||||||
|
<span>{{ event.location }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<!-- Info section -->
|
<!-- Description -->
|
||||||
<div class="grid gap-4 sm:grid-cols-2">
|
<div class="prose prose-sm max-w-none text-foreground">
|
||||||
<!-- When -->
|
<p class="whitespace-pre-wrap">{{ event.description }}</p>
|
||||||
<div class="bg-muted/50 rounded-lg p-4 space-y-1">
|
|
||||||
<div class="flex items-center gap-2 text-sm font-medium text-foreground">
|
|
||||||
<Calendar class="w-4 h-4" />
|
|
||||||
{{ t('events.detail.when') }}
|
|
||||||
</div>
|
|
||||||
<p class="text-sm text-muted-foreground">{{ dateDisplay }}</p>
|
|
||||||
<p v-if="event.timezone" class="text-xs text-muted-foreground/70">
|
|
||||||
{{ event.timezone }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Where -->
|
|
||||||
<div v-if="event.location" class="bg-muted/50 rounded-lg p-4 space-y-1">
|
|
||||||
<div class="flex items-center gap-2 text-sm font-medium text-foreground">
|
|
||||||
<MapPin class="w-4 h-4" />
|
|
||||||
{{ t('events.detail.location') }}
|
|
||||||
</div>
|
|
||||||
<p class="text-sm text-muted-foreground">{{ event.location }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RSVP -->
|
<!-- RSVP -->
|
||||||
|
|
@ -304,33 +302,20 @@ function goToMyTickets() {
|
||||||
|
|
||||||
<!-- Tickets — gated on the event carrying ticketInfo (set
|
<!-- Tickets — gated on the event carrying ticketInfo (set
|
||||||
by the calendar→Event converter from the AIO custom
|
by the calendar→Event converter from the AIO custom
|
||||||
tickets_* tags on the published event). Sections render
|
tickets_* tags on the published event). When the user
|
||||||
bottom-up: availability count, then existing owned
|
already owns tickets, the "you have N tickets / view"
|
||||||
tickets (when count > 0) above a Purchase CTA (when
|
card is promoted (filled primary CTA) and the buy CTA
|
||||||
capacity remains). -->
|
is demoted (outline). -->
|
||||||
<div v-if="event.ticketInfo" class="space-y-3">
|
<div v-if="event.ticketInfo" class="space-y-3">
|
||||||
<div class="flex items-center justify-center gap-1.5 text-sm text-muted-foreground">
|
|
||||||
<Ticket class="w-4 h-4 shrink-0" />
|
|
||||||
<span v-if="event.ticketInfo.available === undefined">
|
|
||||||
{{ t('events.detail.unlimitedTickets', 'Unlimited tickets') }}
|
|
||||||
</span>
|
|
||||||
<span v-else-if="event.ticketInfo.available > 0">
|
|
||||||
{{ t('events.detail.ticketsAvailable', { count: event.ticketInfo.available }) }}
|
|
||||||
</span>
|
|
||||||
<span v-else class="text-destructive font-medium">
|
|
||||||
{{ t('events.detail.soldOut') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="ownedPaidCount > 0"
|
v-if="ownedPaidCount > 0"
|
||||||
class="bg-primary/10 border border-primary/30 rounded-lg p-4 flex items-center justify-between gap-3"
|
class="bg-primary/15 border border-primary/40 rounded-lg p-4 flex items-center justify-between gap-3"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 text-sm font-medium text-foreground">
|
<div class="flex items-center gap-2 text-sm font-medium text-foreground">
|
||||||
<CheckCircle2 class="w-4 h-4 text-primary shrink-0" />
|
<CheckCircle2 class="w-4 h-4 text-primary shrink-0" />
|
||||||
{{ t('events.detail.ticketsOwned', { count: ownedPaidCount }, ownedPaidCount) }}
|
{{ t('events.detail.ticketsOwned', { count: ownedPaidCount }, ownedPaidCount) }}
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" size="sm" class="gap-1.5 shrink-0" @click="goToMyTickets">
|
<Button size="sm" class="gap-1.5 shrink-0" @click="goToMyTickets">
|
||||||
<Ticket class="w-4 h-4" />
|
<Ticket class="w-4 h-4" />
|
||||||
{{ t('events.detail.viewMyTickets', 'View in My Tickets') }}
|
{{ t('events.detail.viewMyTickets', 'View in My Tickets') }}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -343,10 +328,11 @@ function goToMyTickets() {
|
||||||
<History class="w-4 h-4 shrink-0" />
|
<History class="w-4 h-4 shrink-0" />
|
||||||
{{ t('events.detail.pastEvent', 'This event has already happened') }}
|
{{ t('events.detail.pastEvent', 'This event has already happened') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="canBuyTicket">
|
<div v-else-if="canBuyTicket" class="space-y-1">
|
||||||
<Button
|
<Button
|
||||||
class="w-full gap-1.5"
|
class="w-full gap-1.5"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
:variant="ownedPaidCount > 0 ? 'outline' : 'default'"
|
||||||
@click="openPurchaseDialog"
|
@click="openPurchaseDialog"
|
||||||
>
|
>
|
||||||
<Ticket class="w-4 h-4" />
|
<Ticket class="w-4 h-4" />
|
||||||
|
|
@ -359,6 +345,14 @@ function goToMyTickets() {
|
||||||
: `${event.ticketInfo.price} ${event.ticketInfo.currency}` }}
|
: `${event.ticketInfo.price} ${event.ticketInfo.currency}` }}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
<p class="text-xs text-muted-foreground text-center">
|
||||||
|
<span v-if="event.ticketInfo.available === undefined">
|
||||||
|
{{ t('events.detail.unlimitedTickets', 'Unlimited tickets') }}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ t('events.detail.ticketsAvailable', { count: event.ticketInfo.available }) }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
v-else-if="ownedPaidCount === 0"
|
v-else-if="ownedPaidCount === 0"
|
||||||
|
|
@ -375,6 +369,13 @@ function goToMyTickets() {
|
||||||
@update:is-open="showPurchaseDialog = $event"
|
@update:is-open="showPurchaseDialog = $event"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- External references -->
|
||||||
|
<div v-if="event.tags.length > 0" class="space-y-2">
|
||||||
|
<!-- References would go here in future phases -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
<!-- Organizer -->
|
<!-- Organizer -->
|
||||||
<div class="bg-muted/50 rounded-lg p-4">
|
<div class="bg-muted/50 rounded-lg p-4">
|
||||||
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2">
|
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2">
|
||||||
|
|
@ -382,18 +383,6 @@ function goToMyTickets() {
|
||||||
</p>
|
</p>
|
||||||
<OrganizerCard :pubkey="event.organizer.pubkey" />
|
<OrganizerCard :pubkey="event.organizer.pubkey" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<!-- Description -->
|
|
||||||
<div class="prose prose-sm max-w-none text-foreground">
|
|
||||||
<p class="whitespace-pre-wrap">{{ event.description }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- External references -->
|
|
||||||
<div v-if="event.tags.length > 0" class="space-y-2">
|
|
||||||
<!-- References would go here in future phases -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue