From 9acb61efcf61aa09b4aaf204d36b3252504fc497 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sun, 24 May 2026 17:39:31 +0200 Subject: [PATCH] feat(activities): "Hosting" filter chip on the activities feed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to the "My tickets" chip from #71. Where "My tickets" narrows the feed to events you're attending, "Hosting" narrows it to events you're organizing — reading `activity.isMine` which useActivities.tagOwnership() already populates from organizer pubkey match + own LNbits drafts. Naming rationale: "My events" would have been ambiguous with favorited / bookmarked. "Hosting" is short, role-oriented, and pairs as the natural counterpart to "My tickets" (attending vs. organizing). Spanish/French translations lean on the verb form ("Organizo" / "J'organise") since those languages don't have a clean noun equivalent. - useActivityFilters: onlyHosting flag, toggleHosting action, resetFilters clears it, hasActiveFilters lights up. - applyFilters filters by `a.isMine === true` when the flag is on. Composes with category / temporal / "My tickets" via the same intersection chain. - ActivitiesPage: chip rendered alongside "My tickets" with the Megaphone icon (lucide). Hidden when logged out. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/i18n/locales/en.ts | 1 + src/i18n/locales/es.ts | 1 + src/i18n/locales/fr.ts | 1 + src/i18n/types.ts | 1 + .../composables/useActivityFilters.ts | 24 ++++++++++++++++++- .../activities/views/ActivitiesPage.vue | 21 ++++++++++++---- 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 38bb64a..0c529a2 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -58,6 +58,7 @@ const messages: LocaleMessages = { thisWeek: 'This Week', thisMonth: 'This Month', myTickets: 'My tickets', + hosting: 'Hosting', }, categories: { concert: 'Concert', diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index b2603e9..e72be71 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -58,6 +58,7 @@ const messages: LocaleMessages = { thisWeek: 'Esta semana', thisMonth: 'Este mes', myTickets: 'Mis boletos', + hosting: 'Organizo', }, categories: { concert: 'Concierto', diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index 388f602..3b6ed76 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -58,6 +58,7 @@ const messages: LocaleMessages = { thisWeek: 'Cette semaine', thisMonth: 'Ce mois-ci', myTickets: 'Mes billets', + hosting: 'J\'organise', }, categories: { concert: 'Concert', diff --git a/src/i18n/types.ts b/src/i18n/types.ts index d44f236..4e5584d 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -59,6 +59,7 @@ export interface LocaleMessages { thisWeek: string thisMonth: string myTickets: string + hosting: string } categories: Record detail: { diff --git a/src/modules/activities/composables/useActivityFilters.ts b/src/modules/activities/composables/useActivityFilters.ts index 60bcb5e..4aba255 100644 --- a/src/modules/activities/composables/useActivityFilters.ts +++ b/src/modules/activities/composables/useActivityFilters.ts @@ -22,6 +22,13 @@ export function useActivityFilters() { * (this composable stays free of ticket fetching). */ const onlyOwnedTickets = ref(false) + /** + * When true, the feed is narrowed to activities the current user + * is hosting (organizer pubkey matches the signed-in user, or the + * row is a local LNbits draft of theirs). Reads `activity.isMine` + * which `useActivities.tagOwnership()` populates. + */ + const onlyHosting = ref(false) const filters = computed(() => ({ temporal: temporal.value, @@ -54,6 +61,13 @@ export function useActivityFilters() { ) } + // Hosting filter — activities the signed-in user organizes. + // Read off `activity.isMine` which `useActivities.tagOwnership()` + // populates from organizer-pubkey match + LNbits drafts. + if (onlyHosting.value) { + result = result.filter(a => a.isMine === true) + } + return result } @@ -89,17 +103,23 @@ export function useActivityFilters() { selectedCategories.value = [] selectedDate.value = undefined onlyOwnedTickets.value = false + onlyHosting.value = false } function toggleOwnedTickets() { onlyOwnedTickets.value = !onlyOwnedTickets.value } + function toggleHosting() { + onlyHosting.value = !onlyHosting.value + } + const hasActiveFilters = computed(() => temporal.value !== 'all' || selectedCategories.value.length > 0 || selectedDate.value !== undefined || - onlyOwnedTickets.value + onlyOwnedTickets.value || + onlyHosting.value ) return { @@ -108,6 +128,7 @@ export function useActivityFilters() { selectedCategories, selectedDate, onlyOwnedTickets, + onlyHosting, filters, hasActiveFilters, @@ -118,6 +139,7 @@ export function useActivityFilters() { toggleCategory, clearCategories, toggleOwnedTickets, + toggleHosting, resetFilters, } } diff --git a/src/modules/activities/views/ActivitiesPage.vue b/src/modules/activities/views/ActivitiesPage.vue index d685a11..ccfc08e 100644 --- a/src/modules/activities/views/ActivitiesPage.vue +++ b/src/modules/activities/views/ActivitiesPage.vue @@ -8,7 +8,7 @@ import { CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible' -import { SlidersHorizontal, ChevronDown, Ticket } from 'lucide-vue-next' +import { SlidersHorizontal, ChevronDown, Ticket, Megaphone } from 'lucide-vue-next' import { useActivities } from '../composables/useActivities' import { useAuth } from '@/composables/useAuthService' import ActivitySearchOverlay from '../components/ActivitySearchOverlay.vue' @@ -30,11 +30,13 @@ const { hasActiveFilters, selectedDate, onlyOwnedTickets, + onlyHosting, selectDate, setTemporal, toggleCategory, clearCategories, toggleOwnedTickets, + toggleHosting, resetFilters, subscribe, } = useActivities() @@ -79,10 +81,10 @@ function handleSelectActivity(activity: Activity) { - -
+ +
+