diff --git a/.env.example b/.env.example index b6b404b..ecae912 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,4 @@ # App Configuration -# Per-standalone display name — sets browser tab title, PWA install -# name/short_name, and the brand string in console logs. Each standalone -# (events, wallet, chat, market, …) gets its own VITE_APP_NAME at build -# time via NixOS `services.webapp-standalones..displayName` (see -# server-deploy). cfaun ships the events app as "Bouge"; defaults to -# "Events" / "Wallet" / etc. when unset. VITE_APP_NAME=MyApp # Nostr Configuration @@ -20,7 +14,7 @@ VITE_WEBSOCKET_ENABLED=true # LNbits Nostr-transport server pubkey (kind-21000 RPC endpoint). # Logged by the LNbits server at startup: # `Nostr transport: starting with pubkey ... on N relay(s)` -# Required for the events ticket scanner; legacy HTTP path still +# Required for the activities ticket scanner; legacy HTTP path still # works without it. VITE_LNBITS_NOSTR_TRANSPORT_PUBKEY= @@ -41,8 +35,8 @@ VITE_PUSH_NOTIFICATIONS_ENABLED=true # Image Upload Configuration (pict-rs) VITE_PICTRS_BASE_URL=https://img.mydomain.com -# Events App Configuration -# Default language for the standalone events app (fr, en, es) +# Activities / Sortir Configuration +# Default language for the standalone activities app (fr, en, es) VITE_DEFAULT_LOCALE=fr # Default map center as "lat,lng" (defaults to France center if not set) VITE_DEFAULT_MAP_CENTER=42.9667,1.6000 @@ -70,7 +64,7 @@ VITE_MARKET_NADDR=naddr1qqjxgdp4vv6rydej943n2dny956rwwf4943xzwfc95ekyd3evenrsvrr # # In LOCAL DEV with `npm run dev:all` use the per-app dev ports (defined # in the vite configs): -# VITE_HUB_EVENTS_URL=http://localhost:5181 +# VITE_HUB_ACTIVITIES_URL=http://localhost:5181 # VITE_HUB_LIBRA_URL=http://localhost:5180 # VITE_HUB_WALLET_URL=http://localhost:5182 # VITE_HUB_CHAT_URL=http://localhost:5183 @@ -80,7 +74,7 @@ VITE_MARKET_NADDR=naddr1qqjxgdp4vv6rydej943n2dny956rwwf4943xzwfc95ekyd3evenrsvrr # VITE_HUB_RESTAURANT_URL=http://localhost:5187 # # In PATH-MODE production (recommended for demo) — note the trailing slash: -# VITE_HUB_EVENTS_URL=https://demo.example.com/events/ +# VITE_HUB_ACTIVITIES_URL=https://demo.example.com/activities/ # VITE_HUB_LIBRA_URL=https://demo.example.com/libra/ # VITE_HUB_WALLET_URL=https://demo.example.com/wallet/ # VITE_HUB_CHAT_URL=https://demo.example.com/chat/ @@ -90,11 +84,11 @@ VITE_MARKET_NADDR=naddr1qqjxgdp4vv6rydej943n2dny956rwwf4943xzwfc95ekyd3evenrsvrr # VITE_HUB_RESTAURANT_URL=https://demo.example.com/restaurant/ # # In SUBDOMAIN-MODE production: -# VITE_HUB_EVENTS_URL=https://events.example.com +# VITE_HUB_ACTIVITIES_URL=https://sortir.example.com # VITE_HUB_LIBRA_URL=https://libra.example.com # ...etc # ─────────────────────────────────────────────────────────────────────── -VITE_HUB_EVENTS_URL= +VITE_HUB_ACTIVITIES_URL= VITE_HUB_LIBRA_URL= VITE_HUB_WALLET_URL= VITE_HUB_CHAT_URL= diff --git a/CLAUDE.md b/CLAUDE.md index 5b890de..b85fab5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -717,7 +717,7 @@ VITE_WEBSOCKET_ENABLED=true ## Payment Rails Pattern Shared primitives for modules that mix Lightning + fiat (and, future, -cash / internal-wallet) payment rails. Events is the first +cash / internal-wallet) payment rails. Activities is the first consumer; restaurant + marketplace will adopt the same primitives as their backends gain fiat support. @@ -784,7 +784,7 @@ type PaymentMethod = { ``` Module usage: -- **Events** passes `[lightning, ...one entry per organizer provider]`. +- **Activities** passes `[lightning, ...one entry per organizer provider]`. - **Restaurant** (future) passes the subset of `[lightning, cash, internal, ...fiat providers]` enabled by the restaurant's `accepts_*` flags. diff --git a/events.html b/activities.html similarity index 68% rename from events.html rename to activities.html index ef24349..b555a4d 100644 --- a/events.html +++ b/activities.html @@ -1,5 +1,5 @@ - + @@ -9,12 +9,12 @@ - %VITE_APP_NAME% - - + Sortir — Activités + +
- + diff --git a/docs/nostr-patterns/README.md b/docs/nostr-patterns/README.md index 237b681..b69da5a 100644 --- a/docs/nostr-patterns/README.md +++ b/docs/nostr-patterns/README.md @@ -1,7 +1,7 @@ # Nostr patterns Living reference for reusable Nostr patterns that show up across modules -(events, forum, market, chat, tasks, base, nostr-feed). +(activities, forum, market, chat, tasks, base, nostr-feed). **Read before writing any new Nostr code in this repo.** **Update whenever you introduce, refine, or correct a pattern.** Each section has a "Canonical diff --git a/docs/nostr-patterns/publishing.md b/docs/nostr-patterns/publishing.md index b0dbccb..3fa9f4a 100644 --- a/docs/nostr-patterns/publishing.md +++ b/docs/nostr-patterns/publishing.md @@ -2,7 +2,7 @@ ## Treat `result.success === 0` as failure, not success -**Canonical:** `src/modules/events/composables/useRSVP.ts` — +**Canonical:** `src/modules/activities/composables/useRSVP.ts` — `if (!result || result.success <= 0) return null`. ```ts @@ -23,7 +23,7 @@ composable. Don't write code that silently treats both as success. ## Optimistic-on-success, not optimistic-on-click -**Canonical:** `src/modules/events/composables/useRSVP.ts` — local +**Canonical:** `src/modules/activities/composables/useRSVP.ts` — local cache update after the `await` resolves with `success > 0`, before the relay echoes the event back through the subscription. @@ -39,7 +39,7 @@ button flip twice. ## Pending-coord debounce: disable the button during in-flight publish -**Canonical:** `src/modules/events/composables/useRSVP.ts` — +**Canonical:** `src/modules/activities/composables/useRSVP.ts` — `pendingCoords: ref>` + `isPending(...)` predicate + `try { … } finally { pendingCoords.value.delete(coord) }`. @@ -66,7 +66,7 @@ while a previous publish on activity B is still flying. A global ## Sign with `nostr-tools.finalizeEvent`, take privkey as bytes -**Canonical:** `src/modules/events/composables/useRSVP.ts` — +**Canonical:** `src/modules/activities/composables/useRSVP.ts` — `hexToUint8Array` helper + `finalizeEvent(template, signingKey)`. `finalizeEvent` expects a `Uint8Array`, not a hex string. Several composables diff --git a/docs/nostr-patterns/replaceable-events.md b/docs/nostr-patterns/replaceable-events.md index 0cad379..602a623 100644 --- a/docs/nostr-patterns/replaceable-events.md +++ b/docs/nostr-patterns/replaceable-events.md @@ -7,7 +7,7 @@ in this file follows from that single fact. ## Strictly-monotonic `created_at` per coord -**Canonical:** `src/modules/events/composables/useRSVP.ts` — +**Canonical:** `src/modules/activities/composables/useRSVP.ts` — `lastPublishAt` map + the `Math.max(now, previous + 1)` line. ```ts @@ -31,7 +31,7 @@ than the last click on the same coord. ## Per-pubkey latest-wins state for derived counts -**Canonical:** `src/modules/events/composables/useRSVP.ts` — +**Canonical:** `src/modules/activities/composables/useRSVP.ts` — `rsvpStates: ref>>` + `upsertRSVPState` + `getRSVPCount` (count entries where status === 'accepted'). @@ -51,7 +51,7 @@ any "who's currently in state X" question. ## Replaceable list, full-rewrite on toggle -**Canonical:** `src/modules/events/composables/useBookmarks.ts` — +**Canonical:** `src/modules/activities/composables/useBookmarks.ts` — NIP-51 kind 10003 bookmark list. For replaceable lists (10003 bookmarks, 10000 mute list, 10006 communities, @@ -66,7 +66,7 @@ diverges on next refresh. ## Vue 3 reactivity for nested `ref` -**Canonical:** `src/modules/events/composables/useRSVP.ts` — +**Canonical:** `src/modules/activities/composables/useRSVP.ts` — `upsertRSVPState` (the `rsvpStates.value.set(coord, inner)` after mutating `inner`). diff --git a/docs/nostr-patterns/subscriptions.md b/docs/nostr-patterns/subscriptions.md index 3dcb1cd..5921351 100644 --- a/docs/nostr-patterns/subscriptions.md +++ b/docs/nostr-patterns/subscriptions.md @@ -2,7 +2,7 @@ ## Subscribe, store the unsubscribe handle, clean up on unmount -**Canonical:** `src/modules/events/composables/useRSVP.ts` — +**Canonical:** `src/modules/activities/composables/useRSVP.ts` — `loadRSVPs()` (subscribe block) + the matching `onUnmounted(() => unsubscribe?.())`. ```ts @@ -33,7 +33,7 @@ session-long vs view-long), not by accident. ## EOSE means "backfill done", not "all events delivered" -**Canonical:** `src/modules/events/composables/useRSVP.ts` — +**Canonical:** `src/modules/activities/composables/useRSVP.ts` — `onEose: () => { isLoaded.value = true }`. `onEose` fires once, after the relay flushes everything stored that matches diff --git a/nginx.conf.example b/nginx.conf.example index 0c75a52..d3163ee 100644 --- a/nginx.conf.example +++ b/nginx.conf.example @@ -15,7 +15,7 @@ http { # PATH-MODE deployment (recommended) # # demo../ — minimal AIO chakra hub - # demo../events/ — events standalone + # demo../activities/ — Sortir / activities standalone # demo../market/ — marketplace standalone # demo../wallet/ — wallet standalone # demo../chat/ — chat standalone @@ -46,11 +46,11 @@ http { try_files $uri $uri/ /index.html; } - # ── Events ────────────────────────────────────────── - location = /events { return 301 /events/$is_args$args; } - location /events/ { - alias /var/www/aio/dist-events/; - try_files $uri $uri/ /events.html; + # ── Activities (Sortir) ────────────────────────────────────────── + location = /activities { return 301 /activities/$is_args$args; } + location /activities/ { + alias /var/www/aio/dist-activities/; + try_files $uri $uri/ /activities.html; } # ── Market ─────────────────────────────────────────────────────── @@ -107,13 +107,13 @@ http { # If you want pretty subdomain URLs that funnel into the path-mode # canonical, add 301 redirects per app. Example: # - # events.demo.. → demo../events/ + # events.demo.. → demo../activities/ # market.demo.. → demo../market/ # ─────────────────────────────────────────────────────────────────────── server { listen 8080; server_name events.demo..; - return 301 https://demo../events/$request_uri; + return 301 https://demo../activities/$request_uri; } server { listen 8080; @@ -154,7 +154,7 @@ http { # # server { server_name app.; root /var/www/aio/dist; ... } # server { server_name market.; root /var/www/aio/dist-market; ... } - # server { server_name events.; root /var/www/aio/dist-events; ... } + # server { server_name sortir.; root /var/www/aio/dist-activities; ... } # server { server_name wallet.; root /var/www/aio/dist-wallet; ... } # server { server_name chat.; root /var/www/aio/dist-chat; ... } # server { server_name forum.; root /var/www/aio/dist-forum; ... } diff --git a/package.json b/package.json index 4fea5ac..1770e19 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "build": "vue-tsc -b && vite build", "preview": "vite preview --host", "analyze": "vite build --mode analyze", - "dev:events": "vite --host --config vite.events.config.ts", - "build:events": "vue-tsc -b && vite build --config vite.events.config.ts", - "preview:events": "vite preview --host --config vite.events.config.ts", + "dev:activities": "vite --host --config vite.activities.config.ts", + "build:activities": "vue-tsc -b && vite build --config vite.activities.config.ts", + "preview:activities": "vite preview --host --config vite.activities.config.ts", "dev:libra": "vite --host --config vite.libra.config.ts", "build:libra": "vue-tsc -b && vite build --config vite.libra.config.ts", "preview:libra": "vite preview --host --config vite.libra.config.ts", @@ -33,8 +33,8 @@ "dev:restaurant": "vite --host --config vite.restaurant.config.ts", "build:restaurant": "vue-tsc -b && vite build --config vite.restaurant.config.ts", "preview:restaurant": "vite preview --host --config vite.restaurant.config.ts", - "dev:all": "concurrently -n hub,libra,events,wallet,chat,forum,market,tasks,restaurant -c blue,magenta,cyan,yellow,green,blue,red,gray,green \"npm:dev\" \"npm:dev:libra\" \"npm:dev:events\" \"npm:dev:wallet\" \"npm:dev:chat\" \"npm:dev:forum\" \"npm:dev:market\" \"npm:dev:tasks\" \"npm:dev:restaurant\"", - "build:demo": "npm run build && VITE_BASE_PATH=/events/ npm run build:events && VITE_BASE_PATH=/libra/ npm run build:libra && VITE_BASE_PATH=/wallet/ npm run build:wallet && VITE_BASE_PATH=/chat/ npm run build:chat && VITE_BASE_PATH=/forum/ npm run build:forum && VITE_BASE_PATH=/market/ npm run build:market && VITE_BASE_PATH=/tasks/ npm run build:tasks && VITE_BASE_PATH=/restaurant/ npm run build:restaurant", + "dev:all": "concurrently -n hub,libra,sortir,wallet,chat,forum,market,tasks,restaurant -c blue,magenta,cyan,yellow,green,blue,red,gray,green \"npm:dev\" \"npm:dev:libra\" \"npm:dev:activities\" \"npm:dev:wallet\" \"npm:dev:chat\" \"npm:dev:forum\" \"npm:dev:market\" \"npm:dev:tasks\" \"npm:dev:restaurant\"", + "build:demo": "npm run build && VITE_BASE_PATH=/sortir/ npm run build:activities && VITE_BASE_PATH=/libra/ npm run build:libra && VITE_BASE_PATH=/wallet/ npm run build:wallet && VITE_BASE_PATH=/chat/ npm run build:chat && VITE_BASE_PATH=/forum/ npm run build:forum && VITE_BASE_PATH=/market/ npm run build:market && VITE_BASE_PATH=/tasks/ npm run build:tasks && VITE_BASE_PATH=/restaurant/ npm run build:restaurant", "electron:dev": "concurrently \"vite --host\" \"electron-forge start\"", "electron:build": "vue-tsc -b && vite build && electron-builder", "electron:package": "electron-builder", diff --git a/src/events-app/App.vue b/src/activities-app/App.vue similarity index 65% rename from src/events-app/App.vue rename to src/activities-app/App.vue index ee16fb1..663fb38 100644 --- a/src/events-app/App.vue +++ b/src/activities-app/App.vue @@ -7,59 +7,59 @@ import { Home, Map, Heart, Ticket, Megaphone } 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 { useEventsStore } from '@/modules/events/stores/events' -import { useEvents } from '@/modules/events/composables/useEvents' -import { useApprovalState } from '@/modules/events/composables/useApprovalState' +import { useActivitiesStore } from '@/modules/activities/stores/activities' +import { useActivities } from '@/modules/activities/composables/useActivities' +import { useApprovalState } from '@/modules/activities/composables/useApprovalState' import { injectService, SERVICE_TOKENS } from '@/core/di-container' -import type { TicketApiService } from '@/modules/events/services/TicketApiService' -import type { CreateEventRequest } from '@/modules/events/types/ticket' -import CreateEventDialog from '@/modules/events/components/CreateEventDialog.vue' +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 router = useRouter() const { t } = useI18n() const { isAuthenticated, currentUser } = useAuth() -const eventsStore = useEventsStore() +const activitiesStore = useActivitiesStore() const { isAdmin, autoApprove } = useApprovalState() -// Used to merge own LNbits drafts into the events feed right after +// 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 EventsPage subscribe cycle. `onlyHosting` +// surfaces on the next ActivitiesPage subscribe cycle. `onlyHosting` // is the feed filter that backs the Hosting bottom-nav tab — tapping // it toggles the filter on; Home tab toggles it off. -const { loadOwnEvents, onlyHosting, toggleHosting } = useEvents() +const { loadOwnEvents, onlyHosting, toggleHosting } = useActivities() -// True for /events and its sub-routes (incl. detail pages) but +// True for /activities and its sub-routes (incl. detail pages) but // not for the routes owned by other tabs (map/favorites). Used by // both Home and Hosting active-state predicates so the highlight // only shifts based on the onlyHosting flag while you're in the feed. function inFeedRoute(): boolean { - if (route.path.startsWith('/events/map')) return false - if (route.path.startsWith('/events/favorites')) return false - return route.path === '/events' || route.path.startsWith('/events/') + if (route.path.startsWith('/activities/map')) return false + if (route.path.startsWith('/activities/favorites')) return false + return route.path === '/activities' || route.path.startsWith('/activities/') } const tabs = computed(() => [ { - name: t('events.nav.feed'), + name: t('activities.nav.feed'), icon: Home, onClick: () => { // Tapping Home clears the hosting filter so the feed always // returns to the unfiltered view, regardless of where the // user just came from. if (onlyHosting.value) toggleHosting() - if (route.path !== '/events') router.push('/events') + if (route.path !== '/activities') router.push('/activities') }, isActive: () => inFeedRoute() && !onlyHosting.value, }, { - name: t('events.filters.myTickets'), + name: t('activities.filters.myTickets'), icon: Ticket, path: '/my-tickets', onClick: () => { if (!isAuthenticated.value) { - toast.info(t('events.detail.loginToBuyTickets'), { + toast.info(t('activities.detail.loginToBuyTickets'), { action: { - label: t('events.detail.logIn'), + label: t('activities.detail.logIn'), onClick: () => router.push('/login'), }, }) @@ -70,43 +70,43 @@ const tabs = computed(() => [ disabled: !isAuthenticated.value, }, { - name: t('events.filters.hosting'), + name: t('activities.filters.hosting'), icon: Megaphone, onClick: () => { if (!isAuthenticated.value) { - toast.info(t('events.hosting.loginPrompt', 'Log in to manage your hosted events'), { + toast.info(t('activities.hosting.loginPrompt', 'Log in to manage your hosted activities'), { action: { - label: t('events.favorites.logIn'), + label: t('activities.favorites.logIn'), onClick: () => router.push('/login'), }, }) return } if (!onlyHosting.value) toggleHosting() - if (route.path !== '/events') router.push('/events') + if (route.path !== '/activities') router.push('/activities') }, isActive: () => inFeedRoute() && onlyHosting.value, disabled: !isAuthenticated.value, }, - { name: t('events.nav.map'), icon: Map, path: '/events/map' }, + { name: t('activities.nav.map'), icon: Map, path: '/activities/map' }, { - name: t('events.nav.favorites'), + name: t('activities.nav.favorites'), icon: Heart, // path kept so the tab stays active-highlighted while the user is - // on /events/favorites; onClick wins for the actual tap so we + // on /activities/favorites; onClick wins for the actual tap so we // can gate on auth (mirrors the Create tab pattern above). - path: '/events/favorites', + path: '/activities/favorites', onClick: () => { if (!isAuthenticated.value) { - toast.info(t('events.favorites.loginPrompt'), { + toast.info(t('activities.favorites.loginPrompt'), { action: { - label: t('events.favorites.logIn'), + label: t('activities.favorites.logIn'), onClick: () => router.push('/login'), }, }) return } - router.push('/events/favorites') + router.push('/activities/favorites') }, disabled: !isAuthenticated.value, }, @@ -118,7 +118,7 @@ function isActive(path: string): boolean { } // Dialog mount lives at shell level so the Create tab works from any route -// within the events standalone, not just /events. +// 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 @@ -130,7 +130,7 @@ async function handleUpdateEvent(eventId: string, eventData: CreateEventRequest) const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService // PUT /events/{id} requires the event's wallet admin key. const wallet = (currentUser.value?.wallets ?? []).find( - (w) => w.id === eventsStore.editingEvent?.wallet, + (w) => w.id === activitiesStore.editingEvent?.wallet, ) const adminKey = wallet?.adminkey if (!adminKey) { @@ -140,18 +140,18 @@ async function handleUpdateEvent(eventId: string, eventData: CreateEventRequest) } function handleDialogOpenChange(open: boolean) { - eventsStore.showCreateDialog = open + activitiesStore.showCreateDialog = open // Closing always clears the edit selection so the next "+ Create" // opens clean instead of inheriting the last-edited event. - if (!open) eventsStore.editingEvent = null + if (!open) activitiesStore.editingEvent = null }