refactor(activities): useApprovalState composable

Both the activities-app shell and EventsPage probed isAdmin /
autoApprove with slightly different code, both needed by the edit
dialog's warning copy. Extract into a single composable so the probe
sequence + re-probe-on-auth-flip behavior lives in one place.

Also clear editingEvent eagerly when the bottom-nav Create tab fires
so a Create tap never inherits a stale Edit selection from a prior
close path that didn't run for any reason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-21 16:13:22 +02:00
commit e540feba44
3 changed files with 61 additions and 41 deletions

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { CalendarDays, Map, Heart, Search, Plus } from 'lucide-vue-next'
@ -7,6 +7,7 @@ import AppShell from '@/components/layout/AppShell.vue'
import type { BottomTab } from '@/components/layout/BottomNav.vue'
import { useAuth } from '@/composables/useAuthService'
import { useActivitiesStore } from '@/modules/activities/stores/activities'
import { useApprovalState } from '@/modules/activities/composables/useApprovalState'
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import type { TicketApiService } from '@/modules/activities/services/TicketApiService'
import type { CreateEventRequest } from '@/modules/activities/types/ticket'
@ -16,26 +17,7 @@ const route = useRoute()
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()
})
const { isAdmin, autoApprove } = useApprovalState()
// 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
@ -47,7 +29,12 @@ const tabs = computed<BottomTab[]>(() => [
{
name: t('activities.createNew'),
icon: Plus,
onClick: () => { activitiesStore.showCreateDialog = true },
onClick: () => {
// Defensively clear any lingering edit selection so the Create
// tap always opens in Create mode regardless of a prior Edit.
activitiesStore.editingEvent = null
activitiesStore.showCreateDialog = true
},
disabled: !isAuthenticated.value,
},
{ name: t('activities.nav.map'), icon: Map, path: '/activities/map' },

View file

@ -0,0 +1,49 @@
import { onMounted, ref, watch } from 'vue'
import { useAuth } from '@/composables/useAuthService'
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import type { TicketApiService } from '../services/TicketApiService'
/**
* Probe the events extension for the caller's approval-flow context:
*
* - `autoApprove` is the global `auto_approve` toggle on? Reads the
* invoice-key-gated public probe (v1.3.0-aio.5+), so non-admin
* wallet holders get an accurate answer.
* - `isAdmin` is the caller an LNbits admin? Tries the admin-only
* `/events/all` endpoint; a 200 means yes.
*
* Both refs default to `false` (the safer assumption for warning UI
* biased toward showing the "edit will go back to pending" copy
* when in doubt). Probe re-runs whenever auth flips to authenticated.
*
* Used by every surface that opens the edit-mode CreateEventDialog
* (activities-app/App.vue shell mount, activities EventsPage). Keeps
* the probe logic single-source-of-truth.
*/
export function useApprovalState() {
const { isAuthenticated, currentUser } = useAuth()
const isAdmin = ref(false)
const autoApprove = ref(false)
async function probe() {
if (!isAuthenticated.value) {
isAdmin.value = false
autoApprove.value = false
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(probe)
watch(isAuthenticated, (yes) => {
if (yes) probe()
})
return { isAdmin, autoApprove, probe }
}

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { computed, ref } from 'vue'
import { useEvents } from '../composables/useEvents'
import { useApprovalState } from '../composables/useApprovalState'
import { useAuth } from '@/composables/useAuthService'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
@ -18,6 +19,7 @@ import type { CreateEventRequest, TicketedEvent } from '../types/ticket'
const { upcomingEvents, pastEvents, isLoading, error, refresh } = useEvents()
const { isAuthenticated, userDisplay, currentUser } = useAuth()
const { isAdmin, autoApprove } = useApprovalState()
const showPurchaseDialog = ref(false)
const selectedEvent = ref<{
@ -31,13 +33,6 @@ const showEventDialog = ref(false)
// `null` create mode; populated edit mode.
const editingEvent = ref<TicketedEvent | null>(null)
// Probe once at mount so the dialog can render the "going to pending"
// warning accurately. Both probes degrade to safe defaults on failure
// (not admin, not auto-approved), which biases the warning toward
// being shown when in doubt.
const isAdmin = ref(false)
const autoApprove = ref(false)
const myWalletIds = computed(
() => new Set((currentUser.value?.wallets ?? []).map((w) => w.id))
)
@ -46,17 +41,6 @@ function canEdit(event: TicketedEvent): boolean {
return isAuthenticated.value && myWalletIds.value.has(event.wallet)
}
onMounted(async () => {
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)
}
})
function formatDate(dateStr: string | null | undefined) {
if (!dateStr) return 'Date not available'
const date = new Date(dateStr)