diff --git a/src/components/FirstRunChecklist.tsx b/src/components/FirstRunChecklist.tsx index fe0fa0d..4a45628 100644 --- a/src/components/FirstRunChecklist.tsx +++ b/src/components/FirstRunChecklist.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from 'react'; +import { Link } from 'react-router-dom'; import { X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useCurrentUser } from '@/hooks/useCurrentUser'; @@ -78,6 +79,13 @@ export function FirstRunChecklist({ onFindNearby }: { onFindNearby?: () => void > {t('mapUi.gotIt')} + setDismissed(true)} + className="self-center text-xs font-medium text-gray-500 underline underline-offset-2 hover:text-gray-300" + > + {t('mapUi.firstRunJoinCrew')} + ); diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index ad415c7..de66881 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -843,7 +843,7 @@ export function Layout({ children, noPadding }: LayoutProps) { alt="SkateSpots" className="w-9 h-9 rounded-lg flex-shrink-0 shadow-lg shadow-red-500/30" /> - SKATESPOTS diff --git a/src/components/MuteMenuItem.tsx b/src/components/MuteMenuItem.tsx index ced3169..bf82149 100644 --- a/src/components/MuteMenuItem.tsx +++ b/src/components/MuteMenuItem.tsx @@ -1,8 +1,9 @@ -import { VolumeX } from 'lucide-react'; +import { Volume2, VolumeX } from 'lucide-react'; import { DropdownMenuItem } from '@/components/ui/dropdown-menu'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useMuteList } from '@/hooks/useMuteList'; import { useMuteUser } from '@/hooks/useMuteUser'; +import { useUnmuteUser } from '@/hooks/useUnmuteUser'; import { useTranslation } from '@/contexts/language'; /** Inline mute action for content cards. Renders nothing when logged out. */ @@ -17,21 +18,23 @@ export function MuteMenuItem({ const { user } = useCurrentUser(); const { data: muted } = useMuteList(); const mute = useMuteUser(); + const unmute = useUnmuteUser(); if (!user || user.pubkey === pubkey) return null; const isMuted = !!muted?.has(pubkey); return ( { event.preventDefault(); event.stopPropagation(); - if (!isMuted) mute.mutate(pubkey); + if (isMuted) unmute.mutate(pubkey); + else mute.mutate(pubkey); }} className={className} > - - {isMuted ? t('walletUi.trust.muted') : t('walletUi.trust.mute')} + {isMuted ? : } + {isMuted ? t('walletUi.trust.unmute') : t('walletUi.trust.mute')} ); } diff --git a/src/hooks/useDmMute.ts b/src/hooks/useDmMute.ts new file mode 100644 index 0000000..060e784 --- /dev/null +++ b/src/hooks/useDmMute.ts @@ -0,0 +1,41 @@ +import { useCallback, useEffect, useState } from 'react'; + +const KEY = 'skatespots:dm-muted-threads'; +const EVENT = 'skatespots:dm-muted-threads-change'; + +function read(): string[] { + try { + const raw = localStorage.getItem(KEY); + const parsed = raw ? JSON.parse(raw) as unknown : []; + return Array.isArray(parsed) ? parsed.filter((id): id is string => typeof id === 'string') : []; + } catch { + return []; + } +} + +/** Device-local per-thread mute for 1:1 DMs, group DMs and crew chat keys. */ +export function useDmMute() { + const [keys, setKeys] = useState(read); + + useEffect(() => { + const onChange = (event: Event) => { + const detail = (event as CustomEvent).detail; + if (Array.isArray(detail)) setKeys(detail); + }; + window.addEventListener(EVENT, onChange); + return () => window.removeEventListener(EVENT, onChange); + }, []); + + const write = useCallback((next: string[]) => { + setKeys(next); + try { localStorage.setItem(KEY, JSON.stringify(next)); } catch { /* ignore */ } + window.dispatchEvent(new CustomEvent(EVENT, { detail: next })); + }, []); + + const isMuted = useCallback((key: string | undefined) => !!key && keys.includes(key), [keys]); + const toggle = useCallback((key: string) => { + write(keys.includes(key) ? keys.filter((k) => k !== key) : [...keys, key].slice(-200)); + }, [keys, write]); + + return { isMuted, toggle }; +} diff --git a/src/hooks/useUnmuteUser.ts b/src/hooks/useUnmuteUser.ts new file mode 100644 index 0000000..0b7c745 --- /dev/null +++ b/src/hooks/useUnmuteUser.ts @@ -0,0 +1,51 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useNostr } from '@nostrify/react'; +import { useNostrPublish } from '@/hooks/useNostrPublish'; +import { useCurrentUser } from '@/hooks/useCurrentUser'; +import { useToast } from '@/hooks/useToast'; +import { useTranslation } from '@/contexts/language'; +import { onlyVerified } from '@/lib/verifyEvents'; +import { removeLocalMute } from '@/lib/localMutes'; +import { MUTE_LIST_KIND } from '@/hooks/useMuteList'; + +/** Remove a pubkey from the viewer's NIP-51 kind-10000 mute list. */ +export function useUnmuteUser() { + const { nostr } = useNostr(); + const { mutateAsync: publish } = useNostrPublish(); + const { user } = useCurrentUser(); + const { toast } = useToast(); + const { t } = useTranslation(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (target: string) => { + if (!user) throw new Error(t('walletUi.trust.loginToMute')); + const events = onlyVerified(await nostr.query( + [{ kinds: [MUTE_LIST_KIND], authors: [user.pubkey], limit: 1 }], + { signal: AbortSignal.timeout(8000) }, + )); + const existing = (events[0]?.tags ?? []) + .filter(([name]) => name === 'p') + .map(([, value]) => value) + .filter((value) => value && value !== target); + await publish({ + kind: MUTE_LIST_KIND, + content: '', + tags: existing.map((pk) => ['p', pk]), + }); + }, + onSuccess: (_data, target) => { + removeLocalMute(target); + queryClient.invalidateQueries({ queryKey: ['mute-list', user?.pubkey] }); + queryClient.invalidateQueries(); + toast({ title: t('walletUi.trust.unmuted'), description: t('walletUi.trust.unmutedDesc') }); + }, + onError: (error) => { + toast({ + title: t('walletUi.trust.unmuteFailed'), + description: error instanceof Error ? error.message : undefined, + variant: 'destructive', + }); + }, + }); +} diff --git a/src/i18n/ca/batch4.ts b/src/i18n/ca/batch4.ts index d763065..a074c71 100644 --- a/src/i18n/ca/batch4.ts +++ b/src/i18n/ca/batch4.ts @@ -1123,6 +1123,7 @@ const batch4 = { "eventDetail": { "aCrew": "una crew", "addToCalendar": "Afegeix al calendari", + "eventPass": "Passi de l'esdeveniment", "backToEvents": "Torna als esdeveniments", "cancelled": "Aquest esdeveniment s'ha cancel·lat", "checkUpdate": ": consulta l'actualització", diff --git a/src/i18n/ca/batch6.ts b/src/i18n/ca/batch6.ts index 6e54e15..3458204 100644 --- a/src/i18n/ca/batch6.ts +++ b/src/i18n/ca/batch6.ts @@ -428,6 +428,10 @@ const batch6 = { mute: "Silencia", muted: "Silenciat", mutedDesc: "No veuràs el seu contingut.", + unmute: "Deixa de silenciar", + unmuted: "Silenciament desactivat", + unmutedDesc: "El seu contingut tornarà a aparèixer.", + unmuteFailed: "No s'ha pogut deixar de silenciar. Torna-ho a provar.", muteFailed: "No s'ha pogut silenciar. Torna-ho a provar.", loginToMute: "Inicia sessió per silenciar.", attributedNetwork: "De gent de la teva xarxa", diff --git a/src/i18n/cs/drift.ts b/src/i18n/cs/drift.ts index f77903e..8f003c3 100644 --- a/src/i18n/cs/drift.ts +++ b/src/i18n/cs/drift.ts @@ -114,6 +114,10 @@ const drift = { "mute": "Ztlumit", "muted": "Ztlumeno", "mutedDesc": "Nebudeš vídat jejich obsah.", + "unmute": "Zrušit ztlumení", + "unmuted": "Ztlumení zrušeno", + "unmutedDesc": "Jejich obsah se znovu objeví.", + "unmuteFailed": "Ztlumení se nepodařilo zrušit. Zkus to znovu.", "muteFailed": "Ztlumení se nezdařilo. Zkus to znovu.", "loginToMute": "Přihlas se, abys mohl ztlumit.", "attributedNetwork": "Od lidí ve tvé síti", @@ -467,6 +471,11 @@ const drift = { offerPickup: "Tento týden si to můžu vyzvednout.", offerShipping: "Můžeš spojit dopravu?", }, + "placesUi": { + "eventDetail": { + "eventPass": "Vstupenka na akci" + }, + }, }; export default drift; diff --git a/src/i18n/de/drift.ts b/src/i18n/de/drift.ts index 03e9b04..b2f3dc6 100644 --- a/src/i18n/de/drift.ts +++ b/src/i18n/de/drift.ts @@ -116,6 +116,10 @@ const drift = { mute: "Stummschalten", muted: "Stummgeschaltet", mutedDesc: "Du siehst deren Inhalte nicht.", + unmute: "Stummschaltung aufheben", + unmuted: "Stummschaltung aufgehoben", + unmutedDesc: "Ihre Inhalte werden wieder angezeigt.", + unmuteFailed: "Stummschaltung konnte nicht aufgehoben werden. Versuch es erneut.", muteFailed: "Stummschalten fehlgeschlagen. Versuche es erneut.", loginToMute: "Melde dich an, um stummzuschalten.", attributedNetwork: "Von Personen in deinem Netzwerk", @@ -469,6 +473,11 @@ const drift = { offerPickup: "Ich kann es diese Woche abholen.", offerShipping: "Kannst du den Versand bündeln?", }, + placesUi: { + eventDetail: { + eventPass: "Event-Pass" + }, + }, }; export default drift; diff --git a/src/i18n/el/drift.ts b/src/i18n/el/drift.ts index 8012509..fea9db3 100644 --- a/src/i18n/el/drift.ts +++ b/src/i18n/el/drift.ts @@ -118,6 +118,10 @@ const drift = { mute: "Σίγαση", muted: "Σε σίγαση", mutedDesc: "Δεν θα βλέπεις το περιεχόμενό τους.", + unmute: "Κατάργηση σίγασης", + unmuted: "Η σίγαση καταργήθηκε", + unmutedDesc: "Το περιεχόμενό τους θα εμφανιστεί ξανά.", + unmuteFailed: "Δεν ήταν δυνατή η κατάργηση της σίγασης. Δοκίμασε ξανά.", muteFailed: "Δεν ήταν δυνατή η σίγαση. Δοκίμασε ξανά.", loginToMute: "Συνδέσου για σίγαση.", attributedNetwork: "Από άτομα στο δίκτυό σου", @@ -471,6 +475,11 @@ const drift = { offerPickup: "Μπορώ να το παραλάβω αυτή την εβδομάδα.", offerShipping: "Μπορείς να συνδυάσεις τα μεταφορικά;", }, + placesUi: { + eventDetail: { + eventPass: "Πάσο εκδήλωσης" + }, + }, }; export default drift; diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 6ebf4b1..593e335 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -3427,6 +3427,7 @@ placesUi: { eventDetail: { aCrew: 'a crew', addToCalendar: 'Add to calendar', + eventPass: 'Event pass', backToEvents: 'Back to Events', cancelled: 'This event was cancelled', checkUpdate: '— check the update', @@ -6207,6 +6208,10 @@ walletUi: { mute: 'Mute', muted: 'Muted', mutedDesc: 'You will not see their content.', + unmute: 'Unmute', + unmuted: 'Unmuted', + unmutedDesc: 'Their content will appear again.', + unmuteFailed: 'Could not unmute. Try again.', muteFailed: 'Could not mute. Try again.', loginToMute: 'Log in to mute.', attributedNetwork: 'From people in your network', diff --git a/src/i18n/es.ts b/src/i18n/es.ts index 81bfd40..05e465a 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -3427,6 +3427,7 @@ placesUi: { eventDetail: { aCrew: 'una crew', addToCalendar: 'Añadir al calendario', + eventPass: 'Pase del evento', backToEvents: 'Volver a eventos', cancelled: 'Este evento se canceló', checkUpdate: '— consulta la actualización', @@ -6207,6 +6208,10 @@ walletUi: { mute: 'Silenciar', muted: 'Silenciado', mutedDesc: 'No veras su contenido.', + unmute: 'Dejar de silenciar', + unmuted: 'Sin silencio', + unmutedDesc: 'Su contenido volvera a aparecer.', + unmuteFailed: 'No se pudo dejar de silenciar. Intentalo de nuevo.', muteFailed: 'No se pudo silenciar. Intentalo de nuevo.', loginToMute: 'Inicia sesion para silenciar.', attributedNetwork: 'De personas de tu red', diff --git a/src/i18n/eu/batch4.ts b/src/i18n/eu/batch4.ts index d5fa698..651720e 100644 --- a/src/i18n/eu/batch4.ts +++ b/src/i18n/eu/batch4.ts @@ -1123,6 +1123,7 @@ const batch4 = { eventDetail: { aCrew: "krew bat", addToCalendar: "Gehitu egutegira", + eventPass: "Ekitaldiko pasea", backToEvents: "Itzuli ekitaldietara", cancelled: "Ekitaldi hau bertan behera utzi da", checkUpdate: "— begiratu eguneratzea", diff --git a/src/i18n/eu/batch6.ts b/src/i18n/eu/batch6.ts index 43d2e7a..322a4a7 100644 --- a/src/i18n/eu/batch6.ts +++ b/src/i18n/eu/batch6.ts @@ -428,6 +428,10 @@ const batch6 = { mute: "Mututu", muted: "Mutututa", mutedDesc: "Ez duzu haien edukia ikusiko.", + unmute: "Mutua kendu", + unmuted: "Mutua kenduta", + unmutedDesc: "Haien edukia berriro agertuko da.", + unmuteFailed: "Ezin izan da mutua kendu. Saiatu berriro.", muteFailed: "Ezin izan da mututu. Saiatu berriro.", loginToMute: "Hasi saioa mututzeko.", attributedNetwork: "Zure sareko pertsonen eskutik", diff --git a/src/i18n/fr/drift.ts b/src/i18n/fr/drift.ts index 2802078..6f5128a 100644 --- a/src/i18n/fr/drift.ts +++ b/src/i18n/fr/drift.ts @@ -116,6 +116,10 @@ const drift = { mute: "Masquer", muted: "Masqué", mutedDesc: "Vous ne verrez pas son contenu.", + unmute: "Ne plus masquer", + unmuted: "Plus masqué", + unmutedDesc: "Son contenu réapparaîtra.", + unmuteFailed: "Impossible de ne plus masquer. Réessayez.", muteFailed: "Impossible de masquer. Réessayez.", loginToMute: "Connectez-vous pour masquer.", attributedNetwork: "De personnes de votre réseau", @@ -469,6 +473,11 @@ const drift = { offerPickup: "Je peux venir le chercher cette semaine.", offerShipping: "Peux-tu regrouper l'expédition ?", }, + placesUi: { + eventDetail: { + eventPass: "Pass événement" + }, + }, }; export default drift; diff --git a/src/i18n/gl/batch4.ts b/src/i18n/gl/batch4.ts index ceab977..f9aed18 100644 --- a/src/i18n/gl/batch4.ts +++ b/src/i18n/gl/batch4.ts @@ -1123,6 +1123,7 @@ const batch4 = { eventDetail: { aCrew: "un crew", addToCalendar: "Engadir ao calendario", + eventPass: "Pase do evento", backToEvents: "Volver aos eventos", cancelled: "Este evento cancelouse", checkUpdate: ": consulta a actualización", diff --git a/src/i18n/gl/batch6.ts b/src/i18n/gl/batch6.ts index 986c4b5..ab99777 100644 --- a/src/i18n/gl/batch6.ts +++ b/src/i18n/gl/batch6.ts @@ -428,6 +428,10 @@ const batch6 = { "mute": "Silenciar", "muted": "Silenciado", "mutedDesc": "Non verás o seu contido.", + "unmute": "Deixar de silenciar", + "unmuted": "Silenciamento desfeito", + "unmutedDesc": "O seu contido volverá aparecer.", + "unmuteFailed": "Non se puido deixar de silenciar. Téntao de novo.", "muteFailed": "Non se puido silenciar. Téntao de novo.", "loginToMute": "Inicia sesión para silenciar.", "attributedNetwork": "De persoas da túa rede", diff --git a/src/i18n/hi/batch4.ts b/src/i18n/hi/batch4.ts index 51a40d3..4d44d31 100644 --- a/src/i18n/hi/batch4.ts +++ b/src/i18n/hi/batch4.ts @@ -1123,6 +1123,7 @@ const batch4 = { "eventDetail": { "aCrew": "एक क्रू", "addToCalendar": "कैलेंडर में जोड़ें", + "eventPass": "इवेंट पास", "backToEvents": "इवेंट्स पर वापस जाएँ", "cancelled": "यह इवेंट रद्द हो गया था", "checkUpdate": "— अपडेट देखें", diff --git a/src/i18n/hi/batch6.ts b/src/i18n/hi/batch6.ts index a1c8b2f..da48249 100644 --- a/src/i18n/hi/batch6.ts +++ b/src/i18n/hi/batch6.ts @@ -428,6 +428,10 @@ const batch6 = { "mute": "म्यूट करें", "muted": "म्यूट किया", "mutedDesc": "आपको इनका कंटेंट नहीं दिखेगा।", + "unmute": "अनम्यूट करें", + "unmuted": "अनम्यूट किया", + "unmutedDesc": "उनका कंटेंट फिर से दिखाई देगा।", + "unmuteFailed": "अनम्यूट नहीं हो सका। फिर कोशिश करें।", "muteFailed": "म्यूट नहीं हो सका। फिर कोशिश करें।", "loginToMute": "म्यूट करने के लिए लॉग इन करें।", "attributedNetwork": "आपके नेटवर्क के लोगों से", diff --git a/src/i18n/id/drift.ts b/src/i18n/id/drift.ts index b29ff8e..d87db1c 100644 --- a/src/i18n/id/drift.ts +++ b/src/i18n/id/drift.ts @@ -110,6 +110,10 @@ const driftStrings = { mute: 'Bisukan', muted: 'Dibisukan', mutedDesc: 'Kamu tidak akan melihat konten mereka.', + unmute: 'Batalkan bisukan', + unmuted: 'Bisukan dibatalkan', + unmutedDesc: 'Konten mereka akan muncul lagi.', + unmuteFailed: 'Gagal membatalkan bisukan. Coba lagi.', muteFailed: 'Gagal membisukan. Coba lagi.', loginToMute: 'Masuk untuk membisukan.', attributedNetwork: 'Dari orang-orang di jaringanmu', @@ -463,6 +467,11 @@ const driftStrings = { offerPickup: "Saya bisa ambil sendiri minggu ini.", offerShipping: "Bisa gabung ongkos kirimnya?", }, + placesUi: { + eventDetail: { + eventPass: "Tiket acara" + }, + }, }; export default driftStrings; diff --git a/src/i18n/it/drift.ts b/src/i18n/it/drift.ts index d40a5bc..abde021 100644 --- a/src/i18n/it/drift.ts +++ b/src/i18n/it/drift.ts @@ -116,6 +116,10 @@ const drift = { mute: "Silenzia", muted: "Silenziato", mutedDesc: "Non vedrai i suoi contenuti.", + unmute: "Annulla silenziamento", + unmuted: "Silenziamento annullato", + unmutedDesc: "I suoi contenuti torneranno visibili.", + unmuteFailed: "Impossibile annullare il silenziamento. Riprova.", muteFailed: "Impossibile silenziare. Riprova.", loginToMute: "Accedi per silenziare.", attributedNetwork: "Da persone nella tua rete", @@ -469,6 +473,11 @@ const drift = { offerPickup: "Posso ritirarlo questa settimana.", offerShipping: "Puoi raggruppare la spedizione?", }, + placesUi: { + eventDetail: { + eventPass: "Pass evento" + }, + }, }; export default drift; diff --git a/src/i18n/ja/drift.ts b/src/i18n/ja/drift.ts index 27eb2f5..6302a7b 100644 --- a/src/i18n/ja/drift.ts +++ b/src/i18n/ja/drift.ts @@ -126,6 +126,10 @@ const drift: Dict = { mute: 'ミュート', muted: 'ミュート済み', mutedDesc: '相手のコンテンツは表示されません。', + unmute: 'ミュート解除', + unmuted: 'ミュート解除済み', + unmutedDesc: '相手のコンテンツが再び表示されます。', + unmuteFailed: 'ミュートを解除できませんでした。もう一度お試しください。', muteFailed: 'ミュートできませんでした。もう一度お試しください。', loginToMute: 'ミュートするにはログインしてください。', attributedNetwork: 'あなたのネットワークの人から', @@ -479,6 +483,11 @@ const drift: Dict = { offerPickup: "今週中に受け取れます。", offerShipping: "送料をまとめられますか?", }, + placesUi: { + eventDetail: { + eventPass: "イベントパス" + }, + }, }; export default drift; diff --git a/src/i18n/ko/drift.ts b/src/i18n/ko/drift.ts index 11bca11..fecadaf 100644 --- a/src/i18n/ko/drift.ts +++ b/src/i18n/ko/drift.ts @@ -126,6 +126,10 @@ const drift: Dict = { mute: '뮤트', muted: '뮤트됨', mutedDesc: '상대방의 콘텐츠가 표시되지 않습니다.', + unmute: '뮤트 해제', + unmuted: '뮤트 해제됨', + unmutedDesc: '상대방의 콘텐츠가 다시 표시됩니다.', + unmuteFailed: '뮤트를 해제하지 못했습니다. 다시 시도하세요.', muteFailed: '뮤트하지 못했습니다. 다시 시도하세요.', loginToMute: '뮤트하려면 로그인하세요.', attributedNetwork: '당신의 네트워크에 있는 사람들로부터', @@ -479,6 +483,11 @@ const drift: Dict = { offerPickup: "이번 주에 직접 수령할 수 있어요.", offerShipping: "배송비를 묶을 수 있나요?", }, + placesUi: { + eventDetail: { + eventPass: "이벤트 패스" + }, + }, }; export default drift; diff --git a/src/i18n/nl/drift.ts b/src/i18n/nl/drift.ts index 1d4d0d0..0461831 100644 --- a/src/i18n/nl/drift.ts +++ b/src/i18n/nl/drift.ts @@ -114,6 +114,10 @@ const drift = { "mute": "Dempen", "muted": "Gedempt", "mutedDesc": "Je ziet hun content niet meer.", + "unmute": "Dempen opheffen", + "unmuted": "Dempen opgeheven", + "unmutedDesc": "Je ziet hun content weer.", + "unmuteFailed": "Kon dempen niet opheffen. Probeer het opnieuw.", "muteFailed": "Kon niet dempen. Probeer het opnieuw.", "loginToMute": "Log in om te dempen.", "attributedNetwork": "Van mensen in je netwerk", @@ -467,6 +471,11 @@ const drift = { offerPickup: "Ik kan het deze week ophalen.", offerShipping: "Kun je de verzendkosten bundelen?", }, + "placesUi": { + "eventDetail": { + "eventPass": "Evenementenpas" + }, + }, }; export default drift; diff --git a/src/i18n/pl/drift.ts b/src/i18n/pl/drift.ts index 0936c33..6000039 100644 --- a/src/i18n/pl/drift.ts +++ b/src/i18n/pl/drift.ts @@ -114,6 +114,10 @@ const drift = { "mute": "Wycisz", "muted": "Wyciszony", "mutedDesc": "Nie zobaczysz ich treści.", + "unmute": "Wyłącz wyciszenie", + "unmuted": "Wyciszenie wyłączone", + "unmutedDesc": "Ich treści znów będą widoczne.", + "unmuteFailed": "Nie udało się wyłączyć wyciszenia. Spróbuj ponownie.", "muteFailed": "Nie udało się wyciszyć. Spróbuj ponownie.", "loginToMute": "Zaloguj się, aby wyciszyć.", "attributedNetwork": "Od osób w twojej sieci", @@ -467,6 +471,11 @@ const drift = { offerPickup: "Mogę odebrać w tym tygodniu.", offerShipping: "Czy możesz połączyć wysyłkę?", }, + "placesUi": { + "eventDetail": { + "eventPass": "Karnet na wydarzenie" + }, + }, }; export default drift; diff --git a/src/i18n/pt/drift.ts b/src/i18n/pt/drift.ts index d7e6684..0f59a96 100644 --- a/src/i18n/pt/drift.ts +++ b/src/i18n/pt/drift.ts @@ -116,6 +116,10 @@ const drift = { mute: "Silenciar", muted: "Silenciado", mutedDesc: "Você não verá o conteúdo dela.", + unmute: "Deixar de silenciar", + unmuted: "Silenciamento desfeito", + unmutedDesc: "O conteúdo dela voltará a aparecer.", + unmuteFailed: "Não foi possível deixar de silenciar. Tente novamente.", muteFailed: "Não foi possível silenciar. Tente novamente.", loginToMute: "Faça login para silenciar.", attributedNetwork: "De pessoas da sua rede", @@ -469,6 +473,11 @@ const drift = { offerPickup: "Consigo retirar esta semana.", offerShipping: "Você consegue agrupar o frete?", }, + placesUi: { + eventDetail: { + eventPass: "Passe do evento" + }, + }, }; export default drift; diff --git a/src/i18n/ro/drift.ts b/src/i18n/ro/drift.ts index 07c4fc2..7c00685 100644 --- a/src/i18n/ro/drift.ts +++ b/src/i18n/ro/drift.ts @@ -114,6 +114,10 @@ const drift = { "mute": "Oprește sunetul", "muted": "Fără sunet", "mutedDesc": "Nu le vei vedea conținutul.", + "unmute": "Reia sunetul", + "unmuted": "Sunet reactivat", + "unmutedDesc": "Conținutul lor va apărea din nou.", + "unmuteFailed": "Nu s-a putut reactiva sunetul. Încearcă din nou.", "muteFailed": "Nu s-a putut opri sunetul. Încearcă din nou.", "loginToMute": "Autentifică-te pentru a opri sunetul.", "attributedNetwork": "De la persoane din rețeaua ta", @@ -467,6 +471,11 @@ const drift = { offerPickup: "Pot ridica personal săptămâna asta.", offerShipping: "Poți combina transportul?", }, + "placesUi": { + "eventDetail": { + "eventPass": "Pass de eveniment" + }, + }, }; export default drift; diff --git a/src/i18n/ru/drift.ts b/src/i18n/ru/drift.ts index b5274c5..f3aa589 100644 --- a/src/i18n/ru/drift.ts +++ b/src/i18n/ru/drift.ts @@ -118,6 +118,10 @@ const drift = { mute: "Заглушить", muted: "Заглушено", mutedDesc: "Вы не будете видеть их контент.", + unmute: "Снять заглушение", + unmuted: "Заглушение снято", + unmutedDesc: "Их контент снова будет виден.", + unmuteFailed: "Не удалось снять заглушение. Попробуйте снова.", muteFailed: "Не удалось заглушить. Попробуйте снова.", loginToMute: "Войдите, чтобы заглушить.", attributedNetwork: "От людей из вашей сети", @@ -471,6 +475,11 @@ const drift = { offerPickup: "Могу забрать на этой неделе.", offerShipping: "Можете объединить доставку?", }, + placesUi: { + eventDetail: { + eventPass: "Пропуск на мероприятие" + }, + }, }; export default drift; diff --git a/src/i18n/sv/drift.ts b/src/i18n/sv/drift.ts index 7364ec9..c90c5db 100644 --- a/src/i18n/sv/drift.ts +++ b/src/i18n/sv/drift.ts @@ -118,6 +118,10 @@ const drift = { mute: "Tysta", muted: "Tystad", mutedDesc: "Du kommer inte se deras innehåll.", + unmute: "Sluta tysta", + unmuted: "Inte längre tystad", + unmutedDesc: "Deras innehåll visas igen.", + unmuteFailed: "Kunde inte sluta tysta. Försök igen.", muteFailed: "Kunde inte tysta. Försök igen.", loginToMute: "Logga in för att tysta.", attributedNetwork: "Från personer i ditt nätverk", @@ -471,6 +475,11 @@ const drift = { offerPickup: "Jag kan hämta den här veckan.", offerShipping: "Kan du samla frakten?", }, + placesUi: { + eventDetail: { + eventPass: "Evenemangspass" + }, + }, }; export default drift; diff --git a/src/i18n/th/drift.ts b/src/i18n/th/drift.ts index 265d335..0c1406b 100644 --- a/src/i18n/th/drift.ts +++ b/src/i18n/th/drift.ts @@ -110,6 +110,10 @@ const driftStrings = { mute: 'ปิดเสียง', muted: 'ปิดเสียงแล้ว', mutedDesc: 'คุณจะไม่เห็นเนื้อหาของพวกเขา', + unmute: 'เปิดเสียง', + unmuted: 'เปิดเสียงแล้ว', + unmutedDesc: 'เนื้อหาของพวกเขาจะปรากฏอีกครั้ง', + unmuteFailed: 'เปิดเสียงไม่สำเร็จ ลองอีกครั้ง', muteFailed: 'ปิดเสียงไม่สำเร็จ ลองอีกครั้ง', loginToMute: 'เข้าสู่ระบบเพื่อปิดเสียง', attributedNetwork: 'จากคนในเครือข่ายของคุณ', @@ -463,6 +467,11 @@ const driftStrings = { offerPickup: "ฉันรับเองได้ภายในสัปดาห์นี้", offerShipping: "รวมค่าส่งได้ไหม?", }, + placesUi: { + eventDetail: { + eventPass: "บัตรอีเวนต์" + }, + }, }; export default driftStrings; diff --git a/src/i18n/tr/drift.ts b/src/i18n/tr/drift.ts index 0a7b71f..cf9686b 100644 --- a/src/i18n/tr/drift.ts +++ b/src/i18n/tr/drift.ts @@ -110,6 +110,10 @@ const driftStrings = { mute: 'Sessize al', muted: 'Sessize alındı', mutedDesc: 'İçeriklerini görmezsin.', + unmute: 'Sesi aç', + unmuted: 'Sesi açıldı', + unmutedDesc: 'İçerikleri yeniden görünür.', + unmuteFailed: 'Sesi açılamadı. Tekrar dene.', muteFailed: 'Sessize alınamadı. Tekrar dene.', loginToMute: 'Sessize almak için giriş yap.', attributedNetwork: 'Ağındaki kişilerden', @@ -463,6 +467,11 @@ const driftStrings = { offerPickup: "Bu hafta kendim alabilirim.", offerShipping: "Kargo ücretini birleştirebilir misin?", }, + placesUi: { + eventDetail: { + eventPass: "Etkinlik pası" + }, + }, }; export default driftStrings; diff --git a/src/i18n/uk/drift.ts b/src/i18n/uk/drift.ts index 9ac16ed..8843599 100644 --- a/src/i18n/uk/drift.ts +++ b/src/i18n/uk/drift.ts @@ -118,6 +118,10 @@ const drift = { mute: "Заглушити", muted: "Заглушено", mutedDesc: "Ви не бачитимете їхній вміст.", + unmute: "Зняти заглушення", + unmuted: "Заглушення знято", + unmutedDesc: "Їхній вміст знову буде видно.", + unmuteFailed: "Не вдалося зняти заглушення. Спробуйте знову.", muteFailed: "Не вдалося заглушити. Спробуйте знову.", loginToMute: "Увійдіть, щоб заглушити.", attributedNetwork: "Від людей з вашої мережі", @@ -471,6 +475,11 @@ const drift = { offerPickup: "Можу забрати цього тижня.", offerShipping: "Можете об'єднати доставку?", }, + placesUi: { + eventDetail: { + eventPass: "Перепустка на подію" + }, + }, }; export default drift; diff --git a/src/i18n/vi/drift.ts b/src/i18n/vi/drift.ts index da9c09e..d02ca3b 100644 --- a/src/i18n/vi/drift.ts +++ b/src/i18n/vi/drift.ts @@ -110,6 +110,10 @@ const driftStrings = { mute: 'Ẩn', muted: 'Đã ẩn', mutedDesc: 'Bạn sẽ không thấy nội dung của họ.', + unmute: 'Bỏ ẩn', + unmuted: 'Đã bỏ ẩn', + unmutedDesc: 'Nội dung của họ sẽ hiện lại.', + unmuteFailed: 'Không thể bỏ ẩn. Thử lại.', muteFailed: 'Không thể ẩn. Thử lại.', loginToMute: 'Đăng nhập để ẩn.', attributedNetwork: 'Từ những người trong mạng lưới của bạn', @@ -463,6 +467,11 @@ const driftStrings = { offerPickup: "Tôi có thể tự lấy trong tuần này.", offerShipping: "Bạn gộp phí ship được không?", }, + placesUi: { + eventDetail: { + eventPass: "Thẻ sự kiện" + }, + }, }; export default driftStrings; diff --git a/src/i18n/zh-CN/drift.ts b/src/i18n/zh-CN/drift.ts index 3bccc7b..666d2ee 100644 --- a/src/i18n/zh-CN/drift.ts +++ b/src/i18n/zh-CN/drift.ts @@ -126,6 +126,10 @@ const drift: Dict = { mute: '屏蔽', muted: '已屏蔽', mutedDesc: '你将不会看到他们的内容。', + unmute: '取消屏蔽', + unmuted: '已取消屏蔽', + unmutedDesc: '他们的内容将重新显示。', + unmuteFailed: '无法取消屏蔽。请重试。', muteFailed: '无法屏蔽。请重试。', loginToMute: '登录以屏蔽。', attributedNetwork: '来自你网络中的人', @@ -479,6 +483,11 @@ const drift: Dict = { offerPickup: "我这周可以自取。", offerShipping: "可以合并运费吗?", }, + placesUi: { + eventDetail: { + eventPass: "活动通行证" + }, + }, }; export default drift; diff --git a/src/i18n/zh-TW/drift.ts b/src/i18n/zh-TW/drift.ts index 2de7761..6566311 100644 --- a/src/i18n/zh-TW/drift.ts +++ b/src/i18n/zh-TW/drift.ts @@ -126,6 +126,10 @@ const drift: Dict = { mute: '靜音', muted: '已靜音', mutedDesc: '你不會看到他們的內容。', + unmute: '取消靜音', + unmuted: '已取消靜音', + unmutedDesc: '他們的內容將重新顯示。', + unmuteFailed: '無法取消靜音。請重試。', muteFailed: '無法靜音。請重試。', loginToMute: '登入以靜音。', attributedNetwork: '來自你網路中的人', @@ -479,6 +483,11 @@ const drift: Dict = { offerPickup: "我這週可以自取。", offerShipping: "可以合併運費嗎?", }, + placesUi: { + eventDetail: { + eventPass: "活動通行證" + }, + }, }; export default drift; diff --git a/src/lib/localMutes.ts b/src/lib/localMutes.ts index 271e408..e84a0eb 100644 --- a/src/lib/localMutes.ts +++ b/src/lib/localMutes.ts @@ -39,3 +39,7 @@ export function addLocalMute(pubkey: string): void { list.push(pubkey); write(list); } + +export function removeLocalMute(pubkey: string): void { + write(read().filter((pk) => pk !== pubkey)); +} diff --git a/src/pages/ConversationPage.tsx b/src/pages/ConversationPage.tsx index e39a00a..eff531a 100644 --- a/src/pages/ConversationPage.tsx +++ b/src/pages/ConversationPage.tsx @@ -19,6 +19,7 @@ import { useCashuEscrow } from '@/hooks/wallet/useCashuEscrow'; import { EscrowPanel } from '@/components/EscrowPanel'; import { TranslateMessage } from '@/components/chat/TranslateMessage'; import { useDMRead } from '@/hooks/useDMRead'; +import { useDmMute } from '@/hooks/useDmMute'; import { useAuthor } from '@/hooks/useAuthor'; import { genUserName } from '@/lib/genUserName'; import { formatDistanceToNow } from '@/lib/dateFnsI18n'; @@ -29,6 +30,8 @@ import { Loader2, AlertCircle, RefreshCw, + VolumeX, + Volume2, CheckCircle2, Handshake, } from 'lucide-react'; @@ -115,6 +118,8 @@ export default function ConversationPage() { const escrow = useCashuEscrow(); const [isRedeeming, setIsRedeeming] = useState(false); const { markRead } = useDMRead(); + const { isMuted, toggle: toggleMute } = useDmMute(); + const muted = isMuted(otherPubkey ?? undefined); const author = useAuthor(otherPubkey || undefined); const metadata = author.data?.metadata; const displayName = metadata?.name ?? (otherPubkey ? genUserName(otherPubkey) : t('socialUi.conversation.unknown')); @@ -316,6 +321,18 @@ export default function ConversationPage() { {t('socialUi.conversation.encrypted')}

+ {otherPubkey && ( + + )} )} + {hasEventPass && ( + + + + )}