Compare commits
3 commits
9b1b56e05d
...
b9bca36b50
| Author | SHA1 | Date | |
|---|---|---|---|
| b9bca36b50 | |||
| 345ca073af | |||
| a77bf7ff6c |
3 changed files with 108 additions and 9 deletions
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { CalendarDays, Map, Heart, Search, Plus } from 'lucide-vue-next'
|
||||
|
|
@ -17,6 +17,26 @@ const { t } = useI18n()
|
|||
const { isAuthenticated, currentUser } = useAuth()
|
||||
const activitiesStore = useActivitiesStore()
|
||||
|
||||
// Probe LNbits admin status + extension auto_approve once at auth-ready
|
||||
// so the shell-mounted dialog renders the right warning copy when an
|
||||
// owner edits their own event.
|
||||
const isAdmin = ref(false)
|
||||
const autoApprove = ref(false)
|
||||
async function probeApprovalState() {
|
||||
if (!isAuthenticated.value) return
|
||||
const wallet = currentUser.value?.wallets?.[0]
|
||||
if (!wallet?.inkey) return
|
||||
const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService
|
||||
autoApprove.value = await ticketApi.getAutoApprove(wallet.inkey)
|
||||
if (wallet.adminkey) {
|
||||
isAdmin.value = await ticketApi.isAdmin(wallet.adminkey)
|
||||
}
|
||||
}
|
||||
onMounted(probeApprovalState)
|
||||
watch(isAuthenticated, (yes) => {
|
||||
if (yes) probeApprovalState()
|
||||
})
|
||||
|
||||
// Settings dropped — theme/lang/currency now live in the shared profile sheet.
|
||||
// Create lives in the bottom nav (auth-gated): activity creation is a deliberate
|
||||
// act, surfacing it as a tab keeps it one tap away when authed and out of the
|
||||
|
|
@ -57,14 +77,38 @@ async function handleCreateEvent(eventData: CreateEventRequest) {
|
|||
if (!invoiceKey) throw new Error('No wallet available. Please log in first.')
|
||||
await ticketApi.createEvent(eventData, invoiceKey)
|
||||
}
|
||||
|
||||
async function handleUpdateEvent(eventId: string, eventData: CreateEventRequest) {
|
||||
const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService
|
||||
// PUT /events/{id} requires the event's wallet admin key.
|
||||
const wallet = (currentUser.value?.wallets ?? []).find(
|
||||
(w) => w.id === activitiesStore.editingEvent?.wallet,
|
||||
)
|
||||
const adminKey = wallet?.adminkey
|
||||
if (!adminKey) {
|
||||
throw new Error("Can't find the admin key for this event's wallet.")
|
||||
}
|
||||
await ticketApi.updateEvent(eventId, eventData, adminKey)
|
||||
}
|
||||
|
||||
function handleDialogOpenChange(open: boolean) {
|
||||
activitiesStore.showCreateDialog = open
|
||||
// Closing always clears the edit selection so the next "+ Create"
|
||||
// opens clean instead of inheriting the last-edited event.
|
||||
if (!open) activitiesStore.editingEvent = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppShell :tabs="tabs" :is-active="isActive">
|
||||
<CreateEventDialog
|
||||
:open="activitiesStore.showCreateDialog"
|
||||
@update:open="activitiesStore.showCreateDialog = $event"
|
||||
:event="activitiesStore.editingEvent"
|
||||
:is-admin="isAdmin"
|
||||
:auto-approve="autoApprove"
|
||||
:on-create-event="handleCreateEvent"
|
||||
:on-update-event="handleUpdateEvent"
|
||||
@update:open="handleDialogOpenChange"
|
||||
/>
|
||||
</AppShell>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { Activity } from '../types/activity'
|
||||
import type { TicketedEvent } from '../types/ticket'
|
||||
|
||||
/**
|
||||
* Pinia store for cached activities from Nostr relays.
|
||||
|
|
@ -14,6 +15,9 @@ export const useActivitiesStore = defineStore('activities', () => {
|
|||
/** Toggle by the standalone bottom-nav Create tab; mounted dialog lives
|
||||
* in activities-app/App.vue so it's available from every route. */
|
||||
const showCreateDialog = ref(false)
|
||||
/** When set, the shell-mounted CreateEventDialog opens in edit mode
|
||||
* for this LNbits event. Cleared when the dialog closes. */
|
||||
const editingEvent = ref<TicketedEvent | null>(null)
|
||||
|
||||
// Computed
|
||||
const activities = computed(() => Array.from(activitiesMap.value.values()))
|
||||
|
|
@ -88,6 +92,7 @@ export const useActivitiesStore = defineStore('activities', () => {
|
|||
isLoading,
|
||||
lastUpdated,
|
||||
showCreateDialog,
|
||||
editingEvent,
|
||||
|
||||
// Computed
|
||||
activities,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { format } from 'date-fns'
|
||||
|
|
@ -8,13 +8,18 @@ import { Button } from '@/components/ui/button'
|
|||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import {
|
||||
Calendar, MapPin, ArrowLeft,
|
||||
Calendar, MapPin, ArrowLeft, Pencil,
|
||||
} from 'lucide-vue-next'
|
||||
import { useActivityDetail } from '../composables/useActivityDetail'
|
||||
import BookmarkButton from '../components/BookmarkButton.vue'
|
||||
import RSVPButton from '../components/RSVPButton.vue'
|
||||
import OrganizerCard from '../components/OrganizerCard.vue'
|
||||
import { NIP52_KINDS } from '../types/nip52'
|
||||
import { useAuth } from '@/composables/useAuthService'
|
||||
import { useActivitiesStore } from '../stores/activities'
|
||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||
import type { TicketApiService } from '../services/TicketApiService'
|
||||
import type { TicketedEvent } from '../types/ticket'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
|
@ -24,6 +29,38 @@ const activityId = route.params.id as string
|
|||
const { activity, isLoading, error, reload } = useActivityDetail(activityId)
|
||||
const { dateLocale } = useDateLocale()
|
||||
|
||||
// Owner-edit affordance: the NIP-52 d-tag we use for the activity id is
|
||||
// the same as the LNbits event id (set at publish time in
|
||||
// nostr_publisher.build_nip52_event). Look the user's own events up
|
||||
// once and offer an Edit button on a match.
|
||||
const { isAuthenticated, currentUser } = useAuth()
|
||||
const activitiesStore = useActivitiesStore()
|
||||
const ownedLnbitsEvent = ref<TicketedEvent | null>(null)
|
||||
|
||||
async function loadOwnedEvent() {
|
||||
ownedLnbitsEvent.value = null
|
||||
if (!isAuthenticated.value) return
|
||||
const invoiceKey = currentUser.value?.wallets?.[0]?.inkey
|
||||
if (!invoiceKey) return
|
||||
try {
|
||||
const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService
|
||||
const mine = await ticketApi.fetchMyEvents(invoiceKey)
|
||||
ownedLnbitsEvent.value =
|
||||
(mine as TicketedEvent[]).find((e) => e.id === activityId) ?? null
|
||||
} catch {
|
||||
ownedLnbitsEvent.value = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadOwnedEvent)
|
||||
watch(isAuthenticated, () => loadOwnedEvent())
|
||||
|
||||
function openEditDialog() {
|
||||
if (!ownedLnbitsEvent.value) return
|
||||
activitiesStore.editingEvent = ownedLnbitsEvent.value
|
||||
activitiesStore.showCreateDialog = true
|
||||
}
|
||||
|
||||
const dateDisplay = computed(() => {
|
||||
if (!activity.value) return ''
|
||||
const a = activity.value
|
||||
|
|
@ -60,11 +97,24 @@ function goBack() {
|
|||
<ArrowLeft class="w-4 h-4" />
|
||||
Back
|
||||
</Button>
|
||||
<BookmarkButton
|
||||
v-if="activity"
|
||||
:pubkey="activity.organizer.pubkey"
|
||||
:d-tag="activity.id"
|
||||
/>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Button
|
||||
v-if="ownedLnbitsEvent"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="gap-1.5"
|
||||
@click="openEditDialog"
|
||||
aria-label="Edit event"
|
||||
>
|
||||
<Pencil class="w-4 h-4" />
|
||||
Edit
|
||||
</Button>
|
||||
<BookmarkButton
|
||||
v-if="activity"
|
||||
:pubkey="activity.organizer.pubkey"
|
||||
:d-tag="activity.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading -->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue