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:
parent
2b376bb244
commit
e540feba44
3 changed files with 61 additions and 41 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { CalendarDays, Map, Heart, Search, Plus } from 'lucide-vue-next'
|
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 type { BottomTab } from '@/components/layout/BottomNav.vue'
|
||||||
import { useAuth } from '@/composables/useAuthService'
|
import { useAuth } from '@/composables/useAuthService'
|
||||||
import { useActivitiesStore } from '@/modules/activities/stores/activities'
|
import { useActivitiesStore } from '@/modules/activities/stores/activities'
|
||||||
|
import { useApprovalState } from '@/modules/activities/composables/useApprovalState'
|
||||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
import type { TicketApiService } from '@/modules/activities/services/TicketApiService'
|
import type { TicketApiService } from '@/modules/activities/services/TicketApiService'
|
||||||
import type { CreateEventRequest } from '@/modules/activities/types/ticket'
|
import type { CreateEventRequest } from '@/modules/activities/types/ticket'
|
||||||
|
|
@ -16,26 +17,7 @@ const route = useRoute()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { isAuthenticated, currentUser } = useAuth()
|
const { isAuthenticated, currentUser } = useAuth()
|
||||||
const activitiesStore = useActivitiesStore()
|
const activitiesStore = useActivitiesStore()
|
||||||
|
const { isAdmin, autoApprove } = useApprovalState()
|
||||||
// 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.
|
// 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
|
// 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'),
|
name: t('activities.createNew'),
|
||||||
icon: Plus,
|
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,
|
disabled: !isAuthenticated.value,
|
||||||
},
|
},
|
||||||
{ name: t('activities.nav.map'), icon: Map, path: '/activities/map' },
|
{ name: t('activities.nav.map'), icon: Map, path: '/activities/map' },
|
||||||
|
|
|
||||||
49
src/modules/activities/composables/useApprovalState.ts
Normal file
49
src/modules/activities/composables/useApprovalState.ts
Normal 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 }
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useEvents } from '../composables/useEvents'
|
import { useEvents } from '../composables/useEvents'
|
||||||
|
import { useApprovalState } from '../composables/useApprovalState'
|
||||||
import { useAuth } from '@/composables/useAuthService'
|
import { useAuth } from '@/composables/useAuthService'
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
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 { upcomingEvents, pastEvents, isLoading, error, refresh } = useEvents()
|
||||||
const { isAuthenticated, userDisplay, currentUser } = useAuth()
|
const { isAuthenticated, userDisplay, currentUser } = useAuth()
|
||||||
|
const { isAdmin, autoApprove } = useApprovalState()
|
||||||
|
|
||||||
const showPurchaseDialog = ref(false)
|
const showPurchaseDialog = ref(false)
|
||||||
const selectedEvent = ref<{
|
const selectedEvent = ref<{
|
||||||
|
|
@ -31,13 +33,6 @@ const showEventDialog = ref(false)
|
||||||
// `null` ↔ create mode; populated ↔ edit mode.
|
// `null` ↔ create mode; populated ↔ edit mode.
|
||||||
const editingEvent = ref<TicketedEvent | null>(null)
|
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(
|
const myWalletIds = computed(
|
||||||
() => new Set((currentUser.value?.wallets ?? []).map((w) => w.id))
|
() => 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)
|
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) {
|
function formatDate(dateStr: string | null | undefined) {
|
||||||
if (!dateStr) return 'Date not available'
|
if (!dateStr) return 'Date not available'
|
||||||
const date = new Date(dateStr)
|
const date = new Date(dateStr)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue