From a9669c17b8cbb4b3ca659d496d88a5bbf87020ba Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Tue, 13 Oct 2026 19:18:14 +0700 Subject: [PATCH] feat(crew-chat): shareable 'join this crew's chat' invite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MLS private-chat panel gets an admin 'Invite' action that shares the crew's private-chat link (…?tab=mls) via the OS share sheet or clipboard, landing the invitee on the Ask-to-join flow. Added cordnUi.shareInvite/shareInviteText/ inviteCopied to en/es and backfilled them into all 20 locale drift fragments so i18n stays at 100%. --- src/components/crew/CordnNativePanel.tsx | 48 +++++++++++++++++++----- src/i18n/cs/drift.ts | 3 ++ src/i18n/de/drift.ts | 3 ++ src/i18n/el/drift.ts | 3 ++ src/i18n/fr/drift.ts | 3 ++ src/i18n/id/drift.ts | 3 ++ src/i18n/it/drift.ts | 3 ++ src/i18n/ja/drift.ts | 3 ++ src/i18n/ko/drift.ts | 3 ++ src/i18n/legacy/cordnUi.ts | 6 +++ src/i18n/nl/drift.ts | 3 ++ src/i18n/pl/drift.ts | 3 ++ src/i18n/pt/drift.ts | 3 ++ src/i18n/ro/drift.ts | 3 ++ src/i18n/ru/drift.ts | 3 ++ src/i18n/sv/drift.ts | 3 ++ src/i18n/th/drift.ts | 3 ++ src/i18n/tr/drift.ts | 3 ++ src/i18n/uk/drift.ts | 3 ++ src/i18n/vi/drift.ts | 3 ++ src/i18n/zh-CN/drift.ts | 3 ++ src/i18n/zh-TW/drift.ts | 3 ++ 22 files changed, 104 insertions(+), 10 deletions(-) diff --git a/src/components/crew/CordnNativePanel.tsx b/src/components/crew/CordnNativePanel.tsx index f0d9b21..d133cbe 100644 --- a/src/components/crew/CordnNativePanel.tsx +++ b/src/components/crew/CordnNativePanel.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import type { ReactNode } from 'react'; -import { KeyRound, Lock, Plus, RefreshCw, Send, UserPlus, Copy, Check, ImagePlus, Loader2 } from 'lucide-react'; +import { KeyRound, Lock, Plus, RefreshCw, Send, UserPlus, Copy, Check, ImagePlus, Loader2, Share2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; @@ -89,6 +89,24 @@ export function CordnNativePanel({ crew, canManage, isMember }: CordnNativePanel if (!joined) setNotice(t('cordnUi.noInvite')); }, [native, t]); + // "Join our crew chat" invite. The chat needs the person to be a crew member + // first, so the link just drops them on this crew's private-chat tab with the + // "Ask to join" flow ready. Uses the OS share sheet where available. + const shareInvite = useCallback(async () => { + const url = `${window.location.origin}/#/crew/${crew.pubkey}/${crew.identifier}?tab=mls`; + try { + if (navigator.share) { + await navigator.share({ title: crew.name, text: t('cordnUi.shareInviteText', { crew: crew.name }), url }); + return; + } + await navigator.clipboard.writeText(url); + setNotice(t('cordnUi.inviteCopied')); + window.setTimeout(() => setNotice(null), 2500); + } catch { + // share cancelled or clipboard blocked — nothing to do + } + }, [crew, t]); + if (!user) { return } text={t('cordnUi.notLoggedIn')} />; } @@ -293,15 +311,25 @@ export function CordnNativePanel({ crew, canManage, isMember }: CordnNativePanel
{t('cordnUi.requests')} - +
+ + +
{native.requests.length === 0 ? (

diff --git a/src/i18n/cs/drift.ts b/src/i18n/cs/drift.ts index 2a96fa3..3ed450b 100644 --- a/src/i18n/cs/drift.ts +++ b/src/i18n/cs/drift.ts @@ -278,6 +278,9 @@ const drift = { "legendWeatherOnly": "Jen počasí" }, "cordnUi": { + "shareInvite": 'Pozvat', + "shareInviteText": 'Přidej se k {{crew}} — soukromý chat crew', + "inviteCopied": 'Odkaz s pozvánkou zkopírován', "title": "Soukromý chat", "subtitle": "End-to-end šifrované (MLS) mezi členy crew. Koordinátor přenáší pouze šifrovaný text a metadata — nikdy nevidí obsah zpráv.", "notLoggedIn": "Přihlas se, abys mohl použít soukromý chat crew.", diff --git a/src/i18n/de/drift.ts b/src/i18n/de/drift.ts index 0774e14..8b0f18c 100644 --- a/src/i18n/de/drift.ts +++ b/src/i18n/de/drift.ts @@ -280,6 +280,9 @@ const drift = { legendWeatherOnly: "Nur Wetter", }, cordnUi: { + shareInvite: 'Einladen', + shareInviteText: 'Komm zu {{crew}} — privater Crew-Chat', + inviteCopied: 'Einladungslink kopiert', title: "Privater Chat", subtitle: "Ende-zu-Ende-verschlüsselt (MLS) zwischen den Crew-Mitgliedern. Der Koordinator leitet nur Chiffretext und Metadaten weiter – er sieht nie den Inhalt der Nachrichten.", notLoggedIn: "Melde dich an, um den privaten Chat der Crew zu nutzen.", diff --git a/src/i18n/el/drift.ts b/src/i18n/el/drift.ts index 7412f83..8d17f97 100644 --- a/src/i18n/el/drift.ts +++ b/src/i18n/el/drift.ts @@ -282,6 +282,9 @@ const drift = { legendWeatherOnly: "Μόνο καιρός", }, cordnUi: { + shareInvite: 'Πρόσκληση', + shareInviteText: 'Μπες στο {{crew}} — ιδιωτική συνομιλία', + inviteCopied: 'Ο σύνδεσμος πρόσκλησης αντιγράφηκε', title: "Ιδιωτική συνομιλία", subtitle: "End-to-end κρυπτογραφημένη (MLS) μεταξύ των μελών του crew. Ο συντονιστής δρομολογεί μόνο κρυπτογραφημένα δεδομένα και metadata — δεν βλέπει ποτέ το περιεχόμενο των μηνυμάτων.", notLoggedIn: "Συνδέσου για να χρησιμοποιήσεις την ιδιωτική συνομιλία του crew.", diff --git a/src/i18n/fr/drift.ts b/src/i18n/fr/drift.ts index da7991a..b8d0346 100644 --- a/src/i18n/fr/drift.ts +++ b/src/i18n/fr/drift.ts @@ -280,6 +280,9 @@ const drift = { legendWeatherOnly: "Météo uniquement", }, cordnUi: { + shareInvite: 'Inviter', + shareInviteText: 'Rejoins {{crew}} — chat privé du crew', + inviteCopied: 'Lien d’invitation copié', title: "Chat privé", subtitle: "Chiffré de bout en bout (MLS) entre les membres de la Crew. Le coordinateur ne route que du texte chiffré et des métadonnées — il ne voit jamais le contenu des messages.", notLoggedIn: "Connectez-vous pour utiliser le chat privé de la Crew.", diff --git a/src/i18n/id/drift.ts b/src/i18n/id/drift.ts index 89c46d9..882a951 100644 --- a/src/i18n/id/drift.ts +++ b/src/i18n/id/drift.ts @@ -274,6 +274,9 @@ const driftStrings = { legendWeatherOnly: 'Cuaca saja', }, cordnUi: { + shareInvite: 'Undang', + shareInviteText: 'Gabung ke {{crew}} — obrolan pribadi kru', + inviteCopied: 'Tautan undangan disalin', title: 'Chat privat', subtitle: 'Terenkripsi ujung-ke-ujung (MLS) antar anggota crew. Koordinator hanya merutekan ciphertext dan metadata — tidak pernah melihat isi pesan.', notLoggedIn: 'Masuk untuk menggunakan chat privat crew.', diff --git a/src/i18n/it/drift.ts b/src/i18n/it/drift.ts index 325487a..ac01c28 100644 --- a/src/i18n/it/drift.ts +++ b/src/i18n/it/drift.ts @@ -280,6 +280,9 @@ const drift = { legendWeatherOnly: "Solo meteo", }, cordnUi: { + shareInvite: 'Invita', + shareInviteText: 'Unisciti a {{crew}} — chat privata del crew', + inviteCopied: 'Link d’invito copiato', title: "Chat privata", subtitle: "Crittografata end-to-end (MLS) tra i membri della crew. Il coordinatore instrada solo testo cifrato e metadati — non vede mai il contenuto dei messaggi.", notLoggedIn: "Accedi per usare la chat privata della crew.", diff --git a/src/i18n/ja/drift.ts b/src/i18n/ja/drift.ts index d6695dd..af93c8d 100644 --- a/src/i18n/ja/drift.ts +++ b/src/i18n/ja/drift.ts @@ -290,6 +290,9 @@ const drift: Dict = { legendWeatherOnly: '天気のみ', }, cordnUi: { + shareInvite: '招待', + shareInviteText: '{{crew}} に参加 — クルーのプライベートチャット', + inviteCopied: '招待リンクをコピーしました', title: 'プライベートチャット', subtitle: 'クルーメンバー間のエンドツーエンド暗号化(MLS)。コーディネーターは暗号文とメタデータのみをルーティングし、メッセージの内容を見ることはありません。', notLoggedIn: 'クルーのプライベートチャットを使うにはログインしてください。', diff --git a/src/i18n/ko/drift.ts b/src/i18n/ko/drift.ts index e163518..9cd5dd5 100644 --- a/src/i18n/ko/drift.ts +++ b/src/i18n/ko/drift.ts @@ -290,6 +290,9 @@ const drift: Dict = { legendWeatherOnly: '날씨만', }, cordnUi: { + shareInvite: '초대', + shareInviteText: '{{crew}} 참여 — 크루 비공개 채팅', + inviteCopied: '초대 링크를 복사했습니다', title: '비공개 채팅', subtitle: '크루 멤버 간 종단 간 암호화(MLS). 코디네이터는 암호문과 메타데이터만 라우팅하며 메시지 내용은 절대 보지 않습니다.', notLoggedIn: '크루의 비공개 채팅을 사용하려면 로그인하세요.', diff --git a/src/i18n/legacy/cordnUi.ts b/src/i18n/legacy/cordnUi.ts index 64bca59..6a2ee49 100644 --- a/src/i18n/legacy/cordnUi.ts +++ b/src/i18n/legacy/cordnUi.ts @@ -27,6 +27,9 @@ const cordnUi = { approve: 'Add', noRequests: 'No pending join requests.', inviteHint: 'Only you are in this chat so far — crew members can tap “Ask to join”, then you approve them here.', + shareInvite: 'Invite', + shareInviteText: 'Join {{crew}} — private crew chat', + inviteCopied: 'Invite link copied', memberCount: '{{count}} in the group', you: 'You', groupRef: 'Group link', @@ -66,6 +69,9 @@ const cordnUi = { approve: 'Añadir', noRequests: 'No hay solicitudes pendientes.', inviteHint: 'De momento solo estás tú en este chat: los miembros pueden pulsar «Pedir entrar» y tú los apruebas aquí.', + shareInvite: 'Invitar', + shareInviteText: 'Únete a {{crew}} — chat privado de la peña', + inviteCopied: 'Enlace de invitación copiado', memberCount: '{{count}} en el grupo', you: 'Tú', groupRef: 'Enlace del grupo', diff --git a/src/i18n/nl/drift.ts b/src/i18n/nl/drift.ts index d884552..16b7e2b 100644 --- a/src/i18n/nl/drift.ts +++ b/src/i18n/nl/drift.ts @@ -278,6 +278,9 @@ const drift = { "legendWeatherOnly": "Alleen weer" }, "cordnUi": { + "shareInvite": 'Uitnodigen', + "shareInviteText": 'Sluit je aan bij {{crew}} — privéchat van de crew', + "inviteCopied": 'Uitnodigingslink gekopieerd', "title": "Privéchat", "subtitle": "End-to-end versleuteld (MLS) tussen crewleden. De coördinator routeert alleen ciphertext en metadata — hij ziet nooit de inhoud van berichten.", "notLoggedIn": "Log in om de privéchat van de crew te gebruiken.", diff --git a/src/i18n/pl/drift.ts b/src/i18n/pl/drift.ts index 3028b2a..3af6297 100644 --- a/src/i18n/pl/drift.ts +++ b/src/i18n/pl/drift.ts @@ -278,6 +278,9 @@ const drift = { "legendWeatherOnly": "Tylko pogoda" }, "cordnUi": { + "shareInvite": 'Zaproś', + "shareInviteText": 'Dołącz do {{crew}} — prywatny czat ekipy', + "inviteCopied": 'Link zaproszenia skopiowany', "title": "Prywatny czat", "subtitle": "Szyfrowany end-to-end (MLS) między członkami crew. Koordynator przekazuje wyłącznie szyfrogram i metadane — nigdy nie widzi treści wiadomości.", "notLoggedIn": "Zaloguj się, aby korzystać z prywatnego czatu crew.", diff --git a/src/i18n/pt/drift.ts b/src/i18n/pt/drift.ts index 9877513..15ed3a7 100644 --- a/src/i18n/pt/drift.ts +++ b/src/i18n/pt/drift.ts @@ -280,6 +280,9 @@ const drift = { legendWeatherOnly: "Apenas clima", }, cordnUi: { + shareInvite: 'Convidar', + shareInviteText: 'Entre em {{crew}} — chat privado do crew', + inviteCopied: 'Link de convite copiado', title: "Chat privado", subtitle: "Criptografado de ponta a ponta (MLS) entre os membros da crew. O coordenador roteia apenas texto cifrado e metadados — nunca vê o conteúdo das mensagens.", notLoggedIn: "Faça login para usar o chat privado da crew.", diff --git a/src/i18n/ro/drift.ts b/src/i18n/ro/drift.ts index 28be604..763ae2e 100644 --- a/src/i18n/ro/drift.ts +++ b/src/i18n/ro/drift.ts @@ -278,6 +278,9 @@ const drift = { "legendWeatherOnly": "Doar vremea" }, "cordnUi": { + "shareInvite": 'Invită', + "shareInviteText": 'Alătură-te la {{crew}} — chat privat al echipei', + "inviteCopied": 'Link de invitație copiat', "title": "Chat privat", "subtitle": "Criptat end-to-end (MLS) între membrii crew-ului. Coordonatorul transportă doar text cifrat și metadate — nu vede niciodată conținutul mesajelor.", "notLoggedIn": "Autentifică-te pentru a folosi chatul privat al crew-ului.", diff --git a/src/i18n/ru/drift.ts b/src/i18n/ru/drift.ts index 2a06057..86867ea 100644 --- a/src/i18n/ru/drift.ts +++ b/src/i18n/ru/drift.ts @@ -282,6 +282,9 @@ const drift = { legendWeatherOnly: "Только погода", }, cordnUi: { + shareInvite: 'Пригласить', + shareInviteText: 'Присоединяйся к {{crew}} — приватный чат команды', + inviteCopied: 'Ссылка-приглашение скопирована', title: "Приватный чат", subtitle: "Сквозное шифрование (MLS) между участниками Crew. Координатор маршрутизирует только шифротекст и метаданные — он никогда не видит содержимое сообщений.", notLoggedIn: "Войдите, чтобы использовать приватный чат Crew.", diff --git a/src/i18n/sv/drift.ts b/src/i18n/sv/drift.ts index d5f29f2..553b516 100644 --- a/src/i18n/sv/drift.ts +++ b/src/i18n/sv/drift.ts @@ -282,6 +282,9 @@ const drift = { legendWeatherOnly: "Endast väder", }, cordnUi: { + shareInvite: 'Bjud in', + shareInviteText: 'Gå med i {{crew}} — privat crewchatt', + inviteCopied: 'Inbjudningslänk kopierad', title: "Privat chatt", subtitle: "End-to-end-krypterad (MLS) mellan crewmedlemmar. Koordinatorn dirigerar bara chiffertext och metadata — den ser aldrig meddelandeinnehåll.", notLoggedIn: "Logga in för att använda crewets privata chatt.", diff --git a/src/i18n/th/drift.ts b/src/i18n/th/drift.ts index 67b9f7d..3ad00b5 100644 --- a/src/i18n/th/drift.ts +++ b/src/i18n/th/drift.ts @@ -274,6 +274,9 @@ const driftStrings = { legendWeatherOnly: 'เฉพาะสภาพอากาศ', }, cordnUi: { + shareInvite: 'เชิญ', + shareInviteText: 'เข้าร่วม {{crew}} — แชทส่วนตัวของ crew', + inviteCopied: 'คัดลอกลิงก์เชิญแล้ว', title: 'แชทส่วนตัว', subtitle: 'เข้ารหัสต้นทาง-ปลายทาง (MLS) ระหว่างสมาชิก crew coordinator เพียงส่งต่อข้อความเข้ารหัสและ metadata — ไม่เคยเห็นเนื้อหาข้อความ', notLoggedIn: 'เข้าสู่ระบบเพื่อใช้แชทส่วนตัวของ crew', diff --git a/src/i18n/tr/drift.ts b/src/i18n/tr/drift.ts index 732d019..b78001a 100644 --- a/src/i18n/tr/drift.ts +++ b/src/i18n/tr/drift.ts @@ -274,6 +274,9 @@ const driftStrings = { legendWeatherOnly: 'Yalnızca hava', }, cordnUi: { + shareInvite: 'Davet et', + shareInviteText: '{{crew}} grubuna katıl — özel ekip sohbeti', + inviteCopied: 'Davet bağlantısı kopyalandı', title: 'Özel sohbet', subtitle: 'Crew üyeleri arasında uçtan uca şifreli (MLS). Koordinatör yalnızca şifreli metni ve üst veriyi yönlendirir — mesaj içeriğini asla görmez.', notLoggedIn: "Crew'in özel sohbetini kullanmak için giriş yap.", diff --git a/src/i18n/uk/drift.ts b/src/i18n/uk/drift.ts index 164cf22..ee57bb0 100644 --- a/src/i18n/uk/drift.ts +++ b/src/i18n/uk/drift.ts @@ -282,6 +282,9 @@ const drift = { legendWeatherOnly: "Лише погода", }, cordnUi: { + shareInvite: 'Запросити', + shareInviteText: 'Долучайся до {{crew}} — приватний чат команди', + inviteCopied: 'Посилання-запрошення скопійовано', title: "Приватний чат", subtitle: "Наскрізне шифрування (MLS) між учасниками команди. Координатор маршрутизує лише шифротекст і метадані — він ніколи не бачить вміст повідомлень.", notLoggedIn: "Увійдіть, щоб користуватися приватним чатом команди.", diff --git a/src/i18n/vi/drift.ts b/src/i18n/vi/drift.ts index fcf83e2..78a15f0 100644 --- a/src/i18n/vi/drift.ts +++ b/src/i18n/vi/drift.ts @@ -274,6 +274,9 @@ const driftStrings = { legendWeatherOnly: 'Chỉ thời tiết', }, cordnUi: { + shareInvite: 'Mời', + shareInviteText: 'Tham gia {{crew}} — trò chuyện riêng của crew', + inviteCopied: 'Đã sao chép liên kết mời', title: 'Chat riêng tư', subtitle: 'Mã hóa đầu-cuối (MLS) giữa các thành viên crew. Điều phối viên chỉ định tuyến bản mã và metadata — không bao giờ thấy nội dung tin nhắn.', notLoggedIn: 'Đăng nhập để dùng chat riêng tư của crew.', diff --git a/src/i18n/zh-CN/drift.ts b/src/i18n/zh-CN/drift.ts index a43e786..7c00a06 100644 --- a/src/i18n/zh-CN/drift.ts +++ b/src/i18n/zh-CN/drift.ts @@ -290,6 +290,9 @@ const drift: Dict = { legendWeatherOnly: '仅天气', }, cordnUi: { + shareInvite: '邀请', + shareInviteText: '加入 {{crew}} — 团队私密聊天', + inviteCopied: '邀请链接已复制', title: '私密聊天', subtitle: 'Crew 成员之间的端到端加密(MLS)。协调器只路由密文和元数据——它永远看不到消息内容。', notLoggedIn: '登录以使用 Crew 的私密聊天。', diff --git a/src/i18n/zh-TW/drift.ts b/src/i18n/zh-TW/drift.ts index ab43762..07497ca 100644 --- a/src/i18n/zh-TW/drift.ts +++ b/src/i18n/zh-TW/drift.ts @@ -290,6 +290,9 @@ const drift: Dict = { legendWeatherOnly: '僅天氣', }, cordnUi: { + shareInvite: '邀請', + shareInviteText: '加入 {{crew}} — 團隊私密聊天', + inviteCopied: '邀請連結已複製', title: '私密聊天', subtitle: 'Crew 成員之間的端到端加密(MLS)。協調器只路由密文和中繼資料——它永遠看不到訊息內容。', notLoggedIn: '登入以使用 Crew 的私密聊天。',