From 5541d2bc7a4fbc813d522041426842cebbb3db37 Mon Sep 17 00:00:00 2001 From: Padreug Date: Tue, 9 Jun 2026 18:18:26 +0000 Subject: [PATCH] refactor(events): rename activities module to events + wire VITE_APP_NAME for per-deployment branding (#94) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why The module was named `activities` originally to avoid colliding with Nostr's `Event` type. In practice that defense added friction without preventing confusion — the backend extension is named `events`, NIP-52 calls them "Calendar Events", and the UI already displayed "Events". The collision with `nostr-tools` `Event` is handled cleanly by the existing `import { Event as NostrEvent }` alias pattern (already in 5 files inside the module). Renaming collapses the 4-way mismatch into one consistent term. Separately, deployments needed per-instance app names. `VITE_APP_NAME` was already plumbed per-standalone via NixOS `services.webapp-standalones..displayName` (e.g. cfaun shipped `"Sortir"`, now rebranded to `"Bouge"`), but nothing in the standalone consumed it — PWA manifest, HTML title, and runtime branding were all hardcoded. This PR wires the env through every app-name display point so any deploy can flip `displayName = "Bouge"` (or anything) and pick up the brand end-to-end. ## What Nine commits on the branch: 1. **`refactor(events): rename activities module to events`** — 70-file rename. `src/modules/activities/` → `src/modules/events/`, `src/activities-app/` → `src/events-app/`, types/services/composables/views/components/store renamed (`Activity`→`Event`, `Activities`→`Events`). Routes `/activities/*` → `/events/*`; the legacy `/events` (ticketing management) moves to `/my-events` so `/events` belongs to the canonical feed. DI tokens `ACTIVITIES_*` → `EVENTS_*`. i18n namespace renamed; English domain strings updated, French/Spanish title key realigned. npm scripts `:activities` → `:events`. Build output `dist-activities/` → `dist-events/`. 2. **`feat(events): wire VITE_APP_NAME through PWA manifest, HTML, runtime`** — PWA manifest `name`/`short_name` template from `process.env.VITE_APP_NAME` with fallback `'Events'`. `events.html` uses Vite's `%VITE_APP_NAME%` substitution. `src/events-app/app.ts` + `main.ts` runtime brand string drives console logs, offline notification, and `acceptTokenFromUrl()`. `events.title` route meta sources from VITE_APP_NAME. `.env.example` updated with per-standalone scoping notes. 3. **`docs(events): update activities→events references`** — `docs/nostr-patterns/*.md` "Canonical: src/modules/activities/composables/useRSVP.ts" anchors point at renamed paths. CLAUDE.md Payment Rails Pattern section updated. 4. **`fix(events): drop lowercase from PWA description brand name`** — minor casing fix caught during verification. 5. **`fix(events): use domain noun in description, not brand name`** — manifest + HTML description switched from `"Discover ${BRAND} near you"` to static `"Discover events near you"`. Description is about *what* the app does, not the brand. 6. **`chore(events): scrub leftover sortir/activities references`** — nginx.conf.example, package.json (concurrently process label + `build:demo` BASE_PATH), router-helpers comment, useMarket comment, and vite.events.config.ts doc-comment. 7. **`refactor(events): conditional brand in console label, tighten docs`** — adds `APP_LABEL` next to `APP_NAME` in `events-app/app.ts`. Reads as `Events` on unbranded builds and `Events (Bouge)` (etc.) when `VITE_APP_NAME` is set to anything other than "Events" (case-insensitive). Used by all four console messages; `acceptTokenFromUrl` keeps the raw `APP_NAME` (it's a token-namespace identifier, not display copy). Also collapses the redundant "cfaun sets X; future deployments can override (e.g. X)" doc-comment. 8. **`i18n(events): finish activité/actividad → événement/evento sweep`** — completes the i18n rename in fr.ts and es.ts (search placeholders, favorites prompts, settings prompt) and fixes five gender-agreement errors that came along with the masculine `événement`/`evento` switch (French: `Aucune ... trouvée`→`Aucun ... trouvé`, `préférées`→`préférés`, `d'une ... la sauvegarder`→`d'un ... le sauvegarder`; Spanish: `favoritas`→`favoritos`, `guardarla`→`guardarlo`). 9. **`chore(events): finish sortir → bouge sweep in .env.example`** — four remaining doc-comment refs in `.env.example` (cfaun branding, ticket-scanner comment, section header, subdomain-mode URL example). ## Cross-repo coordination This PR has matching changes already pushed to two other repos. They have to land in a coordinated bump because the names are tightly coupled. - **`aiolabs/webapp-module` main** — commit `9d82016`. Renames `hubActivitiesUrl` → `hubEventsUrl` and `VITE_HUB_ACTIVITIES_URL` → `VITE_HUB_EVENTS_URL`. No backwards-compat shim. - **`aiolabs/server-deploy` main** — commits `f15e1eb`, `bf4698b`, `d46a520`: - `standalones.nix` `apps.events` uses `build:events` / `dist-events` / `events.html`; `hubUrlOption` maps `events → "hubEventsUrl"`; comment + cfaun-as-example doc switched from sortir/Sortir to bouge/Bouge. - `hosts/cfaun/default.nix` rebranded: `subdomain = "bouge"` + `displayName = "Bouge"`. (Native-French feedback retired the "Sortir" branding as awkward.) - `hosts/atio/default.nix` stale "activities app" comment dropped (atio's `displayName = "Eventos"` config is unchanged). server-deploy's `flake.lock` still pins the OLD webapp + OLD webapp-module, so nix builds from server-deploy main will fail until the bumps. Suggested order after this PR merges to `dev`: one server-deploy commit that does `nix flake lock --update-input webapp-module` + `--update-input webapp-demo` together. DNS for `bouge.ariege.io` needs to point at the cfaun host before the deploy lands. ## Verification - `pnpm typecheck` — clean ✓ - `pnpm build:events` (default) — `dist-events/manifest.webmanifest` has `name: "Events"`, description "Discover events near you" ✓ - `VITE_APP_NAME=Bouge pnpm build:events` — `name: "Bouge"`, HTML title "Bouge", console label resolves to `Events (Bouge)` ✓ - Cross-repo grep sweep: no `Sortir`/`sortir`/`activities`/`Activities` references in events-module scope. (Domain false positives — "Market Activity" in market dashboard, "time-based activities" in nostr-feed content filters, "Activity Lifecycle Kills" in CLAUDE.md mobile-browser docs — are unrelated and intentionally left alone.) Not verified in-browser (no GUI in this session) — needs a manual smoke at `app.ariege.io/bouge/` once the coordinated flake bump lands on cfaun. Watch for: feed loads, `/events/calendar`, `/events/map`, `/events/favorites`, `/events/:id` routes work; "My Events" appears in the user dropdown and `/my-events` shows the management page; PWA install prompt shows "Bouge". ## Out of scope (deferred) - Backend extension rename — `aiolabs/events` is already named correctly. - Nix standalone attribute name — `services.webapp-standalones.events` is already correct. - Overriding domain-noun strings ("Your event was created", RSVP labels) — those stay locale-driven; `VITE_APP_NAME` covers app-name only. - Backwards-compat redirects from `/activities/*` to `/events/*` — pre-launch, public URL was `/sortir/` (path-mode) or `sortir.ariege.io` (subdomain-mode), never `/activities/`. No external bookmarks to preserve. (`sortir.ariege.io` → `bouge.ariege.io` is a separate decision; up to the deploy operator whether to keep a 301 for word-of-mouth referrers.) Reviewed-on: https://git.atitlan.io/aiolabs/webapp/pulls/94 --- .env.example | 20 +- CLAUDE.md | 4 +- docs/nostr-patterns/README.md | 2 +- docs/nostr-patterns/publishing.md | 8 +- docs/nostr-patterns/replaceable-events.md | 8 +- docs/nostr-patterns/subscriptions.md | 4 +- activities.html => events.html | 10 +- nginx.conf.example | 18 +- package.json | 10 +- src/app.config.ts | 4 +- src/app.ts | 2 +- src/composables/useModularNavigation.ts | 12 +- src/core/di-container.ts | 6 +- src/{activities-app => events-app}/App.vue | 74 ++--- .../app.config.ts | 8 +- src/{activities-app => events-app}/app.ts | 29 +- src/{activities-app => events-app}/main.ts | 2 +- .../views/SettingsPage.vue | 16 +- src/i18n/locales/en.ts | 19 +- src/i18n/locales/es.ts | 19 +- src/i18n/locales/fr.ts | 19 +- src/i18n/types.ts | 7 +- src/lib/router-helpers.ts | 2 +- .../components/CreateActivityDialog.vue | 278 ------------------ .../views/ActivitiesCalendarPage.vue | 27 -- .../activities/views/ActivitiesMapPage.vue | 55 ---- .../components/BookmarkButton.vue | 6 +- .../components/CategoryFilterBar.vue | 10 +- .../components/CategorySelector.vue | 12 +- .../components/CreateEventDialog.vue | 2 +- .../components/DatePickerStrip.vue | 0 .../components/EventCalendarView.vue} | 60 ++-- .../components/EventCard.vue} | 80 ++--- .../components/EventList.vue} | 26 +- .../components/EventMap.vue} | 32 +- .../components/EventSearchOverlay.vue} | 48 +-- .../components/LocationPicker.vue | 0 .../components/OrganizerCard.vue | 0 .../components/PurchaseTicketDialog.vue | 0 .../components/RSVPButton.vue | 16 +- .../components/TemporalFilterBar.vue | 10 +- .../composables/useApprovalState.ts | 2 +- .../composables/useBookmarks.ts | 14 +- .../composables/useDateLocale.ts | 0 .../composables/useEventDetail.ts} | 42 +-- .../composables/useEventFilters.ts} | 64 ++-- .../composables/useEvents.ts} | 74 ++--- .../composables/useMyEvents.ts} | 4 +- .../composables/useOrganizerProfile.ts | 2 +- .../composables/useOwnedTickets.ts | 34 +-- .../composables/useRSVP.ts | 46 +-- .../composables/useTicketPurchase.ts | 0 .../composables/useTicketScanner.ts | 8 +- .../composables/useUserTickets.ts | 8 +- src/modules/{activities => events}/index.ts | 80 ++--- .../services/EventsNostrService.ts} | 52 ++-- .../services/LnbitsPaymentProvider.ts | 0 .../services/PaymentProviderInterface.ts | 0 .../services/TicketApiService.ts | 30 +- .../activities.ts => events/stores/events.ts} | 70 ++--- .../{activities => events}/types/category.ts | 8 +- .../activity.ts => events/types/event.ts} | 54 ++-- .../{activities => events}/types/filters.ts | 10 +- .../{activities => events}/types/nip52.ts | 0 .../{activities => events}/types/ticket.ts | 14 +- .../views/EventDetailPage.vue} | 140 ++++----- .../events/views/EventsCalendarPage.vue | 27 ++ .../views/EventsFavoritesPage.vue} | 36 +-- src/modules/events/views/EventsMapPage.vue | 55 ++++ .../views/EventsPage.vue} | 40 +-- .../views/MyEventsPage.vue} | 4 +- .../views/MyTicketsPage.vue | 2 +- .../views/ScanTicketsPage.vue | 18 +- src/modules/market/composables/useMarket.ts | 2 +- src/modules/market/views/CheckoutPage.vue | 2 +- src/pages/Hub.vue | 2 +- ...ivities.config.ts => vite.events.config.ts | 47 +-- 77 files changed, 849 insertions(+), 1107 deletions(-) rename activities.html => events.html (68%) rename src/{activities-app => events-app}/App.vue (61%) rename src/{activities-app => events-app}/app.config.ts (92%) rename src/{activities-app => events-app}/app.ts (80%) rename src/{activities-app => events-app}/main.ts (82%) rename src/{activities-app => events-app}/views/SettingsPage.vue (83%) delete mode 100644 src/modules/activities/components/CreateActivityDialog.vue delete mode 100644 src/modules/activities/views/ActivitiesCalendarPage.vue delete mode 100644 src/modules/activities/views/ActivitiesMapPage.vue rename src/modules/{activities => events}/components/BookmarkButton.vue (82%) rename src/modules/{activities => events}/components/CategoryFilterBar.vue (83%) rename src/modules/{activities => events}/components/CategorySelector.vue (74%) rename src/modules/{activities => events}/components/CreateEventDialog.vue (99%) rename src/modules/{activities => events}/components/DatePickerStrip.vue (100%) rename src/modules/{activities/components/ActivityCalendarView.vue => events/components/EventCalendarView.vue} (75%) rename src/modules/{activities/components/ActivityCard.vue => events/components/EventCard.vue} (70%) rename src/modules/{activities/components/ActivityList.vue => events/components/EventList.vue} (70%) rename src/modules/{activities/components/ActivityMap.vue => events/components/EventMap.vue} (68%) rename src/modules/{activities/components/ActivitySearchOverlay.vue => events/components/EventSearchOverlay.vue} (76%) rename src/modules/{activities => events}/components/LocationPicker.vue (100%) rename src/modules/{activities => events}/components/OrganizerCard.vue (100%) rename src/modules/{activities => events}/components/PurchaseTicketDialog.vue (100%) rename src/modules/{activities => events}/components/RSVPButton.vue (75%) rename src/modules/{activities => events}/components/TemporalFilterBar.vue (72%) rename src/modules/{activities => events}/composables/useApprovalState.ts (96%) rename src/modules/{activities => events}/composables/useBookmarks.ts (87%) rename src/modules/{activities => events}/composables/useDateLocale.ts (100%) rename src/modules/{activities/composables/useActivityDetail.ts => events/composables/useEventDetail.ts} (57%) rename src/modules/{activities/composables/useActivityFilters.ts => events/composables/useEventFilters.ts} (70%) rename src/modules/{activities/composables/useActivities.ts => events/composables/useEvents.ts} (64%) rename src/modules/{activities/composables/useEvents.ts => events/composables/useMyEvents.ts} (95%) rename src/modules/{activities => events}/composables/useOrganizerProfile.ts (98%) rename src/modules/{activities => events}/composables/useOwnedTickets.ts (78%) rename src/modules/{activities => events}/composables/useRSVP.ts (83%) rename src/modules/{activities => events}/composables/useTicketPurchase.ts (100%) rename src/modules/{activities => events}/composables/useTicketScanner.ts (96%) rename src/modules/{activities => events}/composables/useUserTickets.ts (95%) rename src/modules/{activities => events}/index.ts (58%) rename src/modules/{activities/services/ActivitiesNostrService.ts => events/services/EventsNostrService.ts} (78%) rename src/modules/{activities => events}/services/LnbitsPaymentProvider.ts (100%) rename src/modules/{activities => events}/services/PaymentProviderInterface.ts (100%) rename src/modules/{activities => events}/services/TicketApiService.ts (94%) rename src/modules/{activities/stores/activities.ts => events/stores/events.ts} (50%) rename src/modules/{activities => events}/types/category.ts (71%) rename src/modules/{activities/types/activity.ts => events/types/event.ts} (78%) rename src/modules/{activities => events}/types/filters.ts (66%) rename src/modules/{activities => events}/types/nip52.ts (100%) rename src/modules/{activities => events}/types/ticket.ts (94%) rename src/modules/{activities/views/ActivityDetailPage.vue => events/views/EventDetailPage.vue} (70%) create mode 100644 src/modules/events/views/EventsCalendarPage.vue rename src/modules/{activities/views/ActivitiesFavoritesPage.vue => events/views/EventsFavoritesPage.vue} (56%) create mode 100644 src/modules/events/views/EventsMapPage.vue rename src/modules/{activities/views/ActivitiesPage.vue => events/views/EventsPage.vue} (81%) rename src/modules/{activities/views/EventsPage.vue => events/views/MyEventsPage.vue} (99%) rename src/modules/{activities => events}/views/MyTicketsPage.vue (99%) rename src/modules/{activities => events}/views/ScanTicketsPage.vue (94%) rename vite.activities.config.ts => vite.events.config.ts (73%) diff --git a/.env.example b/.env.example index ecae912..b6b404b 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,10 @@ # 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 @@ -14,7 +20,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 activities ticket scanner; legacy HTTP path still +# Required for the events ticket scanner; legacy HTTP path still # works without it. VITE_LNBITS_NOSTR_TRANSPORT_PUBKEY= @@ -35,8 +41,8 @@ VITE_PUSH_NOTIFICATIONS_ENABLED=true # Image Upload Configuration (pict-rs) VITE_PICTRS_BASE_URL=https://img.mydomain.com -# Activities / Sortir Configuration -# Default language for the standalone activities app (fr, en, es) +# Events App Configuration +# Default language for the standalone events 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 @@ -64,7 +70,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_ACTIVITIES_URL=http://localhost:5181 +# VITE_HUB_EVENTS_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 @@ -74,7 +80,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_ACTIVITIES_URL=https://demo.example.com/activities/ +# VITE_HUB_EVENTS_URL=https://demo.example.com/events/ # 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/ @@ -84,11 +90,11 @@ VITE_MARKET_NADDR=naddr1qqjxgdp4vv6rydej943n2dny956rwwf4943xzwfc95ekyd3evenrsvrr # VITE_HUB_RESTAURANT_URL=https://demo.example.com/restaurant/ # # In SUBDOMAIN-MODE production: -# VITE_HUB_ACTIVITIES_URL=https://sortir.example.com +# VITE_HUB_EVENTS_URL=https://events.example.com # VITE_HUB_LIBRA_URL=https://libra.example.com # ...etc # ─────────────────────────────────────────────────────────────────────── -VITE_HUB_ACTIVITIES_URL= +VITE_HUB_EVENTS_URL= VITE_HUB_LIBRA_URL= VITE_HUB_WALLET_URL= VITE_HUB_CHAT_URL= diff --git a/CLAUDE.md b/CLAUDE.md index b85fab5..5b890de 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. Activities is the first +cash / internal-wallet) payment rails. Events 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: -- **Activities** passes `[lightning, ...one entry per organizer provider]`. +- **Events** 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/docs/nostr-patterns/README.md b/docs/nostr-patterns/README.md index b69da5a..237b681 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 -(activities, forum, market, chat, tasks, base, nostr-feed). +(events, 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 3fa9f4a..b0dbccb 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/activities/composables/useRSVP.ts` — +**Canonical:** `src/modules/events/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/activities/composables/useRSVP.ts` — local +**Canonical:** `src/modules/events/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/activities/composables/useRSVP.ts` — +**Canonical:** `src/modules/events/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/activities/composables/useRSVP.ts` — +**Canonical:** `src/modules/events/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 602a623..0cad379 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/activities/composables/useRSVP.ts` — +**Canonical:** `src/modules/events/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/activities/composables/useRSVP.ts` — +**Canonical:** `src/modules/events/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/activities/composables/useBookmarks.ts` — +**Canonical:** `src/modules/events/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/activities/composables/useRSVP.ts` — +**Canonical:** `src/modules/events/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 5921351..3dcb1cd 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/activities/composables/useRSVP.ts` — +**Canonical:** `src/modules/events/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/activities/composables/useRSVP.ts` — +**Canonical:** `src/modules/events/composables/useRSVP.ts` — `onEose: () => { isLoaded.value = true }`. `onEose` fires once, after the relay flushes everything stored that matches diff --git a/activities.html b/events.html similarity index 68% rename from activities.html rename to events.html index b555a4d..ef24349 100644 --- a/activities.html +++ b/events.html @@ -1,5 +1,5 @@ - + @@ -9,12 +9,12 @@ - Sortir — Activités - - + %VITE_APP_NAME% + +
- + diff --git a/nginx.conf.example b/nginx.conf.example index d3163ee..0c75a52 100644 --- a/nginx.conf.example +++ b/nginx.conf.example @@ -15,7 +15,7 @@ http { # PATH-MODE deployment (recommended) # # demo../ — minimal AIO chakra hub - # demo../activities/ — Sortir / activities standalone + # demo../events/ — events standalone # demo../market/ — marketplace standalone # demo../wallet/ — wallet standalone # demo../chat/ — chat standalone @@ -46,11 +46,11 @@ http { try_files $uri $uri/ /index.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; + # ── Events ────────────────────────────────────────── + location = /events { return 301 /events/$is_args$args; } + location /events/ { + alias /var/www/aio/dist-events/; + try_files $uri $uri/ /events.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../activities/ + # events.demo.. → demo../events/ # market.demo.. → demo../market/ # ─────────────────────────────────────────────────────────────────────── server { listen 8080; server_name events.demo..; - return 301 https://demo../activities/$request_uri; + return 301 https://demo../events/$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 sortir.; root /var/www/aio/dist-activities; ... } + # server { server_name events.; root /var/www/aio/dist-events; ... } # 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 1770e19..4fea5ac 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: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: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: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,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", + "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", "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/app.config.ts b/src/app.config.ts index c1447ea..6753927 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -3,8 +3,8 @@ import type { AppConfig } from './core/types' /** * Minimal AIO hub configuration. * The all-in-one app at app.${domain} ships only the base module — - * each feature module (wallet, chat, market, tasks, forum, activities, - * libra) is now its own standalone PWA at its own subdomain. + * each feature module (wallet, chat, market, tasks, forum, events, + * libra) is now its own standalone PWA at its own subdomain. */ export const appConfig: AppConfig = { modules: { diff --git a/src/app.ts b/src/app.ts index 557c324..48ce5e9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -20,7 +20,7 @@ import { installLenientAuthGuard, markAuthReady, catchAllRoute } from '@/lib/rou * * The all-in-one app at app.${domain} now ships only the base module * plus a chakra icon hub linking out to the standalone module apps - * (wallet, chat, market, tasks, forum, activities, libra). + * (wallet, chat, market, tasks, forum, events, libra). */ export async function createAppInstance() { console.log('🚀 Starting AIO hub...') diff --git a/src/composables/useModularNavigation.ts b/src/composables/useModularNavigation.ts index a29efd2..17a6abc 100644 --- a/src/composables/useModularNavigation.ts +++ b/src/composables/useModularNavigation.ts @@ -26,7 +26,7 @@ export function useModularNavigation() { items.push({ name: t('nav.home'), href: '/', requiresAuth: true }) // Add navigation items based on enabled modules - if (appConfig.modules.activities?.enabled) { + if (appConfig.modules.events?.enabled) { items.push({ name: t('nav.events'), href: '/events', @@ -67,14 +67,20 @@ export function useModularNavigation() { const userMenuItems = computed(() => { const items: NavigationItem[] = [] - // Activities module items (events + tickets) - if (appConfig.modules.activities?.enabled) { + // Events module items (tickets + my events) + if (appConfig.modules.events?.enabled) { items.push({ name: 'My Tickets', href: '/my-tickets', icon: 'Ticket', requiresAuth: true }) + items.push({ + name: 'My Events', + href: '/my-events', + icon: 'CalendarPlus', + requiresAuth: true + }) } // Market module items diff --git a/src/core/di-container.ts b/src/core/di-container.ts index fa6e762..f406812 100644 --- a/src/core/di-container.ts +++ b/src/core/di-container.ts @@ -147,9 +147,9 @@ export const SERVICE_TOKENS = { // Nostr transport (kind-21000 RPC over relays — LNbits backend) NOSTR_TRANSPORT_SERVICE: Symbol('nostrTransportService'), - // Activities services (Nostr-native events + ticketing module) - ACTIVITIES_NOSTR_SERVICE: Symbol('activitiesNostrService'), - ACTIVITIES_TICKET_API: Symbol('activitiesTicketApi'), + // Events services (Nostr-native NIP-52 calendar events + LNbits ticketing) + EVENTS_NOSTR_SERVICE: Symbol('eventsNostrService'), + EVENTS_TICKET_API: Symbol('eventsTicketApi'), TICKET_API: Symbol('ticketApi'), // Invoice services diff --git a/src/activities-app/App.vue b/src/events-app/App.vue similarity index 61% rename from src/activities-app/App.vue rename to src/events-app/App.vue index ac80413..2540dad 100644 --- a/src/activities-app/App.vue +++ b/src/events-app/App.vue @@ -7,38 +7,38 @@ 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 { useActivities } from '@/modules/activities/composables/useActivities' -import { useApprovalState } from '@/modules/activities/composables/useApprovalState' +import { useEventsStore } from '@/modules/events/stores/events' +import { useEvents } from '@/modules/events/composables/useEvents' +import { useApprovalState } from '@/modules/events/composables/useApprovalState' 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' +import type { TicketApiService } from '@/modules/events/services/TicketApiService' +import type { CreateEventRequest } from '@/modules/events/types/ticket' +import CreateEventDialog from '@/modules/events/components/CreateEventDialog.vue' const route = useRoute() const router = useRouter() const { t } = useI18n() const { isAuthenticated, currentUser } = useAuth() -const activitiesStore = useActivitiesStore() +const eventsStore = useEventsStore() const { isAdmin, autoApprove } = useApprovalState() -// Used to merge own LNbits drafts into the activities feed right after +// Used to merge own LNbits drafts into the events 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() +// surfaces on the next EventsPage subscribe cycle. +const { loadOwnEvents } = useEvents() // 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 // auth-prompt toast (mirroring BookmarkButton/RSVPButton) instead of // opening the dialog. 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('events.nav.feed'), icon: Search, path: '/events' }, + { name: t('events.nav.calendar'), icon: CalendarDays, path: '/events/calendar' }, { - name: t('activities.createNew'), + name: t('events.createNew'), icon: Plus, onClick: () => { if (!isAuthenticated.value) { - toast.info('Log in to create an activity', { + toast.info('Log in to create an event', { action: { label: 'Log in', onClick: () => router.push('/login'), @@ -48,52 +48,52 @@ const tabs = computed(() => [ } // Defensively clear any lingering edit selection so the Create // tap always opens in Create mode regardless of a prior Edit. - activitiesStore.editingEvent = null - activitiesStore.showCreateDialog = true + eventsStore.editingEvent = null + eventsStore.showCreateDialog = true }, disabled: !isAuthenticated.value, }, - { name: t('activities.nav.map'), icon: Map, path: '/activities/map' }, + { name: t('events.nav.map'), icon: Map, path: '/events/map' }, { - name: t('activities.nav.favorites'), + name: t('events.nav.favorites'), icon: Heart, // path kept so the tab stays active-highlighted while the user is - // on /activities/favorites; onClick wins for the actual tap so we + // on /events/favorites; onClick wins for the actual tap so we // can gate on auth (mirrors the Create tab pattern above). - path: '/activities/favorites', + path: '/events/favorites', onClick: () => { if (!isAuthenticated.value) { - toast.info(t('activities.favorites.loginPrompt'), { + toast.info(t('events.favorites.loginPrompt'), { action: { - label: t('activities.favorites.logIn'), + label: t('events.favorites.logIn'), onClick: () => router.push('/login'), }, }) return } - router.push('/activities/favorites') + router.push('/events/favorites') }, disabled: !isAuthenticated.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). +// Feed tab is active for the bare /events route AND all sub-paths that +// aren't owned by another tab (e.g. /events/ detail pages). function isActive(path: string): boolean { - if (path === '/activities') { + if (path === '/events') { return ( - route.path === '/activities' || - (route.path.startsWith('/activities/') && - !route.path.startsWith('/activities/calendar') && - !route.path.startsWith('/activities/map') && - !route.path.startsWith('/activities/favorites')) + route.path === '/events' || + (route.path.startsWith('/events/') && + !route.path.startsWith('/events/calendar') && + !route.path.startsWith('/events/map') && + !route.path.startsWith('/events/favorites')) ) } 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. +// within the events standalone, not just /events. async function handleCreateEvent(eventData: CreateEventRequest) { const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService const invoiceKey = currentUser.value?.wallets?.[0]?.inkey @@ -105,7 +105,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 === activitiesStore.editingEvent?.wallet, + (w) => w.id === eventsStore.editingEvent?.wallet, ) const adminKey = wallet?.adminkey if (!adminKey) { @@ -115,18 +115,18 @@ async function handleUpdateEvent(eventId: string, eventData: CreateEventRequest) } function handleDialogOpenChange(open: boolean) { - activitiesStore.showCreateDialog = open + eventsStore.showCreateDialog = open // Closing always clears the edit selection so the next "+ Create" // opens clean instead of inheriting the last-edited event. - if (!open) activitiesStore.editingEvent = null + if (!open) eventsStore.editingEvent = null }