From ea4e1960f50eef7c639c4d7ddd25ed44b27da5ca Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 23 May 2026 20:46:42 +0200 Subject: [PATCH] feat(activities): "My tickets" filter chip on ActivitiesPage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new filter chip sits below the temporal pills, hidden when the user is logged out. Clicking it narrows the feed to activities the user holds at least one paid ticket for — intersecting the existing filter pipeline (temporal / categories / date) with the ownedActivityIds set from useOwnedTickets. The coupling lives in useActivities (it already orchestrates the data + filter pipeline). useActivityFilters stays free of ticket fetching; it just carries the boolean state. resetFilters clears the chip alongside the other filters, and hasActiveFilters lights up when it's on so the "Clear all" affordance is visible. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../activities/composables/useActivities.ts | 7 +++++- .../composables/useActivityFilters.ts | 17 +++++++++++++- .../activities/views/ActivitiesPage.vue | 22 ++++++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/modules/activities/composables/useActivities.ts b/src/modules/activities/composables/useActivities.ts index 78b1bb5..67b49b5 100644 --- a/src/modules/activities/composables/useActivities.ts +++ b/src/modules/activities/composables/useActivities.ts @@ -8,6 +8,7 @@ import type { TicketedEvent } from '../types/ticket' import { ticketedEventToActivity } from '../types/activity' import { useActivitiesStore } from '../stores/activities' import { useActivityFilters } from './useActivityFilters' +import { useOwnedTickets } from './useOwnedTickets' /** * Main composable for activities discovery. @@ -17,6 +18,7 @@ export function useActivities() { const store = useActivitiesStore() const filters = useActivityFilters() const { isAuthenticated, currentUser } = useAuth() + const { ownedActivityIds } = useOwnedTickets() const isSubscribed = ref(false) const subscriptionError = ref(null) @@ -70,7 +72,10 @@ export function useActivities() { const all = store.activities.sort( (a, b) => a.startDate.getTime() - b.startDate.getTime() ) - return filters.applyFilters(all) + const filtered = filters.applyFilters(all) + if (!filters.onlyOwnedTickets.value) return filtered + const owned = ownedActivityIds.value + return filtered.filter(a => owned.has(a.id)) }) /** diff --git a/src/modules/activities/composables/useActivityFilters.ts b/src/modules/activities/composables/useActivityFilters.ts index ab3624b..60bcb5e 100644 --- a/src/modules/activities/composables/useActivityFilters.ts +++ b/src/modules/activities/composables/useActivityFilters.ts @@ -15,6 +15,13 @@ export function useActivityFilters() { const temporal = ref(DEFAULT_FILTERS.temporal) const selectedCategories = ref([]) const selectedDate = ref(undefined) + /** + * When true, the feed is narrowed to activities the current user + * holds at least one paid ticket for. Crossed with the + * `ownedActivityIds` set from useOwnedTickets in useActivities + * (this composable stays free of ticket fetching). + */ + const onlyOwnedTickets = ref(false) const filters = computed(() => ({ temporal: temporal.value, @@ -81,12 +88,18 @@ export function useActivityFilters() { temporal.value = DEFAULT_FILTERS.temporal selectedCategories.value = [] selectedDate.value = undefined + onlyOwnedTickets.value = false + } + + function toggleOwnedTickets() { + onlyOwnedTickets.value = !onlyOwnedTickets.value } const hasActiveFilters = computed(() => temporal.value !== 'all' || selectedCategories.value.length > 0 || - selectedDate.value !== undefined + selectedDate.value !== undefined || + onlyOwnedTickets.value ) return { @@ -94,6 +107,7 @@ export function useActivityFilters() { temporal, selectedCategories, selectedDate, + onlyOwnedTickets, filters, hasActiveFilters, @@ -103,6 +117,7 @@ export function useActivityFilters() { selectDate, toggleCategory, clearCategories, + toggleOwnedTickets, resetFilters, } } diff --git a/src/modules/activities/views/ActivitiesPage.vue b/src/modules/activities/views/ActivitiesPage.vue index 684aa97..d685a11 100644 --- a/src/modules/activities/views/ActivitiesPage.vue +++ b/src/modules/activities/views/ActivitiesPage.vue @@ -8,8 +8,9 @@ import { CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible' -import { SlidersHorizontal, ChevronDown } from 'lucide-vue-next' +import { SlidersHorizontal, ChevronDown, Ticket } from 'lucide-vue-next' import { useActivities } from '../composables/useActivities' +import { useAuth } from '@/composables/useAuthService' import ActivitySearchOverlay from '../components/ActivitySearchOverlay.vue' import TemporalFilterBar from '../components/TemporalFilterBar.vue' import CategoryFilterBar from '../components/CategoryFilterBar.vue' @@ -28,14 +29,18 @@ const { selectedCategories, hasActiveFilters, selectedDate, + onlyOwnedTickets, selectDate, setTemporal, toggleCategory, clearCategories, + toggleOwnedTickets, resetFilters, subscribe, } = useActivities() +const { isAuthenticated } = useAuth() + const filtersOpen = ref(false) onMounted(() => { @@ -74,6 +79,21 @@ function handleSelectActivity(activity: Activity) { + +
+ +
+