diff --git a/.env.example b/.env.example index 0119d7e..c92c023 100644 --- a/.env.example +++ b/.env.example @@ -28,6 +28,12 @@ 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) +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 + # Market Configuration VITE_MARKET_NADDR=naddr1qqjxgdp4vv6rydej943n2dny956rwwf4943xzwfc95ekyd3evenrsvrrvc6r2qf8waehxw309akxucnfw3ejuct5d96xcctw9e5k7tmwdaehgunjv4kxz7f0v96xjmczyqrfrfkxv3m8t4elpe28x065z30zszaaqa4u0744qcmadsz3y50cjqcyqqq82scmcafla # OBSOLETE: Not used in codebase - market uses VITE_NOSTR_RELAYS instead diff --git a/.gitignore b/.gitignore index 211cb91..0e23399 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ lerna-debug.log* node_modules dist +dist-* dist-ssr *.local diff --git a/activities.html b/activities.html new file mode 100644 index 0000000..d227e52 --- /dev/null +++ b/activities.html @@ -0,0 +1,19 @@ + + + + + + + + + + + Sortir — Activités + + + + +
+ + + diff --git a/package-lock.json b/package-lock.json index 2222626..24eae4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,10 @@ "date-fns": "^4.1.0", "electron-squirrel-startup": "^1.0.1", "fuse.js": "^7.0.0", + "leaflet": "^1.9.4", "light-bolt11-decoder": "^3.2.0", "lucide-vue-next": "^0.474.0", + "ngeohash": "^0.6.3", "nostr-tools": "^2.10.4", "pinia": "^2.3.1", "qr-scanner": "^1.4.2", @@ -48,6 +50,8 @@ "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.0.12", + "@types/leaflet": "^1.9.21", + "@types/ngeohash": "^0.6.8", "@types/node": "^22.18.1", "@types/qrcode": "^1.5.5", "@types/rollup-plugin-visualizer": "^4.2.3", @@ -5067,6 +5071,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -5084,6 +5095,23 @@ "@types/node": "*" } }, + "node_modules/@types/leaflet": { + "version": "1.9.21", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/ngeohash": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@types/ngeohash/-/ngeohash-0.6.8.tgz", + "integrity": "sha512-A90x3HMwE1yXbWCnd0ztHzv8rAQPjwTzX2diYI/6OrWm/3oairDaehw5WPWJFgZ+8+J/OuF99IbipmMa2le6tQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.18.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.1.tgz", @@ -9755,6 +9783,12 @@ "json-buffer": "3.0.1" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -10885,6 +10919,15 @@ "node": ">= 0.6" } }, + "node_modules/ngeohash": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/ngeohash/-/ngeohash-0.6.3.tgz", + "integrity": "sha512-kltF0cOxgx1AbmVzKxYZaoB0aj7mOxZeHaerEtQV0YaqnkXNq26WWqMmJ6lTqShYxVRWZ/mwvvTrNeOwdslWiw==", + "license": "MIT", + "engines": { + "node": ">=v0.2.0" + } + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", diff --git a/package.json b/package.json index 56c29f6..d73e560 100644 --- a/package.json +++ b/package.json @@ -9,6 +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", "electron:dev": "concurrently \"vite --host\" \"electron-forge start\"", "electron:build": "vue-tsc -b && vite build && electron-builder", "electron:package": "electron-builder", @@ -27,8 +30,10 @@ "date-fns": "^4.1.0", "electron-squirrel-startup": "^1.0.1", "fuse.js": "^7.0.0", + "leaflet": "^1.9.4", "light-bolt11-decoder": "^3.2.0", "lucide-vue-next": "^0.474.0", + "ngeohash": "^0.6.3", "nostr-tools": "^2.10.4", "pinia": "^2.3.1", "qr-scanner": "^1.4.2", @@ -57,6 +62,8 @@ "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.0.12", + "@types/leaflet": "^1.9.21", + "@types/ngeohash": "^0.6.8", "@types/node": "^22.18.1", "@types/qrcode": "^1.5.5", "@types/rollup-plugin-visualizer": "^4.2.3", diff --git a/src/activities-app/App.vue b/src/activities-app/App.vue new file mode 100644 index 0000000..bc520ae --- /dev/null +++ b/src/activities-app/App.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/activities-app/app.config.ts b/src/activities-app/app.config.ts new file mode 100644 index 0000000..eaada70 --- /dev/null +++ b/src/activities-app/app.config.ts @@ -0,0 +1,62 @@ +import type { AppConfig } from '@/core/types' + +function parseMapCenter(envValue: string | undefined, fallback: { lat: number; lng: number }) { + if (!envValue) return fallback + const [lat, lng] = envValue.split(',').map(Number) + if (isNaN(lat) || isNaN(lng)) return fallback + return { lat, lng } +} + +/** + * Standalone activities app configuration. + * Only enables base + activities modules. + */ +export const appConfig: AppConfig = { + modules: { + base: { + name: 'base', + enabled: true, + lazy: false, + config: { + nostr: { + relays: JSON.parse(import.meta.env.VITE_NOSTR_RELAYS || '["wss://relay.damus.io", "wss://nos.lol"]') + }, + auth: { + sessionTimeout: 24 * 60 * 60 * 1000, + }, + pwa: { + autoPrompt: true + }, + imageUpload: { + baseUrl: import.meta.env.VITE_PICTRS_BASE_URL || 'https://img.mydomain.com', + maxSizeMB: 10, + acceptedTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/avif', 'image/gif'] + } + } + }, + activities: { + name: 'activities', + enabled: true, + lazy: false, + config: { + apiConfig: { + baseUrl: import.meta.env.VITE_LNBITS_BASE_URL || 'http://localhost:5000', + apiKey: import.meta.env.VITE_API_KEY || '' + }, + defaultMapCenter: parseMapCenter(import.meta.env.VITE_DEFAULT_MAP_CENTER, { lat: 42.9667, lng: 1.6000 }), + maxTicketsPerUser: 10, + enableMap: true, + enablePrivateEvents: false + } + }, + }, + + features: { + pwa: true, + pushNotifications: true, + electronApp: false, + developmentMode: import.meta.env.DEV + } +} + +export default appConfig diff --git a/src/activities-app/app.ts b/src/activities-app/app.ts new file mode 100644 index 0000000..6375cf0 --- /dev/null +++ b/src/activities-app/app.ts @@ -0,0 +1,138 @@ +import { createApp } from 'vue' +import { createRouter, createWebHistory } from 'vue-router' +import { createPinia } from 'pinia' +import { pluginManager } from '@/core/plugin-manager' +import { eventBus } from '@/core/event-bus' +import { container } from '@/core/di-container' + +import appConfig from './app.config' +import baseModule from '@/modules/base' +import activitiesModule from '@/modules/activities' + +import App from './App.vue' + +import '@/assets/index.css' +import { i18n, changeLocale, type AvailableLocale } from '@/i18n' + +/** + * Initialize the standalone activities app + */ +export async function createAppInstance() { + console.log('🚀 Starting Sortir — Activities App...') + + const app = createApp(App) + + // Collect routes from enabled modules only + const moduleRoutes = [ + ...baseModule.routes || [], + ...activitiesModule.routes || [], + ].filter(Boolean) + + const router = createRouter({ + history: createWebHistory(), + routes: [ + // Activities page is the home page in standalone mode + { + path: '/', + redirect: '/activities' + }, + { + path: '/login', + name: 'login', + component: () => import('@/pages/Login.vue'), + meta: { requiresAuth: false } + }, + ...moduleRoutes, + // App-specific routes + { + path: '/settings', + name: 'settings', + component: () => import('./views/SettingsPage.vue'), + meta: { requiresAuth: false } + }, + ] + }) + + const pinia = createPinia() + + app.use(router) + app.use(pinia) + app.use(i18n) + + // Set default locale from env (user's saved preference takes priority via useStorage in i18n) + const defaultLocale = import.meta.env.VITE_DEFAULT_LOCALE as AvailableLocale | undefined + if (defaultLocale && !localStorage.getItem('user-locale')) { + await changeLocale(defaultLocale) + } + + // Initialize plugin manager + pluginManager.init(app, router) + + // Register modules + const moduleRegistrations = [] + + if (appConfig.modules.base.enabled) { + moduleRegistrations.push( + pluginManager.register(baseModule, appConfig.modules.base) + ) + } + + if (appConfig.modules.activities?.enabled) { + moduleRegistrations.push( + pluginManager.register(activitiesModule, appConfig.modules.activities) + ) + } + + await Promise.all(moduleRegistrations) + await pluginManager.installAll() + + // Initialize auth + const { auth } = await import('@/composables/useAuthService') + await auth.initialize() + + // Auth guard — only redirect for routes that explicitly require auth + router.beforeEach(async (to, _from, next) => { + const requiresAuth = to.meta.requiresAuth === true + + if (requiresAuth && !auth.isAuthenticated.value) { + next('/login') + } else if (to.path === '/login' && auth.isAuthenticated.value) { + next('/') + } else { + next() + } + }) + + // Global error handling + app.config.errorHandler = (err, _vm, info) => { + console.error('Global error:', err, info) + eventBus.emit('app:error', { error: err, info }, 'app') + } + + if (appConfig.features.developmentMode) { + ;(window as any).__pluginManager = pluginManager + ;(window as any).__eventBus = eventBus + ;(window as any).__container = container + } + + console.log('✅ Sortir app initialized') + return { app, router } +} + +export async function startApp() { + try { + const { app } = await createAppInstance() + app.mount('#app') + console.log('🎉 Sortir app started!') + eventBus.emit('app:started', {}, 'app') + } catch (error) { + console.error('💥 Failed to start Sortir app:', error) + document.getElementById('app')!.innerHTML = ` +
+

Failed to Start

+

${error instanceof Error ? error.message : 'Unknown error'}

+

Please refresh the page.

+
+ ` + } +} diff --git a/src/activities-app/main.ts b/src/activities-app/main.ts new file mode 100644 index 0000000..c9c8429 --- /dev/null +++ b/src/activities-app/main.ts @@ -0,0 +1,18 @@ +import { startApp } from './app' +import { registerSW } from 'virtual:pwa-register' +import 'vue-sonner/style.css' + +// PWA service worker with periodic updates +const intervalMS = 60 * 60 * 1000 // 1 hour +registerSW({ + onRegistered(r) { + r && setInterval(() => { + r.update() + }, intervalMS) + }, + onOfflineReady() { + console.log('Sortir app ready to work offline') + } +}) + +startApp() diff --git a/src/activities-app/views/SettingsPage.vue b/src/activities-app/views/SettingsPage.vue new file mode 100644 index 0000000..be6b437 --- /dev/null +++ b/src/activities-app/views/SettingsPage.vue @@ -0,0 +1,95 @@ + + + diff --git a/src/app.config.ts b/src/app.config.ts index 05a0034..a414bd2 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,5 +1,12 @@ import type { AppConfig } from './core/types' +function parseMapCenter(envValue: string | undefined, fallback: { lat: number; lng: number }) { + if (!envValue) return fallback + const [lat, lng] = envValue.split(',').map(Number) + if (isNaN(lat) || isNaN(lng)) return fallback + return { lat, lng } +} + export const appConfig: AppConfig = { modules: { base: { @@ -94,6 +101,21 @@ export const appConfig: AppConfig = { maxTicketsPerUser: 10 } }, + activities: { + name: 'activities', + enabled: true, + lazy: false, + config: { + apiConfig: { + baseUrl: import.meta.env.VITE_LNBITS_BASE_URL || 'http://localhost:5000', + apiKey: import.meta.env.VITE_API_KEY || '' + }, + defaultMapCenter: parseMapCenter(import.meta.env.VITE_DEFAULT_MAP_CENTER, { lat: 46.6034, lng: 1.8883 }), + maxTicketsPerUser: 10, + enableMap: true, + enablePrivateEvents: false + } + }, wallet: { name: 'wallet', enabled: true, diff --git a/src/core/di-container.ts b/src/core/di-container.ts index f2cf091..bcd8177 100644 --- a/src/core/di-container.ts +++ b/src/core/di-container.ts @@ -151,6 +151,10 @@ export const SERVICE_TOKENS = { // Events services EVENTS_SERVICE: Symbol('eventsService'), + + // Activities services (Nostr-native events module) + ACTIVITIES_NOSTR_SERVICE: Symbol('activitiesNostrService'), + ACTIVITIES_TICKET_API: Symbol('activitiesTicketApi'), // Invoice services INVOICE_SERVICE: Symbol('invoiceService'), diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 7c39b78..fd9b1a5 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -9,6 +9,7 @@ const messages: LocaleMessages = { events: 'Events', market: 'Market', chat: 'Chat', + activities: 'Activities', login: 'Login', logout: 'Logout' }, @@ -29,6 +30,95 @@ const messages: LocaleMessages = { de: 'German', zh: 'Chinese' }, + activities: { + title: 'Activities', + createNew: 'Create Activity', + noActivities: 'No activities found', + filters: { + all: 'All', + today: 'Today', + tomorrow: 'Tomorrow', + thisWeek: 'This Week', + thisMonth: 'This Month', + }, + categories: { + concert: 'Concert', + workshop: 'Workshop', + market: 'Market', + festival: 'Festival', + exhibition: 'Exhibition', + sport: 'Sport', + theater: 'Theater', + cinema: 'Cinema', + party: 'Party', + talk: 'Talk', + conference: 'Conference', + meetup: 'Meetup', + food: 'Food', + outdoor: 'Outdoor', + kids: 'Kids', + wellness: 'Wellness', + technology: 'Technology', + art: 'Art', + music: 'Music', + dance: 'Dance', + literature: 'Literature', + comedy: 'Comedy', + charity: 'Charity', + tradition: 'Tradition', + other: 'Other', + }, + detail: { + getTicket: 'Get Ticket', + going: 'Going', + maybe: 'Maybe', + notGoing: 'Not Going', + contactOrganizer: 'Contact Organizer', + organizer: 'Organizer', + location: 'Location', + when: 'When', + tickets: 'Tickets', + ticketsAvailable: '{count} tickets available', + soldOut: 'Sold Out', + free: 'Free', + }, + tickets: { + myTickets: 'My Tickets', + scanTicket: 'Scan Ticket', + noTickets: 'No tickets yet', + paid: 'Paid', + pending: 'Pending', + registered: 'Registered', + }, + nav: { + feed: 'Feed', + calendar: 'Calendar', + map: 'Map', + favorites: 'Favorites', + settings: 'Settings', + }, + search: { + placeholder: 'Search activities...', + noResults: 'No activities found', + }, + favorites: { + title: 'Favorites', + loginPrompt: 'Log in to save your favorite activities', + empty: 'No favorites yet', + emptyHint: 'Tap the heart icon on any activity to save it here', + logIn: 'Log in', + }, + settings: { + title: 'Settings', + account: 'Account', + loginPrompt: 'Log in to bookmark activities, RSVP, and purchase tickets.', + logIn: 'Log in', + logOut: 'Log out', + appearance: 'Appearance', + theme: 'Theme', + language: 'Language', + }, + }, dateTimeFormats: { short: { year: 'numeric', diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index 303ed46..f707db0 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -9,6 +9,7 @@ const messages: LocaleMessages = { events: 'Eventos', market: 'Mercado', chat: 'Chat', + activities: 'Actividades', login: 'Iniciar Sesión', logout: 'Cerrar Sesión' }, @@ -29,6 +30,95 @@ const messages: LocaleMessages = { de: 'Alemán', zh: 'Chino' }, + activities: { + title: 'Actividades', + createNew: 'Crear actividad', + noActivities: 'No se encontraron actividades', + filters: { + all: 'Todas', + today: 'Hoy', + tomorrow: 'Mañana', + thisWeek: 'Esta semana', + thisMonth: 'Este mes', + }, + categories: { + concert: 'Concierto', + workshop: 'Taller', + market: 'Mercado', + festival: 'Festival', + exhibition: 'Exposición', + sport: 'Deporte', + theater: 'Teatro', + cinema: 'Cine', + party: 'Fiesta', + talk: 'Charla', + conference: 'Conferencia', + meetup: 'Encuentro', + food: 'Gastronomía', + outdoor: 'Al aire libre', + kids: 'Niños', + wellness: 'Bienestar', + technology: 'Tecnología', + art: 'Arte', + music: 'Música', + dance: 'Danza', + literature: 'Literatura', + comedy: 'Comedia', + charity: 'Solidario', + tradition: 'Tradición', + other: 'Otro', + }, + detail: { + getTicket: 'Obtener boleto', + going: 'Voy', + maybe: 'Tal vez', + notGoing: 'No voy', + contactOrganizer: 'Contactar organizador', + organizer: 'Organizador', + location: 'Ubicación', + when: 'Cuándo', + tickets: 'Boletos', + ticketsAvailable: '{count} boletos disponibles', + soldOut: 'Agotado', + free: 'Gratis', + }, + tickets: { + myTickets: 'Mis boletos', + scanTicket: 'Escanear boleto', + noTickets: 'Aún no tienes boletos', + paid: 'Pagado', + pending: 'Pendiente', + registered: 'Registrado', + }, + nav: { + feed: 'Inicio', + calendar: 'Calendario', + map: 'Mapa', + favorites: 'Favoritos', + settings: 'Ajustes', + }, + search: { + placeholder: 'Buscar actividades...', + noResults: 'No se encontraron actividades', + }, + favorites: { + title: 'Favoritos', + loginPrompt: 'Inicia sesión para guardar tus actividades favoritas', + empty: 'Aún no tienes favoritos', + emptyHint: 'Toca el corazón en cualquier actividad para guardarla aquí', + logIn: 'Iniciar sesión', + }, + settings: { + title: 'Ajustes', + account: 'Cuenta', + loginPrompt: 'Inicia sesión para guardar actividades, confirmar asistencia y comprar boletos.', + logIn: 'Iniciar sesión', + logOut: 'Cerrar sesión', + appearance: 'Apariencia', + theme: 'Tema', + language: 'Idioma', + }, + }, dateTimeFormats: { short: { year: 'numeric', diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index 2fb2b3c..bd012eb 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -9,6 +9,7 @@ const messages: LocaleMessages = { events: 'Événements', market: 'Marché', chat: 'Chat', + activities: 'Activités', login: 'Connexion', logout: 'Déconnexion' }, @@ -29,6 +30,95 @@ const messages: LocaleMessages = { de: 'Allemand', zh: 'Chinois' }, + activities: { + title: 'Activités', + createNew: 'Créer une activité', + noActivities: 'Aucune activité trouvée', + filters: { + all: 'Tout', + today: "Aujourd'hui", + tomorrow: 'Demain', + thisWeek: 'Cette semaine', + thisMonth: 'Ce mois-ci', + }, + categories: { + concert: 'Concert', + workshop: 'Atelier', + market: 'Marché', + festival: 'Festival', + exhibition: 'Exposition', + sport: 'Sport', + theater: 'Théâtre', + cinema: 'Cinéma', + party: 'Fête', + talk: 'Conférence', + conference: 'Congrès', + meetup: 'Rencontre', + food: 'Gastronomie', + outdoor: 'Plein air', + kids: 'Enfants', + wellness: 'Bien-être', + technology: 'Technologie', + art: 'Art', + music: 'Musique', + dance: 'Danse', + literature: 'Littérature', + comedy: 'Humour', + charity: 'Caritatif', + tradition: 'Tradition', + other: 'Autre', + }, + detail: { + getTicket: 'Obtenir un billet', + going: 'Présent', + maybe: 'Peut-être', + notGoing: 'Absent', + contactOrganizer: "Contacter l'organisateur", + organizer: 'Organisateur', + location: 'Lieu', + when: 'Quand', + tickets: 'Billets', + ticketsAvailable: '{count} billets disponibles', + soldOut: 'Épuisé', + free: 'Gratuit', + }, + tickets: { + myTickets: 'Mes billets', + scanTicket: 'Scanner le billet', + noTickets: 'Pas encore de billets', + paid: 'Payé', + pending: 'En attente', + registered: 'Enregistré', + }, + nav: { + feed: 'Fil', + calendar: 'Calendrier', + map: 'Carte', + favorites: 'Favoris', + settings: 'Réglages', + }, + search: { + placeholder: 'Rechercher des activités...', + noResults: 'Aucune activité trouvée', + }, + favorites: { + title: 'Favoris', + loginPrompt: 'Connectez-vous pour sauvegarder vos activités préférées', + empty: 'Pas encore de favoris', + emptyHint: "Appuyez sur le cœur d'une activité pour la sauvegarder ici", + logIn: 'Se connecter', + }, + settings: { + title: 'Réglages', + account: 'Compte', + loginPrompt: 'Connectez-vous pour sauvegarder des activités, confirmer votre présence et acheter des billets.', + logIn: 'Se connecter', + logOut: 'Se déconnecter', + appearance: 'Apparence', + theme: 'Thème', + language: 'Langue', + }, + }, dateTimeFormats: { short: { year: 'numeric', diff --git a/src/i18n/types.ts b/src/i18n/types.ts index 12747de..cb303a0 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -7,6 +7,7 @@ export interface LocaleMessages { events: string market: string chat: string + activities: string login: string logout: string } @@ -29,6 +30,70 @@ export interface LocaleMessages { de: string zh: string } + // Activities module + activities?: { + title: string + createNew: string + noActivities: string + filters: { + all: string + today: string + tomorrow: string + thisWeek: string + thisMonth: string + } + categories: Record + detail: { + getTicket: string + going: string + maybe: string + notGoing: string + contactOrganizer: string + organizer: string + location: string + when: string + tickets: string + ticketsAvailable: string + soldOut: string + free: string + } + tickets: { + myTickets: string + scanTicket: string + noTickets: string + paid: string + pending: string + registered: string + } + nav: { + feed: string + calendar: string + map: string + favorites: string + settings: string + } + search: { + placeholder: string + noResults: string + } + favorites: { + title: string + loginPrompt: string + empty: string + emptyHint: string + logIn: string + } + settings: { + title: string + account: string + loginPrompt: string + logIn: string + logOut: string + appearance: string + theme: string + language: string + } + } // Add date/time formats dateTimeFormats: { short: { diff --git a/src/modules/activities/components/ActivityCalendarView.vue b/src/modules/activities/components/ActivityCalendarView.vue new file mode 100644 index 0000000..e9066d6 --- /dev/null +++ b/src/modules/activities/components/ActivityCalendarView.vue @@ -0,0 +1,183 @@ + + + diff --git a/src/modules/activities/components/ActivityCard.vue b/src/modules/activities/components/ActivityCard.vue new file mode 100644 index 0000000..71054fb --- /dev/null +++ b/src/modules/activities/components/ActivityCard.vue @@ -0,0 +1,152 @@ + + + diff --git a/src/modules/activities/components/ActivityList.vue b/src/modules/activities/components/ActivityList.vue new file mode 100644 index 0000000..726f09e --- /dev/null +++ b/src/modules/activities/components/ActivityList.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/modules/activities/components/ActivityMap.vue b/src/modules/activities/components/ActivityMap.vue new file mode 100644 index 0000000..06f6a69 --- /dev/null +++ b/src/modules/activities/components/ActivityMap.vue @@ -0,0 +1,126 @@ + + + diff --git a/src/modules/activities/components/ActivitySearchOverlay.vue b/src/modules/activities/components/ActivitySearchOverlay.vue new file mode 100644 index 0000000..2dd77ab --- /dev/null +++ b/src/modules/activities/components/ActivitySearchOverlay.vue @@ -0,0 +1,169 @@ + + + diff --git a/src/modules/activities/components/BookmarkButton.vue b/src/modules/activities/components/BookmarkButton.vue new file mode 100644 index 0000000..cd04ac1 --- /dev/null +++ b/src/modules/activities/components/BookmarkButton.vue @@ -0,0 +1,48 @@ + + + diff --git a/src/modules/activities/components/CategoryFilterBar.vue b/src/modules/activities/components/CategoryFilterBar.vue new file mode 100644 index 0000000..8069dcf --- /dev/null +++ b/src/modules/activities/components/CategoryFilterBar.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/modules/activities/components/CategorySelector.vue b/src/modules/activities/components/CategorySelector.vue new file mode 100644 index 0000000..5acb3f8 --- /dev/null +++ b/src/modules/activities/components/CategorySelector.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/modules/activities/components/CreateActivityDialog.vue b/src/modules/activities/components/CreateActivityDialog.vue new file mode 100644 index 0000000..0c6935a --- /dev/null +++ b/src/modules/activities/components/CreateActivityDialog.vue @@ -0,0 +1,270 @@ + + +