diff --git a/src/App.vue b/src/App.vue index dfbadfa..951e01a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,11 +1,7 @@ @@ -23,11 +19,7 @@ const showFooter = computed(() => route.path !== '/support') - + - - diff --git a/src/assets/index.css b/src/assets/index.css index 088cb4d..2f637c2 100644 --- a/src/assets/index.css +++ b/src/assets/index.css @@ -3,65 +3,105 @@ @layer base { :root { - --background: 227 92% 95%; - --foreground: 234 16% 35%; + /* Light theme */ + --background: rgb(231, 236, 254); + --background: oklch(0.97 0.02 250); + --foreground: rgb(75, 78, 104); + --foreground: oklch(0.40 0.03 265); - --card: 225 23% 92%; - --card-foreground: 234 16% 35%; + --card: rgb(226, 230, 242); + --card: oklch(0.92 0.02 260); + --card-foreground: rgb(75, 78, 104); + --card-foreground: oklch(0.40 0.03 265); - --popover: 225 23% 92%; - --popover-foreground: 234 16% 35%; + --popover: rgb(226, 230, 242); + --popover: oklch(0.92 0.02 260); + --popover-foreground: rgb(75, 78, 104); + --popover-foreground: oklch(0.40 0.03 265); - --primary: 220 91% 54%; - --primary-foreground: 227 92% 95%; + --primary: rgb(31, 102, 244); + --primary: oklch(0.60 0.20 260); + --primary-foreground: rgb(231, 236, 254); + --primary-foreground: oklch(0.97 0.02 250); - --secondary: 227 23% 83%; - --secondary-foreground: 234 16% 35%; + --secondary: rgb(212, 217, 228); + --secondary: oklch(0.87 0.03 255); + --secondary-foreground: rgb(75, 78, 104); + --secondary-foreground: oklch(0.40 0.03 265); - --muted: 227 23% 83%; - --muted-foreground: 231 11% 47%; + --muted: rgb(212, 217, 228); + --muted: oklch(0.87 0.03 255); + --muted-foreground: rgb(115, 120, 141); + --muted-foreground: oklch(0.55 0.03 265); - --accent: 11 83% 67%; - --accent-foreground: 227 92% 95%; + --accent: rgb(241, 127, 101); + --accent: oklch(0.70 0.15 30); + --accent-foreground: rgb(231, 236, 254); + --accent-foreground: oklch(0.97 0.02 250); - --destructive: 347 87% 44%; - --destructive-foreground: 227 92% 95%; + --destructive: rgb(210, 15, 57); + --destructive: oklch(0.50 0.28 15); + --destructive-foreground: rgb(231, 236, 254); + --destructive-foreground: oklch(0.97 0.02 250); - --border: 228 17% 77%; - --input: 228 17% 77%; - --ring: 220 91% 54%; + --border: rgb(197, 201, 216); + --border: oklch(0.83 0.02 265); + --input: rgb(197, 201, 216); + --input: oklch(0.83 0.02 265); + --ring: rgb(31, 102, 244); + --ring: oklch(0.60 0.20 260); --radius: 0.5rem; } .dark { - --background: 233 31% 18%; - --foreground: 227 68% 88%; + /* Dark theme */ + --background: rgb(26, 32, 54); + --background: oklch(0.25 0.05 265); + --foreground: rgb(218, 226, 248); + --foreground: oklch(0.90 0.03 260); - --card: 234 32% 15%; - --card-foreground: 227 68% 88%; + --card: rgb(22, 27, 45); + --card: oklch(0.20 0.05 265); + --card-foreground: rgb(218, 226, 248); + --card-foreground: oklch(0.90 0.03 260); - --popover: 234 32% 15%; - --popover-foreground: 227 68% 88%; + --popover: rgb(22, 27, 45); + --popover: oklch(0.20 0.05 265); + --popover-foreground: rgb(218, 226, 248); + --popover-foreground: oklch(0.90 0.03 260); - --primary: 220 83% 76%; - --primary-foreground: 233 31% 18%; + --primary: rgb(127, 167, 249); + --primary: oklch(0.75 0.15 260); + --primary-foreground: rgb(26, 32, 54); + --primary-foreground: oklch(0.25 0.05 265); - --secondary: 233 25% 26%; - --secondary-foreground: 227 68% 88%; + --secondary: rgb(38, 46, 72); + --secondary: oklch(0.30 0.06 265); + --secondary-foreground: rgb(218, 226, 248); + --secondary-foreground: oklch(0.90 0.03 260); - --muted: 233 25% 26%; - --muted-foreground: 225 27% 72%; + --muted: rgb(38, 46, 72); + --muted: oklch(0.30 0.06 265); + --muted-foreground: rgb(177, 186, 211); + --muted-foreground: oklch(0.78 0.06 265); - --accent: 11 77% 90%; - --accent-foreground: 233 31% 18%; + --accent: rgb(255, 179, 164); + --accent: oklch(0.83 0.12 30); + --accent-foreground: rgb(26, 32, 54); + --accent-foreground: oklch(0.25 0.05 265); - --destructive: 351 74% 76%; - --destructive-foreground: 233 31% 18%; + --destructive: rgb(247, 130, 150); + --destructive: oklch(0.75 0.18 15); + --destructive-foreground: rgb(26, 32, 54); + --destructive-foreground: oklch(0.25 0.05 265); - --border: 233 25% 26%; - --input: 233 25% 26%; - --ring: 220 83% 76%; + --border: rgb(38, 46, 72); + --border: oklch(0.30 0.06 265); + --input: rgb(38, 46, 72); + --input: oklch(0.30 0.06 265); + --ring: rgb(127, 167, 249); + --ring: oklch(0.75 0.15 260); } } @@ -78,106 +118,106 @@ @layer utilities { .bg-background { - background-color: hsl(var(--background) / var(--tw-bg-opacity, 1)); + background-color: var(--background); } .bg-foreground { - background-color: hsl(var(--foreground) / var(--tw-bg-opacity, 1)); + background-color: var(--foreground); } .bg-card { - background-color: hsl(var(--card) / var(--tw-bg-opacity, 1)); + background-color: var(--card); } .bg-card-foreground { - background-color: hsl(var(--card-foreground) / var(--tw-bg-opacity, 1)); + background-color: var(--card-foreground); } .bg-popover { - background-color: hsl(var(--popover) / var(--tw-bg-opacity, 1)); + background-color: var(--popover); } .bg-popover-foreground { - background-color: hsl(var(--popover-foreground) / var(--tw-bg-opacity, 1)); + background-color: var(--popover-foreground); } .bg-primary { - background-color: hsl(var(--primary) / var(--tw-bg-opacity, 1)); + background-color: var(--primary); } .bg-primary-foreground { - background-color: hsl(var(--primary-foreground) / var(--tw-bg-opacity, 1)); + background-color: var(--primary-foreground); } .bg-secondary { - background-color: hsl(var(--secondary) / var(--tw-bg-opacity, 1)); + background-color: var(--secondary); } .bg-secondary-foreground { - background-color: hsl(var(--secondary-foreground) / var(--tw-bg-opacity, 1)); + background-color: var(--secondary-foreground); } .bg-muted { - background-color: hsl(var(--muted) / var(--tw-bg-opacity, 1)); + background-color: var(--muted); } .bg-muted-foreground { - background-color: hsl(var(--muted-foreground) / var(--tw-bg-opacity, 1)); + background-color: var(--muted-foreground); } .bg-accent { - background-color: hsl(var(--accent) / var(--tw-bg-opacity, 1)); + background-color: var(--accent); } .bg-accent-foreground { - background-color: hsl(var(--accent-foreground) / var(--tw-bg-opacity, 1)); + background-color: var(--accent-foreground); } .bg-destructive { - background-color: hsl(var(--destructive) / var(--tw-bg-opacity, 1)); + background-color: var(--destructive); } .bg-destructive-foreground { - background-color: hsl(var(--destructive-foreground) / var(--tw-bg-opacity, 1)); + background-color: var(--destructive-foreground); } .text-background { - color: hsl(var(--background) / var(--tw-text-opacity, 1)); + color: var(--background); } .text-foreground { - color: hsl(var(--foreground) / var(--tw-text-opacity, 1)); + color: var(--foreground); } .text-card { - color: hsl(var(--card) / var(--tw-text-opacity, 1)); + color: var(--card); } .text-card-foreground { - color: hsl(var(--card-foreground) / var(--tw-text-opacity, 1)); + color: var(--card-foreground); } .text-popover { - color: hsl(var(--popover) / var(--tw-text-opacity, 1)); + color: var(--popover); } .text-popover-foreground { - color: hsl(var(--popover-foreground) / var(--tw-text-opacity, 1)); + color: var(--popover-foreground); } .text-primary { - color: hsl(var(--primary) / var(--tw-text-opacity, 1)); + color: var(--primary); } .text-primary-foreground { - color: hsl(var(--primary-foreground) / var(--tw-text-opacity, 1)); + color: var(--primary-foreground); } .text-secondary { - color: hsl(var(--secondary) / var(--tw-text-opacity, 1)); + color: var(--secondary); } .text-secondary-foreground { - color: hsl(var(--secondary-foreground) / var(--tw-text-opacity, 1)); + color: var(--secondary-foreground); } .text-muted { - color: hsl(var(--muted) / var(--tw-text-opacity, 1)); + color: var(--muted); } .text-muted-foreground { - color: hsl(var(--muted-foreground) / var(--tw-text-opacity, 1)); + color: var(--muted-foreground); } .text-accent { - color: hsl(var(--accent) / var(--tw-text-opacity, 1)); + color: var(--accent); } .text-accent-foreground { - color: hsl(var(--accent-foreground) / var(--tw-text-opacity, 1)); + color: var(--accent-foreground); } .text-destructive { - color: hsl(var(--destructive) / var(--tw-text-opacity, 1)); + color: var(--destructive); } .text-destructive-foreground { - color: hsl(var(--destructive-foreground) / var(--tw-text-opacity, 1)); + color: var(--destructive-foreground); } @supports not (backdrop-filter: blur(1px)) { .select-content { - background-color: hsl(var(--background)); + background-color: var(--background); } } } @@ -185,25 +225,25 @@ /* Add support for ring colors */ @layer utilities { .ring-border { - --tw-ring-color: hsl(var(--border) / var(--tw-ring-opacity, 1)); + --tw-ring-color: var(--border); } .ring-primary { - --tw-ring-color: hsl(var(--primary) / var(--tw-ring-opacity, 1)); + --tw-ring-color: var(--primary); } .ring-background { - --tw-ring-color: hsl(var(--background) / var(--tw-ring-opacity, 1)); + --tw-ring-color: var(--background); } } /* Add support for border colors */ @layer utilities { .box-border { - border-color: hsl(var(--border) / var(--tw-border-opacity, 1)); + border-color: var(--border); } .border-primary { - border-color: hsl(var(--primary) / var(--tw-border-opacity, 1)); + border-color: var(--primary); } .border-background { - border-color: hsl(var(--background) / var(--tw-border-opacity, 1)); + border-color: var(--background); } } diff --git a/src/components/SupportChat.vue b/src/components/SupportChat.vue deleted file mode 100644 index 5496104..0000000 --- a/src/components/SupportChat.vue +++ /dev/null @@ -1,397 +0,0 @@ - - - - - - - - - - - - SA - - - - Support Agent - - - - - - - - - - Customer Support - - - - - - - - - - - - - - - - - - - - - {{ formatDate(group.timestamp) }} - - - - - - - - - - - - - - - - - - - - - - {{ isSending ? 'Sending...' : 'Send' }} - - - - - - - - Connecting... - - - - - diff --git a/src/components/directory/DirectoryCard.vue b/src/components/directory/DirectoryCard.vue deleted file mode 100644 index 2164b4d..0000000 --- a/src/components/directory/DirectoryCard.vue +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - {{ item.name }} - - - - - {{ item.description }} - - - - - - - - {{ [item.address, item.town].filter(Boolean).join(', ') }} - - - {{ [item.address, item.town].filter(Boolean).join(', ') }} - - - - - {{ item.contact }} - - - - WhatsApp - - - - Telegram - - - - - - - - {{ item.lightning }} - - - - - - - - {{ item.url.replace(/^https?:\/\//, '').split('/')[0] }} - - - - - - - - - - - - - - - - diff --git a/src/components/directory/DirectoryFilter.vue b/src/components/directory/DirectoryFilter.vue deleted file mode 100644 index eeed94d..0000000 --- a/src/components/directory/DirectoryFilter.vue +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - - - - - - - - - {{ to.label }} - - - - - - - - - - - {{ cat.label }} - - - - {{ cat.label }} - - - - - - diff --git a/src/components/directory/DirectoryGrid.vue b/src/components/directory/DirectoryGrid.vue deleted file mode 100644 index c7ec44d..0000000 --- a/src/components/directory/DirectoryGrid.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - No results found. Try adjusting your filters. - - - - diff --git a/src/components/directory/DirectoryItemDetail.vue b/src/components/directory/DirectoryItemDetail.vue deleted file mode 100644 index 74e2cd6..0000000 --- a/src/components/directory/DirectoryItemDetail.vue +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - - - - - - - - - - - - - - {{ item.name }} - - - - {{ item.description }} - - - - - - - - - - - - {{ [item.address, item.town].filter(Boolean).join(', ') }} - - {{ t('directory.viewMap') }} - - - - - - - - - - {{ item.contact }} - - - - WhatsApp - - - - Telegram - - - - - - - - - - {{ item.lightning }} - - - - - - - - - - - {{ item.url.replace(/^https?:\/\//, '') }} - - - - - - - - - {{ platform.charAt(0).toUpperCase() + platform.slice(1) }} - - - - - - - - diff --git a/src/components/icons/TukTuk.vue b/src/components/icons/TukTuk.vue deleted file mode 100644 index 3a46f2f..0000000 --- a/src/components/icons/TukTuk.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/components/layout/Navbar.vue b/src/components/layout/Navbar.vue index 687fddb..c185b50 100644 --- a/src/components/layout/Navbar.vue +++ b/src/components/layout/Navbar.vue @@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n' import { Menu, X, Sun, Moon, Zap, MessageSquareText, LogIn } from 'lucide-vue-next' import { Button } from '@/components/ui/button' import { useTheme } from '@/components/theme-provider' -import { useNostrStore } from '@/stores/nostr' import { useRouter } from 'vue-router' import LogoutDialog from '@/components/ui/logout-dialog/LogoutDialog.vue' import Login from '@/components/Login.vue' @@ -13,15 +12,13 @@ import ConnectionStatus from '@/components/ConnectionStatus.vue' const { t, locale } = useI18n() const { theme, setTheme } = useTheme() -const nostrStore = useNostrStore() +// const nostrStore = useNostrStore() const router = useRouter() const isOpen = ref(false) const showLoginDialog = ref(false) const navigation = computed(() => [ { name: t('nav.home'), href: '/' }, - { name: t('nav.directory'), href: '/directory' }, - { name: t('nav.faq'), href: '/faq' }, { name: t('nav.support'), href: '/support', icon: MessageSquareText }, ]) @@ -42,7 +39,7 @@ const toggleLocale = () => { } const handleLogout = async () => { - await nostrStore.logout() + // await nostrStore.logout() router.push('/') } @@ -82,18 +79,20 @@ const openLogin = () => { - {{ locale === 'en' ? '🇪🇸 ES' : '🇺🇸 EN' }} - + + - + + - @@ -118,8 +117,7 @@ const openLogin = () => { - {{ locale === 'en' ? '🇪🇸 ES' : '🇺🇸 EN' }} diff --git a/src/components/theme-provider/index.ts b/src/components/theme-provider/index.ts index b557ace..d125042 100644 --- a/src/components/theme-provider/index.ts +++ b/src/components/theme-provider/index.ts @@ -5,7 +5,6 @@ type Theme = 'dark' | 'light' | 'system' const useTheme = () => { const theme = ref('dark') const systemTheme = ref<'dark' | 'light'>('light') - const currentTown = ref(localStorage.getItem('current-town') || 'all') const updateSystemTheme = () => { systemTheme.value = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' @@ -28,7 +27,7 @@ const useTheme = () => { if (stored && (stored === 'dark' || stored === 'light' || stored === 'system')) { theme.value = stored as Theme } - + updateSystemTheme() window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateSystemTheme) applyTheme() @@ -43,19 +42,12 @@ const useTheme = () => { localStorage.setItem('ui-theme', newTheme) } - const setCurrentTown = (town: string) => { - currentTown.value = town - localStorage.setItem('current-town', town) - } - return { theme, setTheme, systemTheme, currentTheme, - currentTown, - setCurrentTown } } -export { useTheme } \ No newline at end of file +export { useTheme } diff --git a/src/composables/useChat.ts b/src/composables/useChat.ts deleted file mode 100644 index 4b1ed25..0000000 --- a/src/composables/useChat.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { computed } from 'vue' -import { useMessageStore } from '@/stores/messages' -import { useNostrStore } from '@/stores/nostr' - -export function useChat(pubkey: string) { - const messageStore = useMessageStore() - const nostrStore = useNostrStore() - - const messages = computed(() => - messageStore.messages.get(pubkey) || [] - ) - - const sendMessage = async (content: string) => { - if (!content.trim()) return - await nostrStore.sendMessage(pubkey, content) - } - - const loadHistory = async () => { - await nostrStore.subscribeToMessages() - } - - return { - messages, - sendMessage, - loadHistory - } -} \ No newline at end of file diff --git a/src/data/directory.ts b/src/data/directory.ts deleted file mode 100644 index 73ed991..0000000 --- a/src/data/directory.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { type DirectoryItem } from '@/types/directory' - -export const mockDirectoryItems: DirectoryItem[] = [ - { - id: '1', - name: 'Ixchel Cafe & Bakery', - category: 'restaurant', - description: 'A cozy cafe serving great coffee and accepting Bitcoin Lightning payments.', - url: 'https://ixchel.atitlan.io', - town: 'San Marcos', - mapsUrl: 'https://maps.app.goo.gl/sbjmvqP8U4SB4FS29', - social: { - facebook: 'https://www.facebook.com/emporium.atitlan' - } - }, - { - id: '2', - name: 'Axel', - category: 'tuktuk', - town: 'San Marcos', - contact: '+502 3846 1220', - contactType: ['whatsapp', 'telegram'], - lightning: 'axel@atitlan.io' - }, - { - id: '3', - name: 'Atitlan Muay Thai', - category: 'services', - town: 'San Pedro', - social: { - facebook: 'https://www.facebook.com/muaythaiatitlan' - } - }, - { - id: '4', - name: 'Zoe Nails & Spa', - category: 'services', - town: 'Panajachel', - social: { - facebook: 'https://www.facebook.com/zoenailsyspapanajachel' - }, - local: true - }, - { - id: '5', - name: 'Full Print', - category: 'services', - town: 'San Pedro', - social: { - facebook: 'https://www.facebook.com/profile.php?id=100057645572968' - }, - local: true - }, - { - id: '6', - name: 'Multiservicios Yaxon', - category: 'services', - town: 'Tzununa', - social: { - facebook: 'https://www.facebook.com/Multiservicios-Yaxón-299907600397260' - }, - local: true - }, - { - id: '7', - name: 'Utz Kab', - category: 'goods', - town: 'San Pablo', - social: { - facebook: 'https://www.facebook.com/UTZ-KAB-534232490075686' - }, - local: true - }, - { - id: '8', - name: 'Utz Color Fashion', - category: 'goods', - town: 'San Pedro', - social: { - facebook: 'https://www.facebook.com/UtzColorFashion' - }, - local: true - }, - { - id: '9', - name: 'Do Bau', - category: 'goods', - town: 'San Pedro', - social: { - facebook: 'https://www.facebook.com/Adrianamatrioshka' - }, - local: true - }, - { - id: '10', - name: 'Caffé Kitsch', - category: 'restaurant', - town: 'Panajachel', - social: { - facebook: 'https://www.facebook.com/Kitschers' - }, - local: true - }, - { - id: '11', - name: 'Hotel Corazon del Mundo Fresh', - category: 'lodging', - town: 'Jaibalito', - social: { - facebook: 'https://www.facebook.com/corazondelmundofresh' - }, - local: true - }, - { - id: '12', - name: 'Hostel Fe San Marcos', - category: 'lodging', - town: 'San Marcos', - social: { - facebook: 'https://www.facebook.com/HostelFeSanMarcos' - } - }, - { - id: '13', - name: 'Hotel Casa Maya', - category: 'lodging', - town: 'San Marcos', - mapsUrl: 'https://www.google.com/maps/place/Hotel+Casa+Maya' - }, - { - id: '15', - name: 'Artesanias San Marcos La Laguna', - category: 'goods', - town: 'San Marcos', - social: { - facebook: 'https://www.facebook.com/Artesanias-San-Marcos-La-Laguna-102826589071628/' - }, - local: true - }, - { - id: '16', - name: 'Textiles Felix San Marcos La Laguna', - category: 'goods', - town: 'San Marcos', - social: { - facebook: 'https://www.facebook.com/Textiles-Felix-San-Marcos-La-Laguna-102732085750559' - }, - local: true - }, - { - id: '17', - name: 'Health Food Store San Jose', - category: 'goods', - town: 'San Marcos', - social: { - facebook: 'https://www.facebook.com/Health-Food-Store-San-Jose-719299235179691' - }, - local: true - }, - { - id: '18', - name: 'Tienda San Jose 2', - category: 'goods', - town: 'San Marcos', - mapsUrl: 'https://www.google.com/maps/place/Tienda+San+José+%232', - local: true - }, - { - id: '19', - name: 'Sound Temple San Marcos', - category: 'services', - town: 'San Marcos', - social: { - facebook: 'https://www.facebook.com/SoundTempleSanMarcos' - } - }, - { - id: '21', - name: 'Veda', - category: 'restaurant', - town: 'Tzununa', - social: { - facebook: 'https://www.facebook.com/vedafoodismedicine' - } - }, - { - id: '22', - name: 'Bambu Guest House', - category: 'lodging', - town: 'Tzununa', - social: { - facebook: 'https://www.facebook.com/bambuguesthousegt' - } - }, - { - id: '23', - name: 'Atitlan Organics', - category: 'goods', - town: 'Tzununa', - social: { - facebook: 'https://www.facebook.com/atitlanorganics' - } - }, - { - id: '24', - name: 'Holy Wow Cacao', - category: 'goods', - town: 'Tzununa', - social: { - facebook: 'https://www.facebook.com/HolyWowCacao' - } - }, - { - id: '25', - name: 'Bitcoin Lake Lancha (fake)', - category: 'lancha', - address: 'Pier 21, Harbor Front', - contact: '+1 234-567-8902' - }, - { - id: '26', - name: 'Tor\'s Drums', - category: 'goods', - town: 'San Marcos', - contact: '+502 4900 1279', - contactType: ['whatsapp'], - social: { - facebook: 'https://www.facebook.com/share/1DcBdJhuFH/' - }, - lightning: 'tor@atitlan.io' - }, - { - id: '27', - name: 'Jade Maya', - category: 'goods', - local: true, - town: 'San Marcos', - mapsUrl: 'https://maps.app.goo.gl/kZiKdM2FFAw1TQMN8', - lightning: 'osman@atitlan.io', - }, - { - id: '28', - name: 'La Sala del Lago', - category: 'restaurant', - town: 'San Marcos', - social: { - facebook: 'https://www.facebook.com/La-Sala-Del-Lago-100220539146301' - } - }, - { - id: '29', - name: 'Nectar', - category: 'restaurant', - town: 'San Marcos', - social: { - facebook: 'https://www.facebook.com/lovenectar' - }, - local: true - }, - { - id: '30', - name: 'Arati Cafe', - category: 'restaurant', - town: 'San Marcos', - social: { - facebook: 'https://www.facebook.com/Arati-Cafe-105767784719695' - }, - local: true - }, - { - id: '31', - name: 'Fe Restaurant', - category: 'restaurant', - town: 'San Marcos', - social: { - facebook: 'https://www.facebook.com/fesanmarcos' - } - }, - { - id: '32', - name: 'Hostal del Lago', - category: 'lodging', - town: 'San Marcos', - social: { - facebook: 'https://www.facebook.com/Hostel-Del-Lago-605530306467708' - } - } -] diff --git a/src/lib/encryption.ts b/src/lib/encryption.ts deleted file mode 100644 index 275bcc3..0000000 --- a/src/lib/encryption.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class NostrEncryption { - static async encrypt(privkey: string, pubkey: string, content: string) { - return await window.NostrTools.nip04.encrypt(privkey, pubkey, content) - } - - static async decrypt(privkey: string, pubkey: string, content: string) { - return await window.NostrTools.nip04.decrypt(privkey, pubkey, content) - } -} \ No newline at end of file diff --git a/src/lib/error.ts b/src/lib/error.ts deleted file mode 100644 index 3ea42b8..0000000 --- a/src/lib/error.ts +++ /dev/null @@ -1,24 +0,0 @@ -export class NostrError extends Error { - constructor( - message: string, - public code: string, - public context?: any - ) { - super(message) - this.name = 'NostrError' - } -} - -export function handleNostrError(error: unknown) { - if (error instanceof NostrError) { - switch (error.code) { - case 'CONNECTION_FAILED': - return 'Failed to connect to relay. Please check your connection.' - case 'DECRYPT_FAILED': - return 'Failed to decrypt message.' - default: - return error.message - } - } - return 'An unexpected error occurred' -} \ No newline at end of file diff --git a/src/lib/messages.ts b/src/lib/messages.ts deleted file mode 100644 index c8938e0..0000000 --- a/src/lib/messages.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { DirectMessage } from '@/types/nostr' - -export class MessageManager { - private messages = new Map() - private processedIds = new Set() - - constructor() { - this.loadFromStorage() - } - - addMessage(pubkey: string, message: DirectMessage) { - if (this.processedIds.has(message.id)) return false - if (this.isDuplicate(pubkey, message)) return false - - this.processedIds.add(message.id) - const messages = [...(this.messages.get(pubkey) || []), message] - messages.sort((a, b) => a.created_at - b.created_at) - this.messages.set(pubkey, messages) - this.saveToStorage() - return true - } - - private isDuplicate(pubkey: string, message: DirectMessage) { - const existing = this.messages.get(pubkey) || [] - return existing.some(msg => - msg.content === message.content && - Math.abs(msg.created_at - message.created_at) < 1 - ) - } - - private loadFromStorage() { - try { - const stored = localStorage.getItem('nostr_messages') - if (stored) { - this.messages = new Map(JSON.parse(stored)) - this.messages.forEach(msgs => - msgs.forEach(msg => this.processedIds.add(msg.id)) - ) - } - } catch (err) { - console.error('Failed to load messages:', err) - } - } - - private saveToStorage() { - try { - localStorage.setItem( - 'nostr_messages', - JSON.stringify(Array.from(this.messages.entries())) - ) - } catch (err) { - console.error('Failed to save messages:', err) - } - } -} \ No newline at end of file diff --git a/src/lib/nostr-bundle.ts b/src/lib/nostr-bundle.ts deleted file mode 100644 index 4da9833..0000000 --- a/src/lib/nostr-bundle.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - getPublicKey, - generateSecretKey, - nip04, - getEventHash, - finalizeEvent, - validateEvent, - nip19, - SimplePool, - type Event, - type Filter -} from 'nostr-tools'; -import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; - -// Expose NostrTools to the window object -(window as any).NostrTools = { - getPublicKey, - generatePrivateKey: () => bytesToHex(generateSecretKey()), - nip04, - getEventHash, - getSignature: (event: Event, privateKey: string) => { - const signedEvent = finalizeEvent(event, hexToBytes(privateKey)); - return signedEvent.sig; - }, - signEvent: (event: Event, privateKey: string) => { - const signedEvent = finalizeEvent(event, hexToBytes(privateKey)); - return signedEvent.sig; - }, - verifySignature: validateEvent, - nip19, - relayInit: (url: string) => { - const pool = new SimplePool(); - return { - connect: async () => { - await pool.ensureRelay(url); - return true; - }, - sub: (filters: Filter[]) => { - return { - on: (type: string, callback: (event: Event) => void) => { - if (type === 'event') { - void pool.subscribeMany( - [url], - filters, - { onevent: callback } - ); - } - } - }; - }, - publish: (event: Event) => { - return { - on: (type: string, cb: (msg?: string) => void) => { - if (type === 'ok') { - Promise.all(pool.publish([url], event)) - .then(() => cb()) - .catch((err: Error) => cb(err.message)); - } - } - }; - }, - close: () => { - pool.close([url]); - } - }; - } -}; \ No newline at end of file diff --git a/src/lib/nostr.ts b/src/lib/nostr.ts deleted file mode 100644 index d716641..0000000 --- a/src/lib/nostr.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { NostrEvent, NostrRelayConfig } from '../types/nostr' - -declare global { - interface Window { - NostrTools: { - getPublicKey: (privkey: string) => string - generatePrivateKey: () => string - nip04: { - encrypt: (privkey: string, pubkey: string, content: string) => Promise - decrypt: (privkey: string, pubkey: string, content: string) => Promise - } - getEventHash: (event: NostrEvent) => string - signEvent: (event: NostrEvent, privkey: string) => Promise - getSignature: (event: NostrEvent, privkey: string) => string - verifySignature: (event: NostrEvent) => boolean - nip19: { - decode: (str: string) => { type: string; data: string } - npubEncode: (hex: string) => string - } - relayInit: (url: string) => { - connect: () => Promise - sub: (filters: any[]) => { - on: (event: string, callback: (event: NostrEvent) => void) => void - } - publish: (event: NostrEvent) => { - on: (type: 'ok' | 'failed', cb: (msg?: string) => void) => void - } - close: () => void - } - } - } -} - -export async function connectToRelay(url: string) { - const relay = window.NostrTools.relayInit(url) - try { - await relay.connect() - return relay - } catch (err) { - console.error(`Failed to connect to ${url}:`, err) - return null - } -} - -export async function publishEvent(event: NostrEvent, relays: NostrRelayConfig[]) { - const connectedRelays = await Promise.all( - relays.map(relay => connectToRelay(relay.url)) - ) - - const activeRelays = connectedRelays.filter(relay => relay !== null) - - return Promise.all( - activeRelays.map(relay => - new Promise((resolve) => { - const pub = relay.publish(event) - pub.on('ok', () => { - resolve(true) - }) - pub.on('failed', () => { - resolve(false) - }) - }) - ) - ) -} - -export async function encryptMessage(privkey: string, pubkey: string, content: string): Promise { - return await window.NostrTools.nip04.encrypt(privkey, pubkey, content) -} - -export async function decryptMessage(privkey: string, pubkey: string, content: string): Promise { - return await window.NostrTools.nip04.decrypt(privkey, pubkey, content) -} - -export function generatePrivateKey(): string { - return window.NostrTools.generatePrivateKey() -} - -export function getPublicKey(privateKey: string): string { - return window.NostrTools.getPublicKey(privateKey) -} - -export function getEventHash(event: NostrEvent): string { - return window.NostrTools.getEventHash(event) -} - -export async function signEvent(event: NostrEvent, privateKey: string): Promise { - return window.NostrTools.getSignature(event, privateKey) -} - -export function verifySignature(event: NostrEvent): boolean { - return window.NostrTools.verifySignature(event) -} - -export function npubToHex(npub: string): string { - try { - const { type, data } = window.NostrTools.nip19.decode(npub) - if (type !== 'npub') throw new Error('Invalid npub') - return data - } catch (err) { - console.error('Failed to decode npub:', err) - throw err - } -} - -export function hexToNpub(hex: string): string { - return window.NostrTools.nip19.npubEncode(hex) -} - -export function isValidPrivateKey(key: string): boolean { - try { - if (!/^[0-9a-fA-F]{64}$/.test(key)) { - return false - } - return true - } catch { - return false - } -} - -export function formatPrivateKey(key: string): string { - return key.trim().toLowerCase() -} \ No newline at end of file diff --git a/src/lib/storage.ts b/src/lib/storage.ts deleted file mode 100644 index d3cb79c..0000000 --- a/src/lib/storage.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { DirectMessage } from '@/types/nostr' - -export class MessageStorage { - static saveMessages(pubkey: string, messages: DirectMessage[]) { - try { - localStorage.setItem( - `messages_${pubkey}`, - JSON.stringify(messages) - ) - } catch (err) { - console.error('Failed to save messages:', err) - } - } - - static loadMessages(pubkey: string): DirectMessage[] { - try { - const stored = localStorage.getItem(`messages_${pubkey}`) - return stored ? JSON.parse(stored) : [] - } catch (err) { - console.error('Failed to load messages:', err) - return [] - } - } -} \ No newline at end of file diff --git a/src/lib/subscriptions.ts b/src/lib/subscriptions.ts deleted file mode 100644 index a9e51c5..0000000 --- a/src/lib/subscriptions.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { NostrEvent } from '@/types/nostr' - -export class SubscriptionManager { - private currentSubs: any[] = [] - private isActive = false - - async subscribe(relay: any, filters: any[], handlers: { - onEvent: (event: NostrEvent) => void, - onEose?: () => void - }) { - if (this.isActive) return - - this.isActive = true - const sub = relay.sub(filters) - - sub.on('event', handlers.onEvent) - if (handlers.onEose) { - sub.on('eose', handlers.onEose) - } - - this.currentSubs.push(sub) - return sub - } - - unsubscribe() { - this.currentSubs.forEach(sub => { - try { - if (sub?.unsub) sub.unsub() - } catch (err) { - console.error('Failed to unsubscribe:', err) - } - }) - this.currentSubs = [] - this.isActive = false - } -} \ No newline at end of file diff --git a/src/lib/websocket.ts b/src/lib/websocket.ts deleted file mode 100644 index 9806882..0000000 --- a/src/lib/websocket.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { withTimeout } from '@/lib/utils' -import type { NostrEvent } from '@/types/nostr' - -// Create a new WebSocket manager class -export class NostrWebSocketManager { - private relayPool: any[] = [] - private subscriptions = new Map() - - async connect(relays: { url: string }[]) { - // Close existing connections - await this.disconnect() - - // Connect to all relays - this.relayPool = (await Promise.all( - relays.map(relay => this.connectToRelay(relay.url)) - )).filter((relay): relay is any => relay !== null) - - return this.relayPool.length > 0 - } - - private async connectToRelay(url: string) { - const relay = window.NostrTools.relayInit(url) - try { - await withTimeout(relay.connect()) - return relay - } catch (err) { - console.error(`Failed to connect to ${url}:`, err) - return null - } - } - - async publish(event: NostrEvent, relays: { url: string }[]) { - return Promise.all( - relays.map(({ url }) => this.publishToRelay(event, url)) - ) - } - - disconnect() { - this.relayPool.forEach(relay => relay.close()) - this.relayPool = [] - this.subscriptions.clear() - } - - get isConnected() { - return this.relayPool.length > 0 - } - - private async publishToRelay(event: NostrEvent, url: string) { - const relay = window.NostrTools.relayInit(url) - try { - await relay.connect() - return new Promise((resolve, reject) => { - const pub = relay.publish(event) - pub.on('ok', () => resolve(true)) - pub.on('failed', reject) - }) - } catch (err) { - console.error(`Failed to publish to ${url}:`, err) - return false - } - } -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 4fae542..febdfc9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,7 +5,6 @@ import router from './router' import { i18n } from './i18n' import './assets/index.css' import { registerSW } from 'virtual:pwa-register' -import './lib/nostr-bundle' const app = createApp(App) const pinia = createPinia() diff --git a/src/pages/Directory.vue b/src/pages/Directory.vue deleted file mode 100644 index ed2e45c..0000000 --- a/src/pages/Directory.vue +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - {{ t('directory.title') }} - - - - {{ t('directory.title') }} - - - {{ t('directory.subtitle') }} - - - - - - - - - - - - - - - - - - - - diff --git a/src/pages/DirectoryItem.vue b/src/pages/DirectoryItem.vue deleted file mode 100644 index 0300b45..0000000 --- a/src/pages/DirectoryItem.vue +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - {{ t('directory.itemNotFound') }} - {{ t('directory.itemNotFoundDesc') }} - - {{ t('directory.backToDirectory') }} - - - - - - - - ← {{ t('directory.backToDirectory') }} - - - - - - - {{ t('directory.share') }} - - - - - {{ justCopied ? t('directory.linkCopied') : t('directory.copyLink') }} - - - - - - - - \ No newline at end of file diff --git a/src/pages/FAQ.vue b/src/pages/FAQ.vue deleted file mode 100644 index 802479d..0000000 --- a/src/pages/FAQ.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - {{ t('faq.title') }} - - - - - {{ faq.question }} - {{ faq.answer }} - - - - - diff --git a/src/pages/Home.vue b/src/pages/Home.vue index 72115be..32efb40 100644 --- a/src/pages/Home.vue +++ b/src/pages/Home.vue @@ -1,137 +1,10 @@ + + + + - - - - - - - - {{ t('home.title') }} - - - - {{ t('home.subtitle') }} - - - - - - {{ t('home.selectTown') }} - - - - - - - {{ town.label }} - - - - - - - - - - - - - - - - - - {{ t(`directory.categories.${category}`) }} - - - - - - - - - - - {{ t('home.browse') }} - - - - - - {{ t('home.learnMore') }} - - - - - - diff --git a/src/pages/Support.vue b/src/pages/Support.vue deleted file mode 100644 index cc6a48f..0000000 --- a/src/pages/Support.vue +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/router/index.ts b/src/router/index.ts index 8702666..7b6a5f5 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,8 +1,5 @@ import { createRouter, createWebHistory } from 'vue-router' import Home from '@/pages/Home.vue' -import Directory from '@/pages/Directory.vue' -import FAQ from '@/pages/FAQ.vue' -import Support from '@/pages/Support.vue' const router = createRouter({ history: createWebHistory(), @@ -12,28 +9,7 @@ const router = createRouter({ name: 'home', component: Home }, - { - path: '/directory', - name: 'directory', - component: Directory - }, - { - path: '/faq', - name: 'faq', - component: FAQ - }, - { - path: '/directory/:id', - name: 'directory-item', - component: () => import('@/pages/DirectoryItem.vue'), - props: true - }, - { - path: '/support', - name: 'support', - component: Support - } ] }) -export default router \ No newline at end of file +export default router diff --git a/src/stores/messages.ts b/src/stores/messages.ts deleted file mode 100644 index eeb6d66..0000000 --- a/src/stores/messages.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineStore } from 'pinia' -import { ref } from 'vue' -import type { DirectMessage } from '@/types/nostr' - -// Separate message handling into its own store -export const useMessageStore = defineStore('messages', () => { - const messages = ref>(new Map()) - const processedIds = ref(new Set()) - - const addMessage = async (pubkey: string, message: DirectMessage) => { - if (processedIds.value.has(message.id)) return - - processedIds.value.add(message.id) - const userMessages = messages.value.get(pubkey) || [] - messages.value.set(pubkey, [...userMessages, message].sort((a, b) => - a.created_at - b.created_at - )) - } - - return { - messages, - processedIds, - addMessage - } -}) \ No newline at end of file diff --git a/src/stores/nostr.ts b/src/stores/nostr.ts deleted file mode 100644 index d3f4ce5..0000000 --- a/src/stores/nostr.ts +++ /dev/null @@ -1,621 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed, watch } from 'vue' -import type { NostrEvent, NostrProfile, NostrAccount, DirectMessage } from '../types/nostr' -import { isValidPrivateKey, formatPrivateKey } from '@/lib/nostr' - -declare global { - interface Window { - NostrTools: { - getPublicKey: (privkey: string) => string - generatePrivateKey: () => string - nip04: { - encrypt: (privkey: string, pubkey: string, content: string) => Promise - decrypt: (privkey: string, pubkey: string, content: string) => Promise - } - getEventHash: (event: NostrEvent) => string - signEvent: (event: NostrEvent, privkey: string) => Promise - getSignature: (event: NostrEvent, privkey: string) => string - verifySignature: (event: NostrEvent) => boolean - nip19: { - decode: (str: string) => { type: string; data: string } - npubEncode: (hex: string) => string - } - relayInit: (url: string) => { - connect: () => Promise - sub: (filters: any[]) => { - on: (event: string, callback: (event: NostrEvent) => void) => void - } - publish: (event: NostrEvent) => { - on: (type: 'ok' | 'failed', cb: (msg?: string) => void) => void - } - close: () => void - } - } - } -} - -const DEFAULT_RELAYS = [ - 'wss://nostr.atitlan.io' -] - -// Helper functions -async function withTimeout(promise: Promise, timeoutMs: number = 10000): Promise { - return Promise.race([ - promise, - new Promise((_, reject) => - setTimeout(() => reject(new Error('Operation timed out')), timeoutMs) - ) - ]) -} - -// Add to state -const connectionStatus = ref<'connected' | 'connecting' | 'disconnected'>('disconnected') -const hasUnreadMessages = ref(false) -const viewedMessageIds = ref>(new Set( - JSON.parse(localStorage.getItem('nostr_viewed_messages') || '[]') -)) - -// Update in connect function -async function connectToRelay(url: string) { - connectionStatus.value = 'connecting' - console.log(`Attempting to connect to relay: ${url}`) - const relay = window.NostrTools.relayInit(url) - try { - console.log(`Initializing connection to ${url}...`) - await withTimeout(relay.connect()) - console.log(`Successfully connected to ${url}`) - connectionStatus.value = 'connected' - return relay - } catch (err) { - console.error(`Failed to connect to ${url}:`, err) - connectionStatus.value = 'disconnected' - return null - } -} - -async function publishEvent(event: NostrEvent, relays: { url: string }[]) { - const promises = relays.map(async ({ url }) => { - const relay = window.NostrTools.relayInit(url) - try { - await relay.connect() - const pub = relay.publish(event) - return new Promise((resolve, reject) => { - pub.on('ok', () => resolve(true)) - pub.on('failed', reject) - }) - } catch (err) { - console.error(`Failed to publish to ${url}:`, err) - return false - } - }) - - await Promise.all(promises) -} - -export const useNostrStore = defineStore('nostr', () => { - // State - const account = ref(JSON.parse(localStorage.getItem('nostr_account') || 'null')) - const profiles = ref>(new Map()) - const messages = ref>(new Map()) - const activeChat = ref(null) - const relayPool = ref([]) - const processedMessageIds = ref(new Set()) - const currentSubscription = ref(null) - const hasActiveSubscription = ref(false) - - // Load stored messages and IDs on initialization - const initializeFromStorage = () => { - try { - const messageMap = new Map( - JSON.parse(localStorage.getItem('nostr_messages') || '[]') - ) - - messageMap.forEach((msgs: DirectMessage[]) => { - msgs.forEach(msg => { - processedMessageIds.value.add(msg.id) - }) - }) - - messages.value = messageMap - } catch (err) { - console.error('Failed to load stored messages:', err) - localStorage.removeItem('nostr_messages') - } - } - - // Call initialization - initializeFromStorage() - - // Watch account changes and persist to localStorage - watch(account, (newAccount) => { - if (newAccount) { - localStorage.setItem('nostr_account', JSON.stringify(newAccount)) - } else { - localStorage.removeItem('nostr_account') - } - }, { deep: true }) - - // Watch messages for changes and persist - watch(messages, (newMessages) => { - try { - localStorage.setItem('nostr_messages', - JSON.stringify(Array.from(newMessages.entries())) - ) - } catch (err) { - console.error('Failed to save messages:', err) - } - }, { deep: true }) - - // Initialize store if account exists in localStorage - if (account.value) { - console.log('Found existing account, initializing connection...') - init() - } - - // Computed - const isLoggedIn = computed(() => !!account.value) - const currentMessages = computed(() => - activeChat.value ? messages.value.get(activeChat.value) || [] : [] - ) - - // Initialize connection if account exists - async function init() { - if (!account.value) return - - try { - // Only clear profiles and processed IDs - profiles.value.clear() - processedMessageIds.value.clear() - - // Connect to relays - relayPool.value = (await Promise.all( - account.value.relays.map(async relay => { - console.log('Connecting to relay:', relay.url) - const connection = await connectToRelay(relay.url) - if (!connection) { - console.error('Failed to connect to relay:', relay.url) - } - return connection - }) - )).filter((relay): relay is any => relay !== null) - - if (relayPool.value.length === 0) { - throw new Error('Failed to connect to any relays') - } - - // Setup visibility change handler - setupVisibilityHandler() - - // Subscribe to messages in the background - subscribeToMessages().catch(err => { - console.error('Background subscription failed:', err) - }) - - } catch (err) { - console.error('Failed to initialize:', err) - throw err - } - } - - // Actions - async function login(privkey: string) { - if (!isValidPrivateKey(privkey)) { - throw new Error('Invalid private key') - } - - const formattedKey = formatPrivateKey(privkey) - const pubkey = window.NostrTools.getPublicKey(formattedKey) - - account.value = { - pubkey, - privkey: formattedKey, - relays: DEFAULT_RELAYS.map(url => ({ url, read: true, write: true })) - } - - // Initialize connection in the background - init().catch(err => { - console.error('Background initialization failed:', err) - }) - } - - async function logout() { - account.value = null - relayPool.value.forEach(relay => relay.close()) - relayPool.value = [] - messages.value.clear() - profiles.value.clear() - processedMessageIds.value.clear() - activeChat.value = null - localStorage.removeItem('nostr_messages') - localStorage.removeItem('nostr_account') - hasUnreadMessages.value = false - localStorage.removeItem('nostr_unread_messages') - viewedMessageIds.value.clear() - localStorage.removeItem('nostr_viewed_messages') - } - - const addMessage = async (pubkey: string, message: DirectMessage) => { - // Skip if we've already processed this message - if (processedMessageIds.value.has(message.id)) { - return - } - - processedMessageIds.value.add(message.id) - const userMessages = messages.value.get(pubkey) || [] - - // Check for duplicates - const isDuplicate = userMessages.some(msg => - msg.content === message.content && - Math.abs(msg.created_at - message.created_at) < 1 - ) - - if (!isDuplicate) { - messages.value.set(pubkey, [...userMessages, message].sort((a, b) => - a.created_at - b.created_at - )) - - // Only set unread if: - // 1. Message came from websocket (not storage) - // 2. Not from current chat - // 3. Not sent by us - if (!message.fromStorage && pubkey !== activeChat.value && !message.sent) { - console.log('New unread message received:', { pubkey, messageId: message.id }) - hasUnreadMessages.value = true - } - } - } - - async function sendMessage(to: string, content: string) { - if (!account.value) return - - const encrypted = await window.NostrTools.nip04.encrypt(account.value.privkey, to, content) - const event: NostrEvent = { - kind: 4, - pubkey: account.value.pubkey, - created_at: Math.floor(Date.now() / 1000), - tags: [['p', to]], - content: encrypted, - id: '', - sig: '' - } - - event.id = window.NostrTools.getEventHash(event) - event.sig = await window.NostrTools.signEvent(event, account.value.privkey) - - // Add to local messages first - const dm: DirectMessage = { - id: event.id, - pubkey: to, - content, - created_at: event.created_at, - sent: true - } - - await addMessage(to, dm) - - // Then publish to relays - await publishEvent(event, account.value.relays) - } - - async function subscribeToMessages() { - if (!account.value || hasActiveSubscription.value) return - - hasActiveSubscription.value = true - // Cleanup existing subscription - unsubscribeFromMessages() - - // Get timestamp from 24 hours ago - const since = Math.floor(Date.now() / 1000) - (24 * 60 * 60) - let hasReceivedMessages = false - - try { - const subscribeToRelay = (relay: any) => { - return new Promise((resolve) => { - const subs: any[] = [] - - try { - console.log('Setting up subscriptions for relay...') - - // Subscribe to received messages - const receivedSub = relay.sub([{ - kinds: [4], - '#p': [account.value!.pubkey], - since, - limit: 100 // Add limit to ensure we get historical messages - }]) - subs.push(receivedSub) - - // Subscribe to sent messages - const sentSub = relay.sub([{ - kinds: [4], - authors: [account.value!.pubkey], - since, - limit: 100 // Add limit to ensure we get historical messages - }]) - subs.push(sentSub) - - // Handle received messages - receivedSub.on('event', async (event: NostrEvent) => { - hasReceivedMessages = true - try { - if (processedMessageIds.value.has(event.id)) return - - const decrypted = await window.NostrTools.nip04.decrypt( - account.value!.privkey, - event.pubkey, - event.content - ) - - const dm: DirectMessage = { - id: event.id, - pubkey: event.pubkey, - content: decrypted, - created_at: event.created_at, - sent: false, - fromStorage: false // Mark as not from storage - } - - await addMessage(event.pubkey, dm) - } catch (err) { - console.error('Failed to decrypt received message:', err) - } - }) - - // Handle sent messages - sentSub.on('event', async (event: NostrEvent) => { - hasReceivedMessages = true - try { - if (processedMessageIds.value.has(event.id)) return - - const targetPubkey = event.tags.find(tag => tag[0] === 'p')?.[1] - if (!targetPubkey) return - - const decrypted = await window.NostrTools.nip04.decrypt( - account.value!.privkey, - targetPubkey, - event.content - ) - - const dm: DirectMessage = { - id: event.id, - pubkey: targetPubkey, - content: decrypted, - created_at: event.created_at, - sent: true - } - - await addMessage(targetPubkey, dm) - } catch (err) { - console.error('Failed to decrypt sent message:', err) - } - }) - - // Handle EOSE (End of Stored Events) - receivedSub.on('eose', () => { - console.log('Received EOSE for received messages') - if (!hasReceivedMessages) { - console.log('No messages received yet, keeping subscription open') - } - }) - - sentSub.on('eose', () => { - console.log('Received EOSE for sent messages') - if (!hasReceivedMessages) { - console.log('No messages received yet, keeping subscription open') - } - }) - - // Store subscriptions for cleanup - currentSubscription.value = { - unsub: () => { - subs.forEach(sub => { - try { - if (sub && typeof sub.unsub === 'function') { - sub.unsub() - } - } catch (err) { - console.debug('Failed to unsubscribe:', err) - } - }) - } - } - - // Keep subscription open - resolve(true) - - } catch (err) { - console.debug('Error in subscription setup:', err) - resolve(false) - } - }) - } - - // Wait for all relays to set up subscriptions - const results = await Promise.all( - relayPool.value.map(relay => subscribeToRelay(relay)) - ) - - // Consider success if at least one relay worked - return results.some(result => result) - } catch (err) { - console.debug('Subscription process failed:', err) - return false - } - } - - function unsubscribeFromMessages() { - if (currentSubscription.value && typeof currentSubscription.value.unsub === 'function') { - try { - currentSubscription.value.unsub() - } catch (err) { - console.error('Failed to unsubscribe:', err) - } - currentSubscription.value = null - } - hasActiveSubscription.value = false - } - - async function loadProfiles() { - if (!account.value) return - - const pubkeysToLoad = new Set() - - // Collect all unique pubkeys from messages - for (const [pubkey] of messages.value.entries()) { - if (!profiles.value.has(pubkey)) { - pubkeysToLoad.add(pubkey) - } - } - - if (pubkeysToLoad.size === 0) return - - try { - const filter = { - kinds: [0], - authors: Array.from(pubkeysToLoad) - } - - const loadFromRelay = (relay: any) => { - return new Promise((resolve) => { - const sub = relay.sub([filter]) - - sub.on('event', (event: NostrEvent) => { - try { - const profile = JSON.parse(event.content) - profiles.value.set(event.pubkey, { - pubkey: event.pubkey, - name: profile.name, - picture: profile.picture, - about: profile.about, - nip05: profile.nip05 - }) - } catch (err) { - console.error('Failed to parse profile:', err) - } - }) - - // Resolve after receiving EOSE (End of Stored Events) - sub.on('eose', () => { - resolve() - }) - - // Set a timeout in case EOSE is not received - setTimeout(() => { - resolve() - }, 5000) - }) - } - - // Load profiles from all relays concurrently - await Promise.all(relayPool.value.map(relay => loadFromRelay(relay))) - - } catch (err) { - console.error('Failed to load profiles:', err) - } - } - - // Add a reconnection function - async function reconnectToRelays() { - if (!account.value) return - - console.log('Attempting to reconnect to relays...') - - // Close existing connections - relayPool.value.forEach(relay => { - try { - relay.close() - } catch (err) { - console.error('Error closing relay:', err) - } - }) - relayPool.value = [] - - // Reconnect - relayPool.value = (await Promise.all( - account.value.relays.map(async relay => { - console.log('Reconnecting to relay:', relay.url) - const connection = await connectToRelay(relay.url) - if (!connection) { - console.error('Failed to reconnect to relay:', relay.url) - } - return connection - }) - )).filter((relay): relay is any => relay !== null) - - if (relayPool.value.length === 0) { - throw new Error('Failed to connect to any relays') - } - - // Resubscribe to messages - await subscribeToMessages() - } - - // Update visibility handler - function setupVisibilityHandler() { - const handleVisibilityChange = async () => { - if (document.visibilityState === 'visible' && account.value) { - console.log('Page became visible, checking connection...') - try { - // Only reconnect if we don't have active connections - if (relayPool.value.length === 0 || !hasActiveSubscription.value) { - await reconnectToRelays() - } - } catch (err) { - console.error('Failed to reconnect:', err) - } - } - } - - // Remove any existing handler - document.removeEventListener('visibilitychange', handleVisibilityChange) - document.addEventListener('visibilitychange', handleVisibilityChange) - - // Add focus handler for mobile - window.addEventListener('focus', handleVisibilityChange) - } - - // Add function to clear unread state - function clearUnreadMessages() { - hasUnreadMessages.value = false - localStorage.setItem('nostr_unread_messages', 'false') - - // Mark all current chat messages as viewed - if (activeChat.value) { - const chatMessages = messages.value.get(activeChat.value) || [] - chatMessages.forEach(msg => { - viewedMessageIds.value.add(msg.id) - }) - // Persist viewed message IDs - localStorage.setItem('nostr_viewed_messages', - JSON.stringify(Array.from(viewedMessageIds.value)) - ) - } - } - - // Add to watch section - watch(activeChat, () => { - // Clear unread messages when changing to a chat - clearUnreadMessages() - }) - - return { - account, - profiles, - messages, - activeChat, - isLoggedIn, - currentMessages, - init, - login, - logout, - sendMessage, - subscribeToMessages, - unsubscribeFromMessages, - loadProfiles, - connectionStatus, - hasActiveSubscription, - hasUnreadMessages, - clearUnreadMessages, - reconnectToRelays, - } -}) \ No newline at end of file diff --git a/src/types/directory.ts b/src/types/directory.ts deleted file mode 100644 index c4cf191..0000000 --- a/src/types/directory.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface DirectoryItem { - id: string - name: string - category: 'restaurant' | 'lodging' | 'goods' | 'services' | 'tuktuk' | 'taxi' | 'lancha' | 'other' - local?: boolean - description?: string - url?: string - address?: string - town?: string - mapsUrl?: string - contact?: string - contactType?: ('whatsapp' | 'telegram')[] - lightning?: string - coordinates?: { - lat: number - lng: number - } - imageUrl?: string - social?: { - facebook?: string - instagram?: string - twitter?: string - youtube?: string - } -} diff --git a/src/types/nostr.ts b/src/types/nostr.ts deleted file mode 100644 index 091392d..0000000 --- a/src/types/nostr.ts +++ /dev/null @@ -1,38 +0,0 @@ -export interface NostrEvent { - kind: number - pubkey: string - content: string - tags: string[][] - created_at: number - id: string - sig: string -} - -export interface NostrProfile { - pubkey: string - name?: string - picture?: string - about?: string - nip05?: string -} - -export interface NostrRelayConfig { - url: string - read?: boolean - write?: boolean -} - -export interface NostrAccount { - pubkey: string - privkey: string - relays: NostrRelayConfig[] -} - -export interface DirectMessage { - id: string - pubkey: string - content: string - created_at: number - sent: boolean - fromStorage?: boolean -} \ No newline at end of file
Support Agent
- No results found. Try adjusting your filters. -
- {{ t('directory.subtitle') }} -
{{ t('directory.itemNotFoundDesc') }}
- {{ t('home.subtitle') }} -