From 4924e70fe869e8b8cc25dbf191a167d191418fa5 Mon Sep 17 00:00:00 2001 From: Padreug Date: Thu, 4 Jun 2026 17:32:16 +0200 Subject: [PATCH 01/36] feat(activities): restructure event detail page layout - Move bookmark heart from top bar to the right of the title. - Replace the When/Where info cards with caption-style lines directly under the title (calendar + map-pin icons + muted text). - Move description above the organizer so it sits right under the title/info separator; push the organizer card to the bottom. - Promote the "you own N tickets" CTA (filled primary "View" button) and demote "Buy another ticket" to outline when the user already owns tickets, so the My-Tickets path is what jumps out. - Tighten ticket availability against the buy button: standalone strip removed, count rendered as an xs muted caption directly under the buy CTA. --- .../activities/views/ActivityDetailPage.vue | 123 ++++++++---------- 1 file changed, 56 insertions(+), 67 deletions(-) diff --git a/src/modules/activities/views/ActivityDetailPage.vue b/src/modules/activities/views/ActivityDetailPage.vue index 38682f5..6d46e9c 100644 --- a/src/modules/activities/views/ActivityDetailPage.vue +++ b/src/modules/activities/views/ActivityDetailPage.vue @@ -199,11 +199,6 @@ function goToMyTickets() { Edit - @@ -233,23 +228,23 @@ function goToMyTickets() { /> - +
-
- +
+ {{ categoryLabel }} {{ activity.lnbitsStatus === 'rejected' ? 'Rejected' : 'Pending review' }} Yours @@ -257,38 +252,41 @@ function goToMyTickets() { {{ tag }}
-

- {{ activity.title }} -

+
+

+ {{ activity.title }} +

+ +

{{ activity.summary }}

+ + +
+
+ + + {{ dateDisplay }} + ({{ activity.timezone }}) + +
+
+ + {{ activity.location }} +
+
- -
- -
-
- - {{ t('activities.detail.when') }} -
-

{{ dateDisplay }}

-

- {{ activity.timezone }} -

-
- - -
-
- - {{ t('activities.detail.location') }} -
-

{{ activity.location }}

-
+ +
+

{{ activity.description }}

@@ -304,33 +302,20 @@ function goToMyTickets() { + tickets_* tags on the published event). When the user + already owns tickets, the "you have N tickets / view" + card is promoted (filled primary CTA) and the buy CTA + is demoted (outline). -->
-
- - - {{ t('activities.detail.unlimitedTickets', 'Unlimited tickets') }} - - - {{ t('activities.detail.ticketsAvailable', { count: activity.ticketInfo.available }) }} - - - {{ t('activities.detail.soldOut') }} - -
-
{{ t('activities.detail.ticketsOwned', { count: ownedPaidCount }, ownedPaidCount) }}
- @@ -343,10 +328,11 @@ function goToMyTickets() { {{ t('activities.detail.pastEvent', 'This event has already happened') }}
-
+
+

+ + {{ t('activities.detail.unlimitedTickets', 'Unlimited tickets') }} + + + {{ t('activities.detail.ticketsAvailable', { count: activity.ticketInfo.available }) }} + +

+ +

+ +
+ + +

@@ -382,18 +383,6 @@ function goToMyTickets() {

- - - - -
-

{{ activity.description }}

-
- - -
- -
From 13ece88f821d00f5a12112a02b5178011dd08a66 Mon Sep 17 00:00:00 2001 From: Padreug Date: Thu, 4 Jun 2026 21:51:45 +0200 Subject: [PATCH 02/36] feat(webapp): replace HubPill with hamburger sidebar menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The top-right "Back to hub" pill in each standalone is replaced by a hamburger button that opens a right-side sheet reusing the existing ProfileSheetContent (identity card, back-to-hub link, theme/lang/ currency prefs, profile settings or log-in CTA). The redundant Profile entry is removed from BottomNav and its loggedOutOpensSheet plumbing (BottomNav → AppShell) is dropped — Hub.vue still mounts ProfileSheetTrigger directly so it's unaffected. ProfileSheetContent gains an `app-nav` slot so standalones can inject app-specific nav items above the cross-app section. AppShell exposes a new optional `sidebarNav` prop that forwards items to the menu; unset on non-activities standalones, those still get the hamburger menu showing just the shared profile/preferences content. Activities passes "My tickets" (routes to /my-tickets) and "Hosting" (toggles the onlyHosting feed filter and lands on /activities), so those entries leave the inline filter chip row on ActivitiesPage and live in the sidebar instead. The "Past events" chip stays inline — it doesn't require auth and pairs visually with the temporal filters. --- src/activities-app/App.vue | 34 +++++++- src/components/layout/AppShell.vue | 19 ++--- src/components/layout/BottomNav.vue | 11 +-- src/components/layout/HubPill.vue | 24 ------ src/components/layout/ProfileSheetContent.vue | 3 + src/components/layout/StandaloneMenu.vue | 81 +++++++++++++++++++ src/i18n/locales/en.ts | 1 + src/i18n/locales/es.ts | 1 + src/i18n/locales/fr.ts | 1 + src/i18n/types.ts | 1 + .../activities/views/ActivitiesPage.vue | 38 ++------- 11 files changed, 135 insertions(+), 79 deletions(-) delete mode 100644 src/components/layout/HubPill.vue create mode 100644 src/components/layout/StandaloneMenu.vue diff --git a/src/activities-app/App.vue b/src/activities-app/App.vue index ac80413..829ba78 100644 --- a/src/activities-app/App.vue +++ b/src/activities-app/App.vue @@ -3,9 +3,10 @@ import { computed } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useI18n } from 'vue-i18n' import { toast } from 'vue-sonner' -import { CalendarDays, Map, Heart, Search, Plus } from 'lucide-vue-next' +import { CalendarDays, Map, Heart, Search, Plus, Ticket, Megaphone } from 'lucide-vue-next' import AppShell from '@/components/layout/AppShell.vue' import type { BottomTab } from '@/components/layout/BottomNav.vue' +import type { SidebarNavItem } from '@/components/layout/StandaloneMenu.vue' import { useAuth } from '@/composables/useAuthService' import { useActivitiesStore } from '@/modules/activities/stores/activities' import { useActivities } from '@/modules/activities/composables/useActivities' @@ -24,7 +25,9 @@ const { isAdmin, autoApprove } = useApprovalState() // Used to merge own LNbits drafts into the activities feed right after // the user creates or edits an event — otherwise the new draft only // surfaces on the next ActivitiesPage subscribe cycle. -const { loadOwnEvents } = useActivities() +// The hosting filter also lives on the activities composable; the +// sidebar entry below mirrors what the old in-page chip used to do. +const { loadOwnEvents, onlyHosting, toggleHosting } = useActivities() // Settings dropped — theme/lang/currency now live in the shared profile sheet. // Create lives in the bottom nav: when logged out, tapping it shows an @@ -77,6 +80,31 @@ const tabs = computed(() => [ }, ]) +// Sidebar entries shown to authed users only. "My tickets" routes to +// the dedicated /my-tickets page; "Hosting" toggles the feed filter +// (no dedicated page yet) and lands on /activities so the user can +// see the filtered list. +const sidebarNav = computed(() => { + if (!isAuthenticated.value) return [] + return [ + { + name: t('activities.filters.myTickets', 'My tickets'), + icon: Ticket, + path: '/my-tickets', + isActive: () => route.path.startsWith('/my-tickets'), + }, + { + name: t('activities.filters.hosting', 'Hosting'), + icon: Megaphone, + onClick: () => { + if (!onlyHosting.value) toggleHosting() + if (!route.path.startsWith('/activities')) router.push('/activities') + }, + isActive: () => onlyHosting.value, + }, + ] +}) + // Feed tab is active for the bare /activities route AND all sub-paths that // aren't owned by another tab (e.g. /activities/ detail pages). function isActive(path: string): boolean { @@ -123,7 +151,7 @@ function handleDialogOpenChange(open: boolean) { diff --git a/src/components/layout/HubPill.vue b/src/components/layout/HubPill.vue deleted file mode 100644 index dbe4f5a..0000000 --- a/src/components/layout/HubPill.vue +++ /dev/null @@ -1,24 +0,0 @@ - - - diff --git a/src/components/layout/ProfileSheetContent.vue b/src/components/layout/ProfileSheetContent.vue index d08735a..26667b1 100644 --- a/src/components/layout/ProfileSheetContent.vue +++ b/src/components/layout/ProfileSheetContent.vue @@ -53,6 +53,9 @@ function goLogin() {
+ + +
+import { ref, type Component } from 'vue' +import { useI18n } from 'vue-i18n' +import { useRouter } from 'vue-router' +import { Menu } from 'lucide-vue-next' +import { + Sheet, + SheetContent, + SheetTrigger, +} from '@/components/ui/sheet' +import { Separator } from '@/components/ui/separator' +import ProfileSheetContent from './ProfileSheetContent.vue' + +export interface SidebarNavItem { + /** Display label. */ + name: string + /** Lucide (or any) component to render as the leading icon. */ + icon: Component + /** Optional route to navigate to on click. */ + path?: string + /** Optional click handler. Runs after navigation if both are set. */ + onClick?: () => void + /** Visual-only "active" predicate for highlight state. */ + isActive?: () => boolean +} + +interface Props { + /** App-specific nav items rendered at the top of the sheet. */ + items?: SidebarNavItem[] +} + +const props = withDefaults(defineProps(), { items: () => [] }) + +const { t } = useI18n() +const router = useRouter() +const open = ref(false) + +function handleClick(item: SidebarNavItem) { + if (item.path) router.push(item.path) + item.onClick?.() + open.value = false +} + + + diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 19e4ac7..c2e47f5 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -23,6 +23,7 @@ const messages: LocaleMessages = { profileDescription: 'Your Nostr identity and display name.', profileLoggedOutDescription: 'Sign in or change your preferences.', login: 'Log in', + menu: 'Menu', backToHub: 'Back to hub', hub: 'Hub', theme: 'Theme', diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index bd22a88..70bd19d 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -23,6 +23,7 @@ const messages: LocaleMessages = { profileDescription: 'Tu identidad Nostr y nombre de visualización.', profileLoggedOutDescription: 'Inicia sesión o cambia tus preferencias.', login: 'Iniciar sesión', + menu: 'Menú', backToHub: 'Volver al hub', hub: 'Hub', theme: 'Tema', diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index a6ece4d..65bca74 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -23,6 +23,7 @@ const messages: LocaleMessages = { profileDescription: 'Votre identité Nostr et votre nom d\u2019affichage.', profileLoggedOutDescription: 'Connectez-vous ou modifiez vos préférences.', login: 'Se connecter', + menu: 'Menu', backToHub: 'Retour au hub', hub: 'Hub', theme: 'Thème', diff --git a/src/i18n/types.ts b/src/i18n/types.ts index 1f4e1b9..0b0b794 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -22,6 +22,7 @@ export interface LocaleMessages { profileDescription: string profileLoggedOutDescription: string login: string + menu: string backToHub: string hub: string theme: string diff --git a/src/modules/activities/views/ActivitiesPage.vue b/src/modules/activities/views/ActivitiesPage.vue index c1fd712..a158caa 100644 --- a/src/modules/activities/views/ActivitiesPage.vue +++ b/src/modules/activities/views/ActivitiesPage.vue @@ -8,9 +8,8 @@ import { CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible' -import { SlidersHorizontal, ChevronDown, Ticket, Megaphone, History } from 'lucide-vue-next' +import { SlidersHorizontal, ChevronDown, History } 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' @@ -29,22 +28,16 @@ const { selectedCategories, hasActiveFilters, selectedDate, - onlyOwnedTickets, - onlyHosting, showPast, selectDate, setTemporal, toggleCategory, clearCategories, - toggleOwnedTickets, - toggleHosting, togglePast, resetFilters, subscribe, } = useActivities() -const { isAuthenticated } = useAuth() - const filtersOpen = ref(false) onMounted(() => { @@ -83,32 +76,11 @@ function handleSelectActivity(activity: Activity) {
- +
-