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 && (
+
+
+
+ )}