diff --git a/src/activities-app/App.vue b/src/activities-app/App.vue index b7ff6d6..4ac820a 100644 --- a/src/activities-app/App.vue +++ b/src/activities-app/App.vue @@ -2,17 +2,34 @@ import { computed } from 'vue' import { useRoute } from 'vue-router' import { useI18n } from 'vue-i18n' -import { CalendarDays, Map, Heart, Search } from 'lucide-vue-next' +import { CalendarDays, Map, Heart, Search, Plus } from 'lucide-vue-next' 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 { injectService, SERVICE_TOKENS } from '@/core/di-container' +import type { TicketApiService } from '@/modules/activities/services/TicketApiService' +import type { CreateEventRequest } from '@/modules/activities/types/ticket' +import CreateEventDialog from '@/modules/activities/components/CreateEventDialog.vue' const route = useRoute() const { t } = useI18n() +const { isAuthenticated, currentUser } = useAuth() +const activitiesStore = useActivitiesStore() // 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 +// way when not. Per-app placement deliberation tracked at #53. const tabs = computed(() => [ { name: t('activities.nav.feed'), icon: Search, path: '/activities' }, { name: t('activities.nav.calendar'), icon: CalendarDays, path: '/activities/calendar' }, + { + name: t('activities.createNew'), + icon: Plus, + onClick: () => { activitiesStore.showCreateDialog = true }, + disabled: !isAuthenticated.value, + }, { name: t('activities.nav.map'), icon: Map, path: '/activities/map' }, { name: t('activities.nav.favorites'), icon: Heart, path: '/activities/favorites' }, ]) @@ -31,8 +48,23 @@ function isActive(path: string): boolean { } return route.path.startsWith(path) } + +// Dialog mount lives at shell level so the Create tab works from any route +// within the activities standalone, not just /activities. +async function handleCreateEvent(eventData: CreateEventRequest) { + const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService + const invoiceKey = currentUser.value?.wallets?.[0]?.inkey + if (!invoiceKey) throw new Error('No wallet available. Please log in first.') + await ticketApi.createEvent(eventData, invoiceKey) +} diff --git a/src/components/layout/AppShell.vue b/src/components/layout/AppShell.vue index 9bfacbd..c59b54c 100644 --- a/src/components/layout/AppShell.vue +++ b/src/components/layout/AppShell.vue @@ -51,5 +51,9 @@ const isLoginPage = computed(() => route.path === '/login') + + + diff --git a/src/modules/activities/stores/activities.ts b/src/modules/activities/stores/activities.ts index 1faec7b..0387ea4 100644 --- a/src/modules/activities/stores/activities.ts +++ b/src/modules/activities/stores/activities.ts @@ -11,6 +11,9 @@ export const useActivitiesStore = defineStore('activities', () => { const activitiesMap = ref>(new Map()) const isLoading = ref(false) const lastUpdated = ref(null) + /** 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) // Computed const activities = computed(() => Array.from(activitiesMap.value.values())) @@ -84,6 +87,7 @@ export const useActivitiesStore = defineStore('activities', () => { activitiesMap, isLoading, lastUpdated, + showCreateDialog, // Computed activities, diff --git a/src/modules/activities/views/ActivitiesPage.vue b/src/modules/activities/views/ActivitiesPage.vue index 8afd937..684aa97 100644 --- a/src/modules/activities/views/ActivitiesPage.vue +++ b/src/modules/activities/views/ActivitiesPage.vue @@ -8,13 +8,8 @@ import { CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible' -import { SlidersHorizontal, ChevronDown, Plus } from 'lucide-vue-next' -import { useAuth } from '@/composables/useAuthService' +import { SlidersHorizontal, ChevronDown } from 'lucide-vue-next' import { useActivities } from '../composables/useActivities' -import CreateEventDialog from '../components/CreateEventDialog.vue' -import { injectService, SERVICE_TOKENS } from '@/core/di-container' -import type { TicketApiService } from '../services/TicketApiService' -import type { CreateEventRequest } from '../types/ticket' import ActivitySearchOverlay from '../components/ActivitySearchOverlay.vue' import TemporalFilterBar from '../components/TemporalFilterBar.vue' import CategoryFilterBar from '../components/CategoryFilterBar.vue' @@ -24,9 +19,6 @@ import type { Activity } from '../types/activity' const router = useRouter() const { t } = useI18n() -const { isAuthenticated } = useAuth() - -const showCreateDialog = ref(false) const { activities, @@ -42,7 +34,6 @@ const { clearCategories, resetFilters, subscribe, - refresh, } = useActivities() const filtersOpen = ref(false) @@ -54,39 +45,15 @@ onMounted(() => { function handleSelectActivity(activity: Activity) { router.push({ name: 'activity-detail', params: { id: activity.id } }) } - -function handleRefresh() { - refresh() -} - -async function handleCreateEvent(eventData: CreateEventRequest) { - const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService - const { currentUser } = useAuth() - const invoiceKey = currentUser.value?.wallets?.[0]?.inkey - if (!invoiceKey) { - throw new Error('No wallet available. Please log in first.') - } - await ticketApi.createEvent(eventData, invoiceKey) -}