Merge pull request #41 from nbenaglia/feature/i18n-group-continue

i18n: Add group translations
This commit is contained in:
nico.benaz 2025-05-10 15:00:04 +02:00 committed by GitHub
commit a97d735b84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
64 changed files with 1806 additions and 742 deletions

View File

@ -1,69 +1,131 @@
{ {
"add": "hinzufügen", "action": {
"cancel": "abbrechen", "add": "hinzufügen",
"choose": "auswählen", "accept": "akzeptieren",
"close": "schließen", "backup_account": "Konto sichern",
"continue": "fortfahren", "backup_wallet": "Wallet sichern",
"cancel": "abbrechen",
"cancel_invitation": "Einladung abbrechen",
"change": "ändern",
"change_language": "Sprache ändern",
"choose": "wählen",
"close": "schließen",
"continue": "fortfahren",
"continue_logout": "mit dem Abmelden fortfahren",
"create_thread": "Thread erstellen",
"decline": "ablehnen",
"decrypt": "entschlüsseln",
"edit": "bearbeiten",
"export": "exportieren",
"import": "importieren",
"invite": "einladen",
"join": "beitreten",
"logout": "abmelden",
"new": {
"post": "neuer Beitrag",
"thread": "neuer Thread"
},
"notify": "benachrichtigen",
"post": "veröffentlichen",
"post_message": "Nachricht senden"
},
"admin": "Administrator",
"core": { "core": {
"block_height": "Blockhöhe", "block_height": "Blockhöhe",
"information": "Kerninformationen", "information": "Kerninformationen",
"peers": "verbundene Peers", "peers": "verbundene Peers",
"version": "Kernversion" "version": "Core-Version"
},
"ui": {
"version": "UI-Version"
},
"count": {
"none": "keine",
"one": "eins"
}, },
"description": "Beschreibung", "description": "Beschreibung",
"edit": "bearbeiten", "downloading_qdn": "Herunterladen von QDN",
"export": "exportieren", "fee": {
"import": "importieren", "payment": "Zahlungsgebühr",
"publish": "Veröffentlichungsgebühr"
},
"general_settings": "allgemeine Einstellungen",
"last_height": "letzte Höhe", "last_height": "letzte Höhe",
"loading": "Lade...", "list": {
"logout": "abmelden", "invite": "Einladungsliste",
"minting_status": "Präge-Status", "join_request": "Beitrittsanfragenliste",
"payment_notification": "Zahlungsbenachrichtigung", "member": "Mitgliederliste"
"price": "Preis", },
"q_mail": "Q-Mail", "loading": "Lädt...",
"result": { "loading_posts": "Beiträge werden geladen... bitte warten.",
"message_us": "Bitte kontaktiere uns über Telegram oder Discord, wenn du 4 QORT benötigst, um ohne Einschränkungen zu chatten",
"message": {
"error": { "error": {
"generic": "Ein Fehler ist aufgetreten", "generic": "Ein Fehler ist aufgetreten",
"incorrect_password": "Falsches Passwort", "incorrect_password": "falsches Passwort",
"missing_field": "fehlt: {{ field }}",
"save_qdn": "Speichern in QDN nicht möglich" "save_qdn": "Speichern in QDN nicht möglich"
}, },
"status": { "status": {
"minting": "(Prägung)", "minting": "(minting)",
"not_minting": "(keine Prägung)", "not_minting": "(kein minting)",
"synchronized": "synchronisiert", "synchronized": "synchronisiert",
"synchronizing": "synchronisiere" "synchronizing": "synchronisiere"
}, },
"success": { "success": {
"publish_qdn": "Erfolgreich in QDN veröffentlicht" "order_submitted": "Deine Kauforder wurde übermittelt",
"publish_qdn": "Erfolgreich in QDN veröffentlicht",
"request_read": "Ich habe diese Anfrage gelesen",
"transfer": "Die Übertragung war erfolgreich!"
} }
}, },
"minting_status": "Minting-Status",
"page": {
"last": "letzte",
"first": "erste",
"next": "nächste",
"previous": "vorherige"
},
"payment_notification": "Zahlungsbenachrichtigung",
"price": "Preis",
"q_mail": "Q-Mail",
"question": {
"new_user": "Bist du ein neuer Benutzer?"
},
"save_options": { "save_options": {
"no_pinned_changes": "Derzeit keine Änderungen an Ihren angehefteten Apps", "no_pinned_changes": "Du hast derzeit keine Änderungen an deinen angehefteten Apps",
"overwrite_changes": "Die App konnte Ihre vorhandenen in QDN gespeicherten angehefteten Apps nicht herunterladen. Möchten Sie diese Änderungen überschreiben?", "overwrite_changes": "Die App konnte deine gespeicherten angehefteten QDN-Apps nicht laden. Möchtest du diese Änderungen überschreiben?",
"overwrite_qdn": "In QDN überschreiben", "overwrite_qdn": "in QDN überschreiben",
"publish_qdn": "Möchten Sie Ihre Einstellungen in QDN (verschlüsselt) veröffentlichen?", "publish_qdn": "Möchtest du deine Einstellungen verschlüsselt in QDN veröffentlichen?",
"qdn": "QDN-Speicherung verwenden", "qdn": "QDN-Speicherung verwenden",
"register_name": "Sie benötigen einen registrierten Qortal-Namen, um Ihre angehefteten Apps in QDN zu speichern.", "register_name": "Du brauchst einen registrierten Qortal-Namen, um deine angehefteten Apps in QDN zu speichern.",
"reset_pinned": "Gefällt Ihnen Ihre aktuellen lokalen Änderungen nicht? Möchten Sie zu den Standard-Anheftungen zurückkehren?", "reset_pinned": "Gefällt dir deine lokale Änderung nicht? Möchtest du auf die Standard-Apps zurücksetzen?",
"reset_qdn": "Gefällt Ihnen Ihre aktuellen lokalen Änderungen nicht? Möchten Sie zu Ihren in QDN gespeicherten Anheftungen zurückkehren?", "reset_qdn": "Gefällt dir deine lokale Änderung nicht? Möchtest du auf deine in QDN gespeicherten Apps zurücksetzen?",
"revert_default": "Auf Standard zurücksetzen", "revert_default": "auf Standard zurücksetzen",
"revert_qdn": "Auf QDN zurücksetzen", "revert_qdn": "auf QDN zurücksetzen",
"save_qdn": "In QDN speichern", "save_qdn": "in QDN speichern",
"save": "speichern", "save": "speichern",
"settings": "Sie verwenden die Export/Import-Methode zum Speichern von Einstellungen.", "settings": "Du nutzt die Export-/Import-Methode zum Speichern der Einstellungen.",
"unsaved_changes": "Sie haben nicht gespeicherte Änderungen an Ihren angehefteten Apps. Speichern Sie sie in QDN." "unsaved_changes": "Du hast nicht gespeicherte Änderungen an deinen angehefteten Apps. Speichere sie in QDN."
}, },
"settings": "Einstellungen", "settings": "Einstellungen",
"supply": "Angebot", "supply": "Versorgung",
"theme": { "theme": {
"dark": "Dunkelmodus", "dark": "dunkler Modus",
"light": "Hellmodus" "light": "heller Modus"
},
"time": {
"day_one": "{{count}} Tag",
"day_other": "{{count}} Tage",
"hour_one": "{{count}} Stunde",
"hour_other": "{{count}} Stunden",
"minute_one": "{{count}} Minute",
"minute_other": "{{count}} Minuten"
}, },
"title": "Titel", "title": "Titel",
"tutorial": "Tutorial", "tutorial": "Tutorial",
"user_lookup": "Benutzersuche", "user_lookup": "Benutzersuche",
"wallet": { "wallet": {
"backup_wallet": "Wallet sichern",
"wallet": "Wallet", "wallet": "Wallet",
"wallet_other": "Wallets" "wallet_other": "Wallets"
}, },

View File

@ -0,0 +1,105 @@
{
"action": {
"ban": "Mitglied aus der Gruppe verbannen",
"cancel_ban": "Sperre aufheben",
"copy_private_key": "Privaten Schlüssel kopieren",
"create_group": "Gruppe erstellen",
"disable_push_notifications": "Alle Push-Benachrichtigungen deaktivieren",
"enable_dev_mode": "Entwicklermodus aktivieren",
"export_password": "Passwort exportieren",
"export_private_key": "Privaten Schlüssel exportieren",
"find_group": "Gruppe finden",
"join_group": "Gruppe beitreten",
"kick_member": "Mitglied aus der Gruppe entfernen",
"invite_member": "Mitglied einladen",
"leave_group": "Gruppe verlassen",
"load_members": "Mitglieder mit Namen laden",
"make_admin": "Zum Administrator ernennen",
"manage_members": "Mitglieder verwalten",
"refetch_page": "Seite neu laden",
"remove_admin": "Als Administrator entfernen",
"return_to_thread": "Zu den Threads zurückkehren"
},
"advanced_options": "Erweiterte Optionen",
"approval_threshold": "Genehmigungsschwelle der Gruppe (Anzahl / Prozentsatz der Administratoren, die eine Transaktion genehmigen müssen)",
"ban_list": "Sperrliste",
"block_delay": {
"minimum": "Minimale Blockverzögerung für Gruppen-Transaktionsgenehmigungen",
"maximum": "Maximale Blockverzögerung für Gruppen-Transaktionsgenehmigungen"
},
"group": {
"closed": "geschlossen (privat) Benutzer benötigen eine Erlaubnis zum Beitreten",
"description": "Gruppenbeschreibung",
"id": "Gruppen-ID",
"invites": "Gruppeneinladungen",
"management": "Gruppenverwaltung",
"member_number": "Anzahl der Mitglieder",
"name": "Gruppenname",
"open": "offen (öffentlich)",
"type": "Gruppentyp"
},
"invitation_expiry": "Ablaufzeit der Einladung",
"invitees_list": "Liste der Eingeladenen",
"join_link": "Link zum Beitritt zur Gruppe",
"join_requests": "Beitrittsanfragen",
"last_message": "Letzte Nachricht",
"latest_mails": "Neueste Q-Mails",
"message": {
"generic": {
"already_in_group": "Sie sind bereits in dieser Gruppe!",
"closed_group": "Dies ist eine geschlossene/private Gruppe, daher müssen Sie warten, bis ein Administrator Ihre Anfrage akzeptiert",
"descrypt_wallet": "Wallet wird entschlüsselt...",
"encryption_key": "Der erste gemeinsame Verschlüsselungsschlüssel der Gruppe wird erstellt. Bitte warten Sie ein paar Minuten, bis er vom Netzwerk abgerufen wird. Überprüfung alle 2 Minuten...",
"group_invited_you": "{{group}} hat Sie eingeladen",
"loading_members": "Mitgliederliste mit Namen wird geladen... bitte warten.",
"no_display": "Nichts anzuzeigen",
"no_selection": "Keine Gruppe ausgewählt",
"not_part_group": "Sie sind nicht Teil der verschlüsselten Mitgliedergruppe. Warten Sie, bis ein Administrator die Schlüssel neu verschlüsselt.",
"only_encrypted": "Nur unverschlüsselte Nachrichten werden angezeigt.",
"private_key_copied": "Privater Schlüssel kopiert",
"provide_message": "Bitte geben Sie eine erste Nachricht für den Thread ein",
"secure_place": "Bewahren Sie Ihren privaten Schlüssel an einem sicheren Ort auf. Nicht teilen!",
"setting_group": "Gruppe wird eingerichtet... bitte warten."
},
"error": {
"access_name": "Kann keine Nachricht senden, ohne Zugriff auf Ihren Namen",
"descrypt_wallet": "Fehler beim Entschlüsseln der Wallet {{ :errorMessage }}",
"description_required": "Bitte geben Sie eine Beschreibung an",
"group_info": "Kann nicht auf Gruppeninformationen zugreifen",
"group_secret_key": "Kann den geheimen Gruppenschlüssel nicht abrufen",
"name_required": "Bitte geben Sie einen Namen an",
"notify_admins": "Versuchen Sie, einen Administrator aus der untenstehenden Liste zu benachrichtigen:",
"thread_id": "Thread-ID konnte nicht gefunden werden"
},
"success": {
"group_ban": "Mitglied erfolgreich aus der Gruppe verbannt. Es kann ein paar Minuten dauern, bis die Änderungen wirksam werden",
"group_creation": "Gruppe erfolgreich erstellt. Es kann ein paar Minuten dauern, bis die Änderungen wirksam werden",
"group_creation_name": "Gruppe {{group_name}} erstellt: Warte auf Bestätigung",
"group_creation_label": "Gruppe {{name}} erstellt: Erfolg!",
"group_invite": "{{value}} erfolgreich eingeladen. Es kann ein paar Minuten dauern, bis die Änderungen wirksam werden",
"group_join": "Beitritt zur Gruppe erfolgreich angefordert. Es kann ein paar Minuten dauern, bis die Änderungen wirksam werden",
"group_join_name": "Gruppe {{group_name}} beigetreten: Warte auf Bestätigung",
"group_join_label": "Gruppe {{name}} beigetreten: Erfolg!",
"group_join_request": "Beitritt zur Gruppe {{group_name}} angefordert: Warte auf Bestätigung",
"group_join_outcome": "Beitritt zur Gruppe {{group_name}} angefordert: Erfolg!",
"group_kick": "Mitglied erfolgreich aus der Gruppe entfernt. Es kann ein paar Minuten dauern, bis die Änderungen wirksam werden",
"group_leave": "Verlassen der Gruppe erfolgreich angefordert. Es kann ein paar Minuten dauern, bis die Änderungen wirksam werden",
"group_leave_name": "Gruppe {{group_name}} verlassen: Warte auf Bestätigung",
"group_leave_label": "Gruppe {{name}} verlassen: Erfolg!",
"group_member_admin": "Mitglied erfolgreich zum Administrator gemacht. Es kann ein paar Minuten dauern, bis die Änderungen wirksam werden",
"group_remove_member": "Mitglied erfolgreich als Administrator entfernt. Es kann ein paar Minuten dauern, bis die Änderungen wirksam werden",
"invitation_cancellation": "Einladung erfolgreich storniert. Es kann ein paar Minuten dauern, bis die Änderungen wirksam werden",
"invitation_request": "Beitrittsanfrage akzeptiert: Warte auf Bestätigung",
"loading_threads": "Threads werden geladen... bitte warten.",
"post_creation": "Beitrag erfolgreich erstellt. Es kann einige Zeit dauern, bis die Veröffentlichung verbreitet wird",
"thread_creation": "Thread erfolgreich erstellt. Es kann einige Zeit dauern, bis die Veröffentlichung verbreitet wird",
"unbanned_user": "Benutzer erfolgreich entsperrt. Es kann ein paar Minuten dauern, bis die Änderungen wirksam werden",
"user_joined": "Benutzer erfolgreich beigetreten!"
}
},
"question": {
"perform_transaction": "Möchten Sie eine {{action}}-Transaktion durchführen?",
"provide_thread": "Bitte geben Sie einen Thread-Titel an"
},
"thread_posts": "Neue Thread-Beiträge"
}

View File

@ -5,21 +5,31 @@
"backup_account": "backup account", "backup_account": "backup account",
"backup_wallet": "backup wallet", "backup_wallet": "backup wallet",
"cancel": "cancel", "cancel": "cancel",
"cancel_invitation": "cancel invitation",
"change": "change", "change": "change",
"change_language": "change language", "change_language": "change language",
"choose": "choose", "choose": "choose",
"close": "close", "close": "close",
"continue": "continue", "continue": "continue",
"continue_logout": "continue to logout", "continue_logout": "continue to logout",
"create_thread": "create thread",
"decline": "decline", "decline": "decline",
"decrypt": "decrypt",
"edit": "edit", "edit": "edit",
"export": "export", "export": "export",
"import": "import", "import": "import",
"invite": "invite", "invite": "invite",
"join": "join", "join": "join",
"logout": "logout", "logout": "logout",
"notify": "notify" "new": {
"post": "new post",
"thread": "new thread"
},
"notify": "notify",
"post": "post",
"post_message": "post message"
}, },
"admin": "admin",
"core": { "core": {
"block_height": "block height", "block_height": "block height",
"information": "core information", "information": "core information",
@ -34,30 +44,26 @@
"one": "one" "one": "one"
}, },
"description": "description", "description": "description",
"downloading_qdn": "downloading from QDN",
"fee": { "fee": {
"payment": "payment fee", "payment": "payment fee",
"publish": "publish fee" "publish": "publish fee"
}, },
"page": { "general_settings": "general settings",
"last": "last",
"first": "first",
"next": "next",
"previous": "previous"
},
"downloading_qdn": "downloading from QDN",
"last_height": "last height", "last_height": "last height",
"list": {
"invite": "invite list",
"join_request": "join request list",
"member": "member list"
},
"loading": "loading...", "loading": "loading...",
"loading_posts": "loading posts... please wait.", "loading_posts": "loading posts... please wait.",
"message_us": "please message us on Telegram or Discord if you need 4 QORT to start chatting without any limitations", "message_us": "please message us on Telegram or Discord if you need 4 QORT to start chatting without any limitations",
"minting_status": "minting status",
"new_user": "are you a new user?",
"payment_notification": "payment notification",
"price": "price",
"q_mail": "q-mail",
"message": { "message": {
"error": { "error": {
"generic": "an error occurred", "generic": "an error occurred",
"incorrect_password": "incorrect password", "incorrect_password": "incorrect password",
"missing_field": "missing: {{ field }}",
"save_qdn": "unable to save to QDN" "save_qdn": "unable to save to QDN"
}, },
"status": { "status": {
@ -73,6 +79,19 @@
"transfer": "the transfer was succesful!" "transfer": "the transfer was succesful!"
} }
}, },
"minting_status": "minting status",
"page": {
"last": "last",
"first": "first",
"next": "next",
"previous": "previous"
},
"payment_notification": "payment notification",
"price": "price",
"q_mail": "q-mail",
"question": {
"new_user": "are you a new user?"
},
"save_options": { "save_options": {
"no_pinned_changes": "you currently do not have any changes to your pinned apps", "no_pinned_changes": "you currently do not have any changes to your pinned apps",
"overwrite_changes": "the app was unable to download your existing QDN-saved pinned apps. Would you like to overwrite those changes?", "overwrite_changes": "the app was unable to download your existing QDN-saved pinned apps. Would you like to overwrite those changes?",

View File

@ -1,11 +1,23 @@
{ {
"action": { "action": {
"ban": "ban member from group",
"cancel_ban": "cancel ban", "cancel_ban": "cancel ban",
"copy_private_key": "copy private key",
"create_group": "create group", "create_group": "create group",
"disable_push_notifications": "disable all push notifications",
"enable_dev_mode": "enable dev mode",
"export_password": "export password",
"export_private_key": "export private key",
"find_group": "find group", "find_group": "find group",
"join_group": "join group", "join_group": "join group",
"kick_member": "kick member from group",
"invite_member": "invite member", "invite_member": "invite member",
"leave_group": "leave group",
"load_members": "load members with names",
"make_admin": "make an admin",
"manage_members": "manage members",
"refetch_page": "refetch page", "refetch_page": "refetch page",
"remove_admin": "remove as admin",
"return_to_thread": "return to threads" "return_to_thread": "return to threads"
}, },
"advanced_options": "advanced options", "advanced_options": "advanced options",
@ -18,48 +30,76 @@
"group": { "group": {
"closed": "closed (private) - users need permission to join", "closed": "closed (private) - users need permission to join",
"description": "description of group", "description": "description of group",
"id": "group id",
"invites": "group invites", "invites": "group invites",
"management": "group management", "management": "group management",
"name": "name of group", "member_number": "number of members",
"name": "group name",
"open": "open (public)", "open": "open (public)",
"type": "group type" "type": "group type"
}, },
"invitation_expiry": "invitation Expiry Time", "invitation_expiry": "invitation Expiry Time",
"invitees_list": "invitees list",
"join_link": "join group link",
"join_requests": "join requests", "join_requests": "join requests",
"question": { "last_message": "last message",
"cancel_ban": "would you like to perform a CANCEL_GROUP_BAN transaction?", "latest_mails": "latest Q-Mails",
"create_group": "would you like to perform an CREATE_GROUP transaction?",
"group_invite": "would you like to perform a GROUP_INVITE transaction?",
"join_group": "would you like to perform an JOIN_GROUP transaction?",
"provide_thread": "please provide a thread title"
},
"message": { "message": {
"generic": { "generic": {
"already_in_group": "you are already in this group!",
"closed_group": "this is a closed/private group, so you will need to wait until an admin accepts your request",
"descrypt_wallet": "decrypting wallet...",
"encryption_key": "the group's first common encryption key is in the process of creation. Please wait a few minutes for it to be retrieved by the network. Checking every 2 minutes...", "encryption_key": "the group's first common encryption key is in the process of creation. Please wait a few minutes for it to be retrieved by the network. Checking every 2 minutes...",
"group_invited_you": "{{group}} has invited you", "group_invited_you": "{{group}} has invited you",
"loading_members": "loading member list with names... please wait.",
"no_display": "nothing to display", "no_display": "nothing to display",
"no_selection": "no group selected", "no_selection": "no group selected",
"not_part_group": "you are not part of the encrypted group of members. Wait until an admin re-encrypts the keys.", "not_part_group": "you are not part of the encrypted group of members. Wait until an admin re-encrypts the keys.",
"only_encrypted": "only unencrypted messages will be displayed.", "only_encrypted": "only unencrypted messages will be displayed.",
"private_key_copied": "private key copied",
"provide_message": "please provide a first message to the thread",
"secure_place": "keep your private key in a secure place. Do not share!",
"setting_group": "setting up group... please wait." "setting_group": "setting up group... please wait."
}, },
"error": { "error": {
"access_name": "cannot send a message without a access to your name", "access_name": "cannot send a message without a access to your name",
"descrypt_wallet": "error decrypting wallet {{ :errorMessage }}",
"description_required": "please provide a description", "description_required": "please provide a description",
"group_info": "cannot access group information", "group_info": "cannot access group information",
"group_secret_key": "cannot get group secret key",
"name_required": "please provide a name", "name_required": "please provide a name",
"notify_admins": "try notifying an admin from the list of admins below:" "notify_admins": "try notifying an admin from the list of admins below:",
"thread_id": "unable to locate thread Id"
}, },
"success": { "success": {
"group_ban": "successfully banned member from group. It may take a couple of minutes for the changes to propagate",
"group_creation": "successfully created group. It may take a couple of minutes for the changes to propagate", "group_creation": "successfully created group. It may take a couple of minutes for the changes to propagate",
"group_creation_name": "created group {{group_name}}: awaiting confirmation", "group_creation_name": "created group {{group_name}}: awaiting confirmation",
"group_creation_label": "created group {{name}}: success!", "group_creation_label": "created group {{name}}: success!",
"group_invite": "successfully invited {{value}}. It may take a couple of minutes for the changes to propagate", "group_invite": "successfully invited {{value}}. It may take a couple of minutes for the changes to propagate",
"join_creation": "successfully requested to join group. It may take a couple of minutes for the changes to propagate", "group_join": "successfully requested to join group. It may take a couple of minutes for the changes to propagate",
"group_join_name": "joined group {{group_name}}: awaiting confirmation", "group_join_name": "joined group {{group_name}}: awaiting confirmation",
"group_join_label": "joined group {{name}}: success!", "group_join_label": "joined group {{name}}: success!",
"group_join_request": "requested to join Group {{group_name}}: awaiting confirmation",
"group_join_outcome": "requested to join Group {{group_name}}: success!",
"group_kick": "successfully kicked member from group. It may take a couple of minutes for the changes to propagate",
"group_leave": "successfully requested to leave group. It may take a couple of minutes for the changes to propagate",
"group_leave_name": "left group {{group_name}}: awaiting confirmation",
"group_leave_label": "left group {{name}}: success!",
"group_member_admin": "successfully made member an admin. It may take a couple of minutes for the changes to propagate",
"group_remove_member": "successfully removed member as an admin. It may take a couple of minutes for the changes to propagate",
"invitation_cancellation": "successfully canceled invitation. It may take a couple of minutes for the changes to propagate",
"invitation_request": "accepted join request: awaiting confirmation",
"loading_threads": "loading threads... please wait.", "loading_threads": "loading threads... please wait.",
"unbanned_user": "successfully unbanned user. It may take a couple of minutes for the changes to propagate" "post_creation": "successfully created post. It may take some time for the publish to propagate",
"thread_creation": "successfully created thread. It may take some time for the publish to propagate",
"unbanned_user": "successfully unbanned user. It may take a couple of minutes for the changes to propagate",
"user_joined": "user successfully joined!"
} }
} },
"question": {
"perform_transaction": "would you like to perform a {{action}} transaction?",
"provide_thread": "please provide a thread title"
},
"thread_posts": "new thread posts"
} }

View File

@ -1,71 +1,133 @@
{ {
"add": "agregar", "action": {
"cancel": "cancelar", "add": "añadir",
"choose": "elegir", "accept": "aceptar",
"close": "cerrar", "backup_account": "respaldar cuenta",
"continue": "continuar", "backup_wallet": "respaldar monedero",
"cancel": "cancelar",
"cancel_invitation": "cancelar invitación",
"change": "cambiar",
"change_language": "cambiar idioma",
"choose": "elegir",
"close": "cerrar",
"continue": "continuar",
"continue_logout": "continuar para cerrar sesión",
"create_thread": "crear hilo",
"decline": "rechazar",
"decrypt": "descifrar",
"edit": "editar",
"export": "exportar",
"import": "importar",
"invite": "invitar",
"join": "unirse",
"logout": "cerrar sesión",
"new": {
"post": "nueva publicación",
"thread": "nuevo hilo"
},
"notify": "notificar",
"post": "publicar",
"post_message": "enviar mensaje"
},
"admin": "administrador",
"core": { "core": {
"block_height": "altura de bloque", "block_height": "altura del bloque",
"information": "información del núcleo", "information": "información del núcleo",
"peers": "pares conectados", "peers": "pares conectados",
"version": "versión del núcleo" "version": "versión del núcleo"
}, },
"ui": {
"version": "versión de la interfaz"
},
"count": {
"none": "ninguno",
"one": "uno"
},
"description": "descripción", "description": "descripción",
"edit": "editar", "downloading_qdn": "descargando desde QDN",
"export": "exportar", "fee": {
"import": "importar", "payment": "tarifa de pago",
"publish": "tarifa de publicación"
},
"general_settings": "configuración general",
"last_height": "última altura", "last_height": "última altura",
"list": {
"invite": "lista de invitaciones",
"join_request": "lista de solicitudes de unión",
"member": "lista de miembros"
},
"loading": "cargando...", "loading": "cargando...",
"logout": "cerrar sesión", "loading_posts": "cargando publicaciones... por favor espera.",
"minting_status": "estado de acuñación", "message_us": "por favor contáctanos en Telegram o Discord si necesitas 4 QORT para comenzar a chatear sin limitaciones",
"payment_notification": "notificación de pago", "message": {
"price": "precio",
"q_mail": "q-mail",
"result": {
"error": { "error": {
"generic": "ocurrió un error", "generic": "ocurrió un error",
"incorrect_password": "contraseña incorrecta", "incorrect_password": "contraseña incorrecta",
"missing_field": "falta: {{ field }}",
"save_qdn": "no se pudo guardar en QDN" "save_qdn": "no se pudo guardar en QDN"
}, },
"status": { "status": {
"minting": "(acuñando)", "minting": "(generando)",
"not_minting": "(no acuñando)", "not_minting": "(no generando)",
"synchronized": "sincronizado", "synchronized": "sincronizado",
"synchronizing": "sincronizando" "synchronizing": "sincronizando"
}, },
"success": { "success": {
"publish_qdn": "publicado exitosamente en QDN" "order_submitted": "tu orden de compra fue enviada",
"publish_qdn": "publicado correctamente en QDN",
"request_read": "he leído esta solicitud",
"transfer": "¡la transferencia fue exitosa!"
} }
}, },
"minting_status": "estado de generación",
"page": {
"last": "última",
"first": "primera",
"next": "siguiente",
"previous": "anterior"
},
"payment_notification": "notificación de pago",
"price": "precio",
"q_mail": "q-mail",
"question": {
"new_user": "¿eres un usuario nuevo?"
},
"save_options": { "save_options": {
"no_pinned_changes": "actualmente no tienes cambios en tus aplicaciones fijadas", "no_pinned_changes": "actualmente no tienes cambios en tus aplicaciones fijadas",
"overwrite_changes": "la aplicación no pudo descargar tus aplicaciones fijadas existentes guardadas en QDN. ¿Deseas sobrescribir esos cambios?", "overwrite_changes": "la aplicación no pudo descargar tus aplicaciones fijadas guardadas en QDN. ¿Deseas sobrescribir esos cambios?",
"overwrite_qdn": "sobrescribir en QDN", "overwrite_qdn": "sobrescribir en QDN",
"publish_qdn": "¿Deseas publicar tus configuraciones en QDN (cifrado)?", "publish_qdn": "¿quieres publicar tu configuración en QDN (cifrada)?",
"qdn": "usar guardado en QDN", "qdn": "usar guardado QDN",
"register_name": "necesitas un nombre Qortal registrado para guardar tus aplicaciones fijadas en QDN.", "register_name": "necesitas un nombre de Qortal registrado para guardar tus aplicaciones fijadas en QDN.",
"reset_pinned": "¿No te gustan tus cambios locales actuales? ¿Deseas restablecer las aplicaciones fijadas predeterminadas?", "reset_pinned": "¿no te gustan tus cambios locales actuales? ¿Quieres restablecer las aplicaciones fijadas por defecto?",
"reset_qdn": "¿No te gustan tus cambios locales actuales? ¿Deseas restablecer tus aplicaciones fijadas guardadas en QDN?", "reset_qdn": "¿no te gustan tus cambios locales actuales? ¿Quieres restablecer tus aplicaciones fijadas guardadas en QDN?",
"revert_default": "restablecer a predeterminado", "revert_default": "restablecer a valores predeterminados",
"revert_qdn": "restablecer a QDN", "revert_qdn": "restablecer desde QDN",
"save_qdn": "guardar en QDN", "save_qdn": "guardar en QDN",
"save": "guardar", "save": "guardar",
"settings": "estás utilizando el método de exportación/importación para guardar configuraciones.", "settings": "estás usando el método de exportación/importación para guardar la configuración.",
"unsaved_changes": "tienes cambios no guardados en tus aplicaciones fijadas. Guárdalos en QDN." "unsaved_changes": "tienes cambios no guardados en tus aplicaciones fijadas. Guárdalos en QDN."
}, },
"settings": "configuraciones", "settings": "configuración",
"supply": "suministro", "supply": "oferta",
"theme": { "theme": {
"dark": "modo oscuro", "dark": "modo oscuro",
"light": "modo claro" "light": "modo claro"
}, },
"time": {
"day_one": "{{count}} día",
"day_other": "{{count}} días",
"hour_one": "{{count}} hora",
"hour_other": "{{count}} horas",
"minute_one": "{{count}} minuto",
"minute_other": "{{count}} minutos"
},
"title": "título", "title": "título",
"tutorial": "tutorial", "tutorial": "tutorial",
"user_lookup": "búsqueda de usuario", "user_lookup": "búsqueda de usuario",
"wallet": { "wallet": {
"backup_wallet": "respaldar billetera", "wallet": "monedero",
"wallet": "billetera", "wallet_other": "monederos"
"wallet_other": "billeteras"
}, },
"welcome": "bienvenido" "welcome": "bienvenido"
} }

View File

@ -0,0 +1,105 @@
{
"action": {
"ban": "expulsar miembro del grupo",
"cancel_ban": "cancelar expulsión",
"copy_private_key": "copiar clave privada",
"create_group": "crear grupo",
"disable_push_notifications": "desactivar todas las notificaciones push",
"enable_dev_mode": "activar modo desarrollador",
"export_password": "exportar contraseña",
"export_private_key": "exportar clave privada",
"find_group": "encontrar grupo",
"join_group": "unirse al grupo",
"kick_member": "expulsar miembro del grupo",
"invite_member": "invitar miembro",
"leave_group": "salir del grupo",
"load_members": "cargar miembros con nombres",
"make_admin": "hacer administrador",
"manage_members": "administrar miembros",
"refetch_page": "recargar página",
"remove_admin": "quitar como administrador",
"return_to_thread": "volver a los hilos"
},
"advanced_options": "opciones avanzadas",
"approval_threshold": "umbral de aprobación del grupo (número / porcentaje de administradores que deben aprobar una transacción)",
"ban_list": "lista de expulsados",
"block_delay": {
"minimum": "retardo mínimo de bloque para aprobaciones de transacciones de grupo",
"maximum": "retardo máximo de bloque para aprobaciones de transacciones de grupo"
},
"group": {
"closed": "cerrado (privado) - los usuarios necesitan permiso para unirse",
"description": "descripción del grupo",
"id": "ID del grupo",
"invites": "invitaciones del grupo",
"management": "gestión del grupo",
"member_number": "número de miembros",
"name": "nombre del grupo",
"open": "abierto (público)",
"type": "tipo de grupo"
},
"invitation_expiry": "tiempo de expiración de la invitación",
"invitees_list": "lista de invitados",
"join_link": "enlace para unirse al grupo",
"join_requests": "solicitudes de ingreso",
"last_message": "último mensaje",
"latest_mails": "últimos Q-Mails",
"message": {
"generic": {
"already_in_group": "¡ya estás en este grupo!",
"closed_group": "este es un grupo cerrado/privado, debes esperar a que un administrador acepte tu solicitud",
"descrypt_wallet": "descifrando billetera...",
"encryption_key": "la primera clave de cifrado común del grupo se está creando. Por favor, espera unos minutos a que la red la recupere. Verificando cada 2 minutos...",
"group_invited_you": "{{group}} te ha invitado",
"loading_members": "cargando lista de miembros con nombres... por favor espera.",
"no_display": "nada que mostrar",
"no_selection": "ningún grupo seleccionado",
"not_part_group": "no eres parte del grupo cifrado de miembros. Espera a que un administrador vuelva a cifrar las claves.",
"only_encrypted": "solo se mostrarán mensajes no cifrados.",
"private_key_copied": "clave privada copiada",
"provide_message": "por favor proporciona un primer mensaje para el hilo",
"secure_place": "guarda tu clave privada en un lugar seguro. ¡No la compartas!",
"setting_group": "configurando grupo... por favor espera."
},
"error": {
"access_name": "no se puede enviar un mensaje sin acceso a tu nombre",
"descrypt_wallet": "error al descifrar la billetera {{ :errorMessage }}",
"description_required": "por favor proporciona una descripción",
"group_info": "no se puede acceder a la información del grupo",
"group_secret_key": "no se puede obtener la clave secreta del grupo",
"name_required": "por favor proporciona un nombre",
"notify_admins": "intenta notificar a un administrador de la lista a continuación:",
"thread_id": "no se puede encontrar el ID del hilo"
},
"success": {
"group_ban": "miembro expulsado del grupo con éxito. Puede tardar unos minutos en propagarse",
"group_creation": "grupo creado con éxito. Puede tardar unos minutos en propagarse",
"group_creation_name": "grupo {{group_name}} creado: esperando confirmación",
"group_creation_label": "grupo {{name}} creado: ¡éxito!",
"group_invite": "{{value}} invitado con éxito. Puede tardar unos minutos en propagarse",
"group_join": "solicitud de ingreso enviada con éxito. Puede tardar unos minutos en propagarse",
"group_join_name": "unido al grupo {{group_name}}: esperando confirmación",
"group_join_label": "unido al grupo {{name}}: ¡éxito!",
"group_join_request": "solicitud de ingreso al grupo {{group_name}}: esperando confirmación",
"group_join_outcome": "solicitud de ingreso al grupo {{group_name}}: ¡éxito!",
"group_kick": "miembro expulsado del grupo con éxito. Puede tardar unos minutos en propagarse",
"group_leave": "solicitud de salida del grupo enviada con éxito. Puede tardar unos minutos en propagarse",
"group_leave_name": "salido del grupo {{group_name}}: esperando confirmación",
"group_leave_label": "salido del grupo {{name}}: ¡éxito!",
"group_member_admin": "miembro convertido en administrador con éxito. Puede tardar unos minutos en propagarse",
"group_remove_member": "miembro eliminado como administrador con éxito. Puede tardar unos minutos en propagarse",
"invitation_cancellation": "invitación cancelada con éxito. Puede tardar unos minutos en propagarse",
"invitation_request": "solicitud de ingreso aceptada: esperando confirmación",
"loading_threads": "cargando hilos... por favor espera.",
"post_creation": "publicación creada con éxito. Puede tardar en propagarse",
"thread_creation": "hilo creado con éxito. Puede tardar en propagarse",
"unbanned_user": "usuario desbloqueado con éxito. Puede tardar unos minutos en propagarse",
"user_joined": "¡usuario se ha unido con éxito!"
}
},
"question": {
"perform_transaction": "¿quieres realizar una transacción de {{action}}?",
"provide_thread": "por favor proporciona un título para el hilo"
},
"thread_posts": "nuevas publicaciones en el hilo"
}

View File

@ -1,57 +1,112 @@
{ {
"add": "ajouter", "action": {
"cancel": "annuler", "add": "ajouter",
"choose": "choisir", "accept": "accepter",
"close": "fermer", "backup_account": "sauvegarder le compte",
"continue": "continuer", "backup_wallet": "sauvegarder le portefeuille",
"cancel": "annuler",
"cancel_invitation": "annuler l'invitation",
"change": "changer",
"change_language": "changer de langue",
"choose": "choisir",
"close": "fermer",
"continue": "continuer",
"continue_logout": "continuer la déconnexion",
"create_thread": "créer un fil",
"decline": "refuser",
"decrypt": "déchiffrer",
"edit": "modifier",
"export": "exporter",
"import": "importer",
"invite": "inviter",
"join": "rejoindre",
"logout": "se déconnecter",
"new": {
"post": "nouveau message",
"thread": "nouveau fil"
},
"notify": "notifier",
"post": "publier",
"post_message": "envoyer un message"
},
"admin": "administrateur",
"core": { "core": {
"block_height": "hauteur de bloc", "block_height": "hauteur de bloc",
"information": "informations du noyau", "information": "informations du noyau",
"peers": "pairs connectés", "peers": "pairs connectés",
"version": "version du noyau" "version": "version du noyau"
}, },
"ui": {
"version": "version de l'interface"
},
"count": {
"none": "aucun",
"one": "un"
},
"description": "description", "description": "description",
"edit": "éditer", "downloading_qdn": "téléchargement depuis QDN",
"export": "exporter", "fee": {
"import": "importer", "payment": "frais de paiement",
"publish": "frais de publication"
},
"general_settings": "paramètres généraux",
"last_height": "dernière hauteur", "last_height": "dernière hauteur",
"list": {
"invite": "liste d'invitations",
"join_request": "liste des demandes d'adhésion",
"member": "liste des membres"
},
"loading": "chargement...", "loading": "chargement...",
"logout": "se déconnecter", "loading_posts": "chargement des messages... veuillez patienter.",
"minting_status": "statut de frappe", "message_us": "veuillez nous contacter sur Telegram ou Discord si vous avez besoin de 4 QORT pour commencer à discuter sans limites",
"payment_notification": "notification de paiement", "message": {
"price": "prix",
"q_mail": "q-mail",
"result": {
"error": { "error": {
"generic": "une erreur s'est produite", "generic": "une erreur est survenue",
"incorrect_password": "mot de passe incorrect", "incorrect_password": "mot de passe incorrect",
"missing_field": "champ manquant : {{ field }}",
"save_qdn": "impossible d'enregistrer dans QDN" "save_qdn": "impossible d'enregistrer dans QDN"
}, },
"status": { "status": {
"minting": "(frappe en cours)", "minting": "(en cours de minage)",
"not_minting": "(pas de frappe)", "not_minting": "(non miné)",
"synchronized": "synchronisé", "synchronized": "synchronisé",
"synchronizing": "synchronisation en cours" "synchronizing": "synchronisation"
}, },
"success": { "success": {
"publish_qdn": "publié avec succès dans QDN" "order_submitted": "votre ordre d'achat a été envoyé",
"publish_qdn": "publié avec succès sur QDN",
"request_read": "j'ai lu cette demande",
"transfer": "le transfert a réussi !"
} }
}, },
"minting_status": "état du minage",
"page": {
"last": "dernier",
"first": "premier",
"next": "suivant",
"previous": "précédent"
},
"payment_notification": "notification de paiement",
"price": "prix",
"q_mail": "q-mail",
"question": {
"new_user": "êtes-vous un nouvel utilisateur ?"
},
"save_options": { "save_options": {
"no_pinned_changes": "vous n'avez actuellement aucune modification de vos applications épinglées", "no_pinned_changes": "vous n'avez actuellement aucun changement dans vos applications épinglées",
"overwrite_changes": "l'application n'a pas pu télécharger vos applications épinglées existantes enregistrées dans QDN. Voulez-vous écraser ces modifications ?", "overwrite_changes": "l'application n'a pas pu télécharger vos applications épinglées enregistrées sur QDN. Voulez-vous écraser ces modifications ?",
"overwrite_qdn": "écraser dans QDN", "overwrite_qdn": "écraser sur QDN",
"publish_qdn": "souhaitez-vous publier vos paramètres dans QDN (chiffré) ?", "publish_qdn": "voulez-vous publier vos paramètres sur QDN (chiffrés) ?",
"qdn": "utiliser l'enregistrement QDN", "qdn": "utiliser l'enregistrement QDN",
"register_name": "vous devez avoir un nom Qortal enregistré pour enregistrer vos applications épinglées dans QDN.", "register_name": "vous devez avoir un nom Qortal enregistré pour enregistrer vos applications épinglées sur QDN.",
"reset_pinned": "vous n'aimez pas vos modifications locales actuelles ? Voulez-vous réinitialiser les applications épinglées par défaut ?", "reset_pinned": "vous n'aimez pas vos changements locaux actuels ? Voulez-vous réinitialiser les applications par défaut ?",
"reset_qdn": "vous n'aimez pas vos modifications locales actuelles ? Voulez-vous réinitialiser vos applications épinglées enregistrées dans QDN ?", "reset_qdn": "vous n'aimez pas vos changements locaux actuels ? Voulez-vous restaurer les applications enregistrées sur QDN ?",
"revert_default": "revenir aux paramètres par défaut", "revert_default": "réinitialiser aux valeurs par défaut",
"revert_qdn": "revenir à QDN", "revert_qdn": "restaurer depuis QDN",
"save_qdn": "enregistrer dans QDN", "save_qdn": "enregistrer sur QDN",
"save": "enregistrer", "save": "enregistrer",
"settings": "vous utilisez la méthode d'exportation/importation pour enregistrer les paramètres.", "settings": "vous utilisez la méthode d'exportation/importation pour sauvegarder les paramètres.",
"unsaved_changes": "vous avez des modifications non enregistrées de vos applications épinglées. Enregistrez-les dans QDN." "unsaved_changes": "vous avez des modifications non enregistrées dans vos applications épinglées. Enregistrez-les sur QDN."
}, },
"settings": "paramètres", "settings": "paramètres",
"supply": "approvisionnement", "supply": "approvisionnement",
@ -59,11 +114,18 @@
"dark": "mode sombre", "dark": "mode sombre",
"light": "mode clair" "light": "mode clair"
}, },
"time": {
"day_one": "{{count}} jour",
"day_other": "{{count}} jours",
"hour_one": "{{count}} heure",
"hour_other": "{{count}} heures",
"minute_one": "{{count}} minute",
"minute_other": "{{count}} minutes"
},
"title": "titre", "title": "titre",
"tutorial": "tutoriel", "tutorial": "tutoriel",
"user_lookup": "recherche d'utilisateur", "user_lookup": "recherche d'utilisateur",
"wallet": { "wallet": {
"backup_wallet": "sauvegarder le portefeuille",
"wallet": "portefeuille", "wallet": "portefeuille",
"wallet_other": "portefeuilles" "wallet_other": "portefeuilles"
}, },

View File

@ -0,0 +1,105 @@
{
"action": {
"ban": "bannir un membre du groupe",
"cancel_ban": "annuler le bannissement",
"copy_private_key": "copier la clé privée",
"create_group": "créer un groupe",
"disable_push_notifications": "désactiver toutes les notifications push",
"enable_dev_mode": "activer le mode développeur",
"export_password": "exporter le mot de passe",
"export_private_key": "exporter la clé privée",
"find_group": "trouver un groupe",
"join_group": "rejoindre le groupe",
"kick_member": "expulser un membre du groupe",
"invite_member": "inviter un membre",
"leave_group": "quitter le groupe",
"load_members": "charger les membres avec noms",
"make_admin": "nommer administrateur",
"manage_members": "gérer les membres",
"refetch_page": "rafraîchir la page",
"remove_admin": "retirer les droits d'admin",
"return_to_thread": "retourner aux discussions"
},
"advanced_options": "options avancées",
"approval_threshold": "seuil d'approbation du groupe (nombre / pourcentage d'administrateurs devant approuver une transaction)",
"ban_list": "liste des bannis",
"block_delay": {
"minimum": "délai de bloc minimum pour l'approbation des transactions de groupe",
"maximum": "délai de bloc maximum pour l'approbation des transactions de groupe"
},
"group": {
"closed": "fermé (privé) les utilisateurs ont besoin d'une autorisation pour rejoindre",
"description": "description du groupe",
"id": "ID du groupe",
"invites": "invitations du groupe",
"management": "gestion du groupe",
"member_number": "nombre de membres",
"name": "nom du groupe",
"open": "ouvert (public)",
"type": "type de groupe"
},
"invitation_expiry": "délai d'expiration de l'invitation",
"invitees_list": "liste des invités",
"join_link": "lien pour rejoindre le groupe",
"join_requests": "demandes dadhésion",
"last_message": "dernier message",
"latest_mails": "derniers Q-Mails",
"message": {
"generic": {
"already_in_group": "vous êtes déjà dans ce groupe !",
"closed_group": "ce groupe est fermé/privé, vous devez attendre quun administrateur accepte votre demande",
"descrypt_wallet": "déchiffrement du portefeuille...",
"encryption_key": "la première clé de chiffrement partagée du groupe est en cours de création. Veuillez patienter quelques minutes pendant sa récupération par le réseau. Vérification toutes les 2 minutes...",
"group_invited_you": "{{group}} vous a invité",
"loading_members": "chargement de la liste des membres avec noms... veuillez patienter.",
"no_display": "rien à afficher",
"no_selection": "aucun groupe sélectionné",
"not_part_group": "vous ne faites pas partie du groupe chiffré. Attendez quun administrateur ré-encrypte les clés.",
"only_encrypted": "seuls les messages non chiffrés seront affichés.",
"private_key_copied": "clé privée copiée",
"provide_message": "veuillez fournir un premier message pour la discussion",
"secure_place": "gardez votre clé privée en lieu sûr. Ne la partagez pas !",
"setting_group": "configuration du groupe... veuillez patienter."
},
"error": {
"access_name": "impossible denvoyer un message sans accès à votre nom",
"descrypt_wallet": "erreur lors du déchiffrement du portefeuille {{ :errorMessage }}",
"description_required": "veuillez fournir une description",
"group_info": "impossible d'accéder aux informations du groupe",
"group_secret_key": "impossible dobtenir la clé secrète du groupe",
"name_required": "veuillez fournir un nom",
"notify_admins": "essayez de contacter un administrateur dans la liste ci-dessous :",
"thread_id": "impossible de localiser lidentifiant de discussion"
},
"success": {
"group_ban": "membre banni avec succès. Les changements peuvent prendre quelques minutes pour se propager",
"group_creation": "groupe créé avec succès. Les changements peuvent prendre quelques minutes pour se propager",
"group_creation_name": "groupe {{group_name}} créé : en attente de confirmation",
"group_creation_label": "groupe {{name}} créé : succès !",
"group_invite": "{{value}} invité avec succès. Les changements peuvent prendre quelques minutes pour se propager",
"group_join": "demande de rejoindre le groupe envoyée avec succès. Les changements peuvent prendre quelques minutes pour se propager",
"group_join_name": "rejoint le groupe {{group_name}} : en attente de confirmation",
"group_join_label": "rejoint le groupe {{name}} : succès !",
"group_join_request": "demande dadhésion au groupe {{group_name}} : en attente de confirmation",
"group_join_outcome": "adhésion au groupe {{group_name}} : succès !",
"group_kick": "membre expulsé avec succès du groupe. Les changements peuvent prendre quelques minutes pour se propager",
"group_leave": "demande de quitter le groupe envoyée avec succès. Les changements peuvent prendre quelques minutes pour se propager",
"group_leave_name": "a quitté le groupe {{group_name}} : en attente de confirmation",
"group_leave_label": "a quitté le groupe {{name}} : succès !",
"group_member_admin": "membre promu administrateur avec succès. Les changements peuvent prendre quelques minutes pour se propager",
"group_remove_member": "membre retiré en tant qu'administrateur avec succès. Les changements peuvent prendre quelques minutes pour se propager",
"invitation_cancellation": "invitation annulée avec succès. Les changements peuvent prendre quelques minutes pour se propager",
"invitation_request": "demande dadhésion acceptée : en attente de confirmation",
"loading_threads": "chargement des discussions... veuillez patienter.",
"post_creation": "publication créée avec succès. La propagation peut prendre un certain temps",
"thread_creation": "discussion créée avec succès. La propagation peut prendre un certain temps",
"unbanned_user": "utilisateur débanni avec succès. Les changements peuvent prendre quelques minutes pour se propager",
"user_joined": "utilisateur rejoint avec succès !"
}
},
"question": {
"perform_transaction": "souhaitez-vous effectuer une transaction de {{action}} ?",
"provide_thread": "veuillez fournir un titre pour la discussion"
},
"thread_posts": "nouveaux messages de discussion"
}

View File

@ -2,92 +2,114 @@
"action": { "action": {
"add": "aggiungi", "add": "aggiungi",
"accept": "accetta", "accept": "accetta",
"backup_account": "backup account", "backup_account": "esegui backup account",
"backup_wallet": "backup wallet", "backup_wallet": "esegui backup portafoglio",
"cancel": "annulla", "cancel": "annulla",
"cancel_invitation": "annulla invito",
"change": "cambia", "change": "cambia",
"change_language": "cambia lingua", "change_language": "cambia lingua",
"choose": "scegli", "choose": "scegli",
"close": "chiudi", "close": "chiudi",
"continue": "continua", "continue": "continua",
"continue_logout": "continua con il logout", "continue_logout": "continua con il logout",
"create_thread": "crea discussione",
"decline": "rifiuta", "decline": "rifiuta",
"decrypt": "decifra",
"edit": "modifica", "edit": "modifica",
"export": "esporta", "export": "esporta",
"import": "importa", "import": "importa",
"invite": "invita", "invite": "invita",
"join": "unisciti", "join": "unisciti",
"logout": "esci", "logout": "esci",
"notify": "notifica" "new": {
"post": "nuovo post",
"thread": "nuova discussione"
},
"notify": "notifica",
"post": "pubblica",
"post_message": "invia messaggio"
}, },
"admin": "amministratore",
"core": { "core": {
"block_height": "altezza blocco", "block_height": "altezza blocco",
"information": "informazioni core", "information": "informazioni core",
"peers": "peer connessi", "peers": "peer connessi",
"version": "versione core" "version": "versione core"
}, },
"ui": {
"version": "versione UI"
},
"count": { "count": {
"none": "nessuno", "none": "nessuno",
"one": "uno" "one": "uno"
}, },
"description": "descrizione", "description": "descrizione",
"downloading_qdn": "scaricamento da QDN",
"fee": { "fee": {
"payment": "commissione di pagamento", "payment": "commissione di pagamento",
"publish": "commissione di pubblicazione" "publish": "commissione di pubblicazione"
}, },
"page": { "general_settings": "impostazioni generali",
"last": "ultimo",
"first": "primo",
"next": "successivo",
"previous": "precedente"
},
"downloading_qdn": "scaricamento da QDN",
"last_height": "ultima altezza", "last_height": "ultima altezza",
"list": {
"invite": "lista inviti",
"join_request": "lista richieste di adesione",
"member": "lista membri"
},
"loading": "caricamento...", "loading": "caricamento...",
"loading_posts": "caricamento post... attendere prego.", "loading_posts": "caricamento post... attendere.",
"message_us": "per favore scrivici su Telegram o Discord se hai bisogno di 4 QORT per iniziare a chattare senza limitazioni", "message_us": "contattaci su Telegram o Discord se hai bisogno di 4 QORT per iniziare a chattare senza limitazioni",
"minting_status": "stato minting",
"new_user": "sei un nuovo utente?",
"payment_notification": "notifica di pagamento",
"price": "prezzo",
"q_mail": "q-mail",
"message": { "message": {
"error": { "error": {
"generic": "si è verificato un errore", "generic": "si è verificato un errore",
"incorrect_password": "password errata", "incorrect_password": "password errata",
"missing_field": "manca: {{ field }}",
"save_qdn": "impossibile salvare su QDN" "save_qdn": "impossibile salvare su QDN"
}, },
"status": { "status": {
"minting": "(minting)", "minting": "(in conio)",
"not_minting": "(non minting)", "not_minting": "(non in conio)",
"synchronized": "sincronizzato", "synchronized": "sincronizzato",
"synchronizing": "sincronizzazione in corso" "synchronizing": "sincronizzazione"
}, },
"success": { "success": {
"order_submitted": "il tuo ordine di acquisto è stato inviato", "order_submitted": "il tuo ordine di acquisto è stato inviato",
"publish_qdn": "pubblicato su QDN con successo", "publish_qdn": "pubblicato con successo su QDN",
"request_read": "ho letto questa richiesta", "request_read": "ho letto questa richiesta",
"transfer": "il trasferimento è stato effettuato con successo!" "transfer": "il trasferimento è riuscito!"
} }
}, },
"minting_status": "stato conio",
"page": {
"last": "ultima",
"first": "prima",
"next": "successiva",
"previous": "precedente"
},
"payment_notification": "notifica di pagamento",
"price": "prezzo",
"q_mail": "q-mail",
"question": {
"new_user": "sei un nuovo utente?"
},
"save_options": { "save_options": {
"no_pinned_changes": "attualmente non hai modifiche alle tue app appuntate", "no_pinned_changes": "attualmente non hai modifiche nelle app fissate",
"overwrite_changes": "l'app non è riuscita a scaricare le tue app appuntate salvate su QDN. Vuoi sovrascrivere le modifiche?", "overwrite_changes": "l'app non è riuscita a scaricare le tue app fissate salvate su QDN. Vuoi sovrascrivere le modifiche?",
"overwrite_qdn": "sovrascrivi su QDN", "overwrite_qdn": "sovrascrivi su QDN",
"publish_qdn": "vuoi pubblicare le tue impostazioni su QDN (crittografato)?", "publish_qdn": "vuoi pubblicare le tue impostazioni su QDN (crittografate)?",
"qdn": "usa il salvataggio su QDN", "qdn": "usa salvataggio QDN",
"register_name": "devi avere un nome Qortal registrato per salvare le tue app appuntate su QDN.", "register_name": "hai bisogno di un nome Qortal registrato per salvare le app fissate su QDN.",
"reset_pinned": "non ti piacciono le modifiche locali attuali? Vuoi ripristinare le app appuntate predefinite?", "reset_pinned": "non ti piacciono le modifiche locali? Vuoi ripristinare le app fissate predefinite?",
"reset_qdn": "non ti piacciono le modifiche locali attuali? Vuoi ripristinare le app appuntate salvate su QDN?", "reset_qdn": "non ti piacciono le modifiche locali? Vuoi ripristinare le app fissate salvate su QDN?",
"revert_default": "ripristina predefinito", "revert_default": "ripristina ai valori predefiniti",
"revert_qdn": "ripristina da QDN", "revert_qdn": "ripristina da QDN",
"save_qdn": "salva su QDN", "save_qdn": "salva su QDN",
"save": "salva", "save": "salva",
"settings": "stai usando il metodo di esportazione/importazione per salvare le impostazioni.", "settings": "stai usando il metodo di esportazione/importazione per salvare le impostazioni.",
"unsaved_changes": "hai modifiche non salvate alle tue app appuntate. Salvale su QDN." "unsaved_changes": "hai modifiche non salvate nelle app fissate. Salvale su QDN."
}, },
"settings": "impostazioni", "settings": "impostazioni",
"supply": "disponibilità", "supply": "offerta",
"theme": { "theme": {
"dark": "modalità scura", "dark": "modalità scura",
"light": "modalità chiara" "light": "modalità chiara"
@ -104,8 +126,8 @@
"tutorial": "tutorial", "tutorial": "tutorial",
"user_lookup": "ricerca utente", "user_lookup": "ricerca utente",
"wallet": { "wallet": {
"wallet": "wallet", "wallet": "portafoglio",
"wallet_other": "wallet" "wallet_other": "portafogli"
}, },
"welcome": "benvenuto" "welcome": "benvenuto"
} }

View File

@ -1,65 +1,105 @@
{ {
"action": { "action": {
"ban": "banna membro dal gruppo",
"cancel_ban": "annulla ban", "cancel_ban": "annulla ban",
"copy_private_key": "copia chiave privata",
"create_group": "crea gruppo", "create_group": "crea gruppo",
"disable_push_notifications": "disattiva tutte le notifiche push",
"enable_dev_mode": "attiva modalità sviluppatore",
"export_password": "esporta password",
"export_private_key": "esporta chiave privata",
"find_group": "trova gruppo", "find_group": "trova gruppo",
"join_group": "unisciti al gruppo", "join_group": "unisciti al gruppo",
"kick_member": "espelli membro dal gruppo",
"invite_member": "invita membro", "invite_member": "invita membro",
"leave_group": "lascia il gruppo",
"load_members": "carica membri con nomi",
"make_admin": "rendi amministratore",
"manage_members": "gestisci membri",
"refetch_page": "ricarica pagina", "refetch_page": "ricarica pagina",
"return_to_thread": "torna ai thread" "remove_admin": "rimuovi come amministratore",
"return_to_thread": "torna alle discussioni"
}, },
"advanced_options": "opzioni avanzate", "advanced_options": "opzioni avanzate",
"approval_threshold": "soglia di Approvazione del gruppo (numero/percentuale di Admin che devono approvare una transazione)", "approval_threshold": "soglia di approvazione del gruppo (numero / percentuale di amministratori che devono approvare una transazione)",
"ban_list": "lista ban", "ban_list": "lista bannati",
"block_delay": { "block_delay": {
"minimum": "ritardo minimo dei blocchi per Approvazione di Transazioni di Gruppo", "minimum": "ritardo minimo dei blocchi per le approvazioni delle transazioni di gruppo",
"maximum": "ritardo massimo dei blocchi per Approvazione di Transazioni di Gruppo" "maximum": "ritardo massimo dei blocchi per le approvazioni delle transazioni di gruppo"
}, },
"group": { "group": {
"closed": "chiuso (privato) - gli utenti necessitano di permesso per unirsi", "closed": "chiuso (privato) gli utenti hanno bisogno del permesso per entrare",
"description": "descrizione del gruppo", "description": "descrizione del gruppo",
"id": "ID gruppo",
"invites": "inviti del gruppo", "invites": "inviti del gruppo",
"management": "gestione del gruppo", "management": "gestione del gruppo",
"member_number": "numero di membri",
"name": "nome del gruppo", "name": "nome del gruppo",
"open": "aperto (pubblico)", "open": "aperto (pubblico)",
"type": "tipo di gruppo" "type": "tipo di gruppo"
}, },
"invitation_expiry": "tempo di scadenza dell'invito", "invitation_expiry": "tempo di scadenza dell'invito",
"invitees_list": "lista degli invitati",
"join_link": "link per unirsi al gruppo",
"join_requests": "richieste di adesione", "join_requests": "richieste di adesione",
"question": { "last_message": "ultimo messaggio",
"cancel_ban": "vuoi eseguire una transazione CANCEL_GROUP_BAN?", "latest_mails": "ultimi Q-Mails",
"create_group": "vuoi eseguire una transazione CREATE_GROUP?",
"group_invite": "vuoi eseguire una transazione GROUP_INVITE?",
"join_group": "vuoi eseguire una transazione JOIN_GROUP?",
"provide_thread": "per favore fornisci un titolo per il thread"
},
"message": { "message": {
"generic": { "generic": {
"encryption_key": "la prima chiave di cifratura comune del gruppo è in fase di creazione. Attendere alcuni minuti affinché venga recuperata dalla rete. Controllo ogni 2 minuti...", "already_in_group": "sei già in questo gruppo!",
"closed_group": "questo è un gruppo chiuso/privato, devi aspettare che un admin accetti la tua richiesta",
"descrypt_wallet": "decrittazione portafoglio in corso...",
"encryption_key": "la prima chiave di cifratura comune del gruppo è in fase di creazione. Attendere qualche minuto affinché venga recuperata dalla rete. Controllo ogni 2 minuti...",
"group_invited_you": "{{group}} ti ha invitato", "group_invited_you": "{{group}} ti ha invitato",
"no_display": "niente da visualizzare", "loading_members": "caricamento elenco membri con nomi... attendere.",
"no_display": "niente da mostrare",
"no_selection": "nessun gruppo selezionato", "no_selection": "nessun gruppo selezionato",
"not_part_group": "non fai parte del gruppo cifrato dei membri. Attendi che un amministratore ri-codifichi le chiavi.", "not_part_group": "non fai parte del gruppo cifrato. Attendi che un amministratore re-critti le chiavi.",
"only_encrypted": "verranno visualizzati solo i messaggi non cifrati.", "only_encrypted": "verranno mostrati solo i messaggi non cifrati.",
"setting_group": "configurazione del gruppo... attendere prego." "private_key_copied": "chiave privata copiata",
"provide_message": "inserisci un primo messaggio per la discussione",
"secure_place": "conserva la tua chiave privata in un luogo sicuro. Non condividerla!",
"setting_group": "configurazione del gruppo in corso... attendere."
}, },
"error": { "error": {
"access_name": "impossibile inviare un messaggio senza accesso al tuo nome", "access_name": "impossibile inviare un messaggio senza accesso al tuo nome",
"description_required": "per favore fornisci una descrizione", "descrypt_wallet": "errore nella decrittazione del portafoglio {{ :errorMessage }}",
"description_required": "inserisci una descrizione",
"group_info": "impossibile accedere alle informazioni del gruppo", "group_info": "impossibile accedere alle informazioni del gruppo",
"name_required": "per favore fornisci un nome", "group_secret_key": "impossibile ottenere la chiave segreta del gruppo",
"notify_admins": "prova a notificare un admin dalla lista di amministratori qui sotto:" "name_required": "inserisci un nome",
"notify_admins": "prova a contattare un amministratore dalla lista qui sotto:",
"thread_id": "impossibile trovare l'ID della discussione"
}, },
"success": { "success": {
"group_ban": "membro bannato con successo dal gruppo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"group_creation": "gruppo creato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino", "group_creation": "gruppo creato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"group_creation_name": "gruppo {{group_name}} creato: in attesa di conferma", "group_creation_name": "gruppo {{group_name}} creato: in attesa di conferma",
"group_creation_label": "gruppo {{name}} creato: successo!", "group_creation_label": "gruppo {{name}} creato: successo!",
"group_invite": "invito inviato con successo a {{value}}. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino", "group_invite": "{{value}} invitato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"join_creation": "richiesta di adesione al gruppo inviata con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino", "group_join": "richiesta di accesso al gruppo inviata con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"group_join_name": "entrato nel gruppo {{group_name}}: in attesa di conferma", "group_join_name": "entrato nel gruppo {{group_name}}: in attesa di conferma",
"group_join_label": "entrato nel gruppo {{name}}: successo!", "group_join_label": "entrato nel gruppo {{name}}: successo!",
"loading_threads": "caricamento thread... attendere prego.", "group_join_request": "richiesta di accesso al gruppo {{group_name}}: in attesa di conferma",
"unbanned_user": "utente sbannato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino" "group_join_outcome": "richiesta di accesso al gruppo {{group_name}}: successo!",
"group_kick": "membro espulso con successo dal gruppo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"group_leave": "richiesta di uscita dal gruppo inviata con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"group_leave_name": "uscito dal gruppo {{group_name}}: in attesa di conferma",
"group_leave_label": "uscito dal gruppo {{name}}: successo!",
"group_member_admin": "membro nominato amministratore con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"group_remove_member": "amministratore rimosso con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"invitation_cancellation": "invito annullato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"invitation_request": "richiesta di adesione accettata: in attesa di conferma",
"loading_threads": "caricamento discussioni... attendere.",
"post_creation": "post creato con successo. Potrebbe volerci un po' per la pubblicazione",
"thread_creation": "discussione creata con successo. Potrebbe volerci un po' per la pubblicazione",
"unbanned_user": "utente sbannato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino",
"user_joined": "utente entrato con successo!"
} }
} },
"question": {
"perform_transaction": "vuoi eseguire una transazione di tipo {{action}}?",
"provide_thread": "inserisci un titolo per la discussione"
},
"thread_posts": "nuovi messaggi nella discussione"
} }

View File

@ -1,53 +1,108 @@
{ {
"add": "добавить", "action": {
"cancel": "отмена", "add": "добавить",
"choose": "выбрать", "accept": "принять",
"close": "закрыть", "backup_account": "резервное копирование аккаунта",
"continue": "продолжить", "backup_wallet": "резервное копирование кошелька",
"cancel": "отменить",
"cancel_invitation": "отменить приглашение",
"change": "изменить",
"change_language": "сменить язык",
"choose": "выбрать",
"close": "закрыть",
"continue": "продолжить",
"continue_logout": "продолжить выход",
"create_thread": "создать тему",
"decline": "отклонить",
"decrypt": "расшифровать",
"edit": "редактировать",
"export": "экспорт",
"import": "импорт",
"invite": "пригласить",
"join": "присоединиться",
"logout": "выйти",
"new": {
"post": "новое сообщение",
"thread": "новая тема"
},
"notify": "уведомить",
"post": "опубликовать",
"post_message": "отправить сообщение"
},
"admin": "администратор",
"core": { "core": {
"block_height": "высота блока", "block_height": "высота блока",
"information": "информация ядра", "information": "информация ядра",
"peers": "подключенные узлы", "peers": "подключённые узлы",
"version": "версия ядра" "version": "версия ядра"
}, },
"ui": {
"version": "версия интерфейса"
},
"count": {
"none": "нет",
"one": "один"
},
"description": "описание", "description": "описание",
"edit": "редактировать", "downloading_qdn": "загрузка из QDN",
"export": "экспорт", "fee": {
"import": "импорт", "payment": "комиссия за платёж",
"publish": "комиссия за публикацию"
},
"general_settings": "общие настройки",
"last_height": "последняя высота", "last_height": "последняя высота",
"list": {
"invite": "список приглашений",
"join_request": "список запросов на вступление",
"member": "список участников"
},
"loading": "загрузка...", "loading": "загрузка...",
"logout": "выйти", "loading_posts": "загрузка сообщений... пожалуйста, подождите.",
"minting_status": "статус чеканки", "message_us": "напишите нам в Telegram или Discord, если вам нужно 4 QORT, чтобы начать чат без ограничений",
"payment_notification": "уведомление о платеже", "message": {
"price": "цена",
"q_mail": "q-mail",
"result": {
"error": { "error": {
"generic": "произошла ошибка", "generic": "произошла ошибка",
"incorrect_password": "неверный пароль", "incorrect_password": "неверный пароль",
"missing_field": "отсутствует: {{ field }}",
"save_qdn": "не удалось сохранить в QDN" "save_qdn": "не удалось сохранить в QDN"
}, },
"status": { "status": {
"minting": "(чеканка)", "minting": "(выпуск монет)",
"not_minting": "(не чеканится)", "not_minting": "(не выпускается)",
"synchronized": "синхронизировано", "synchronized": "синхронизировано",
"synchronizing": "синхронизация" "synchronizing": "синхронизация"
}, },
"success": { "success": {
"publish_qdn": "успешно опубликовано в QDN" "order_submitted": "ваш ордер на покупку отправлен",
"publish_qdn": "успешно опубликовано в QDN",
"request_read": "я прочитал этот запрос",
"transfer": "перевод выполнен успешно!"
} }
}, },
"minting_status": "статус выпуска",
"page": {
"last": "последняя",
"first": "первая",
"next": "следующая",
"previous": "предыдущая"
},
"payment_notification": "уведомление о платеже",
"price": "цена",
"q_mail": "q-mail",
"question": {
"new_user": "вы новый пользователь?"
},
"save_options": { "save_options": {
"no_pinned_changes": "у вас нет изменений в закреплённых приложениях", "no_pinned_changes": "у вас нет изменений в закреплённых приложениях",
"overwrite_changes": "приложению не удалось загрузить ваши закреплённые приложения, сохранённые в QDN. Хотите перезаписать эти изменения?", "overwrite_changes": "не удалось загрузить ваши закреплённые приложения из QDN. Перезаписать изменения?",
"overwrite_qdn": "перезаписать в QDN", "overwrite_qdn": "перезаписать в QDN",
"publish_qdn": "хотите опубликовать свои настройки в QDN (зашифровано)?", "publish_qdn": "опубликовать настройки в QDN (в зашифрованном виде)?",
"qdn": "использовать сохранение в QDN", "qdn": "использовать сохранение в QDN",
"register_name": "вам необходимо зарегистрированное имя Qortal для сохранения закреплённых приложений в QDN.", "register_name": "вам нужно зарегистрированное имя Qortal, чтобы сохранять закреплённые приложения в QDN.",
"reset_pinned": "не устраивают текущие локальные изменения? Хотите сбросить до приложений по умолчанию?", "reset_pinned": "не нравятся текущие локальные изменения? Сбросить до стандартных приложений?",
"reset_qdn": "не устраивают текущие локальные изменения? Хотите сбросить до сохранённых в QDN приложений?", "reset_qdn": "не нравятся текущие локальные изменения? Сбросить до сохранённых в QDN приложений?",
"revert_default": "сбросить до стандартных", "revert_default": "сбросить по умолчанию",
"revert_qdn": "сбросить до QDN", "revert_qdn": "восстановить из QDN",
"save_qdn": "сохранить в QDN", "save_qdn": "сохранить в QDN",
"save": "сохранить", "save": "сохранить",
"settings": "вы используете метод экспорта/импорта для сохранения настроек.", "settings": "вы используете метод экспорта/импорта для сохранения настроек.",
@ -59,11 +114,18 @@
"dark": "тёмная тема", "dark": "тёмная тема",
"light": "светлая тема" "light": "светлая тема"
}, },
"time": {
"day_one": "{{count}} день",
"day_other": "{{count}} дней",
"hour_one": "{{count}} час",
"hour_other": "{{count}} часов",
"minute_one": "{{count}} минута",
"minute_other": "{{count}} минут"
},
"title": "заголовок", "title": "заголовок",
"tutorial": "учебник", "tutorial": "учебник",
"user_lookup": "поиск пользователя", "user_lookup": "поиск пользователя",
"wallet": { "wallet": {
"backup_wallet": "резервная копия кошелька",
"wallet": "кошелёк", "wallet": "кошелёк",
"wallet_other": "кошельки" "wallet_other": "кошельки"
}, },

View File

@ -0,0 +1,105 @@
{
"action": {
"ban": "заблокировать участника группы",
"cancel_ban": "отменить блокировку",
"copy_private_key": "скопировать приватный ключ",
"create_group": "создать группу",
"disable_push_notifications": "отключить все push-уведомления",
"enable_dev_mode": "включить режим разработчика",
"export_password": "экспортировать пароль",
"export_private_key": "экспортировать приватный ключ",
"find_group": "найти группу",
"join_group": "вступить в группу",
"kick_member": "исключить участника из группы",
"invite_member": "пригласить участника",
"leave_group": "выйти из группы",
"load_members": "загрузить участников с именами",
"make_admin": "сделать админом",
"manage_members": "управлять участниками",
"refetch_page": "перезагрузить страницу",
"remove_admin": "удалить из админов",
"return_to_thread": "вернуться к темам"
},
"advanced_options": "расширенные настройки",
"approval_threshold": "порог одобрения группы (число / процент админов, необходимых для подтверждения транзакции)",
"ban_list": "список заблокированных",
"block_delay": {
"minimum": "минимальная задержка блоков для одобрения транзакций в группе",
"maximum": "максимальная задержка блоков для одобрения транзакций в группе"
},
"group": {
"closed": "закрытая (приватная) — требуется разрешение на вступление",
"description": "описание группы",
"id": "ID группы",
"invites": "приглашения группы",
"management": "управление группой",
"member_number": "количество участников",
"name": "название группы",
"open": "открытая (публичная)",
"type": "тип группы"
},
"invitation_expiry": "время истечения приглашения",
"invitees_list": "список приглашённых",
"join_link": "ссылка для вступления в группу",
"join_requests": "запросы на вступление",
"last_message": "последнее сообщение",
"latest_mails": "последние Q-Mail'ы",
"message": {
"generic": {
"already_in_group": "вы уже в этой группе!",
"closed_group": "эта группа закрыта, дождитесь подтверждения от администратора",
"descrypt_wallet": "дешифровка кошелька...",
"encryption_key": "первая общая ключевая фраза группы создаётся. Подождите несколько минут для получения через сеть. Проверка каждые 2 минуты...",
"group_invited_you": "{{group}} пригласила вас",
"loading_members": "загрузка списка участников с именами... пожалуйста, подождите.",
"no_display": "нечего отображать",
"no_selection": "группа не выбрана",
"not_part_group": "вы не состоите в зашифрованной группе. Дождитесь повторного шифрования ключей администратором.",
"only_encrypted": "отображаются только незашифрованные сообщения.",
"private_key_copied": "приватный ключ скопирован",
"provide_message": "введите первое сообщение для темы",
"secure_place": "храните приватный ключ в безопасном месте. Не передавайте его никому!",
"setting_group": "настройка группы... пожалуйста, подождите."
},
"error": {
"access_name": "невозможно отправить сообщение без доступа к вашему имени",
"descrypt_wallet": "ошибка при дешифровке кошелька {{ :errorMessage }}",
"description_required": "введите описание",
"group_info": "не удалось получить информацию о группе",
"group_secret_key": "не удалось получить секретный ключ группы",
"name_required": "введите имя",
"notify_admins": "попробуйте связаться с администратором из списка ниже:",
"thread_id": "не удалось найти ID темы"
},
"success": {
"group_ban": "участник успешно заблокирован. Распространение изменений может занять несколько минут",
"group_creation": "группа успешно создана. Распространение изменений может занять несколько минут",
"group_creation_name": "создана группа {{group_name}}: ожидает подтверждения",
"group_creation_label": "группа {{name}} создана: успех!",
"group_invite": "{{value}} успешно приглашён. Распространение изменений может занять несколько минут",
"group_join": "запрос на вступление успешно отправлен. Распространение изменений может занять несколько минут",
"group_join_name": "вступление в группу {{group_name}}: ожидает подтверждения",
"group_join_label": "вступление в группу {{name}}: успех!",
"group_join_request": "запрос на вступление в группу {{group_name}}: ожидает подтверждения",
"group_join_outcome": "вступление в группу {{group_name}}: успех!",
"group_kick": "участник успешно удалён из группы. Распространение изменений может занять несколько минут",
"group_leave": "запрос на выход из группы успешно отправлен. Распространение изменений может занять несколько минут",
"group_leave_name": "выход из группы {{group_name}}: ожидает подтверждения",
"group_leave_label": "выход из группы {{name}}: успех!",
"group_member_admin": "пользователь успешно назначен администратором. Распространение изменений может занять несколько минут",
"group_remove_member": "администратор успешно удалён. Распространение изменений может занять несколько минут",
"invitation_cancellation": "приглашение успешно отменено. Распространение изменений может занять несколько минут",
"invitation_request": "запрос на вступление принят: ожидает подтверждения",
"loading_threads": "загрузка тем... пожалуйста, подождите.",
"post_creation": "сообщение успешно создано. Публикация может занять некоторое время",
"thread_creation": "тема успешно создана. Публикация может занять некоторое время",
"unbanned_user": "пользователь успешно разблокирован. Распространение изменений может занять несколько минут",
"user_joined": "пользователь успешно присоединился!"
}
},
"question": {
"perform_transaction": "вы хотите выполнить транзакцию типа {{action}}?",
"provide_thread": "укажите заголовок темы"
},
"thread_posts": "новые сообщения в теме"
}

View File

@ -1,10 +1,9 @@
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import React from 'react';
export const CopyIcon = ({ color, height = 11, width = 10 }) => { export const CopyIcon = ({ color, height = 11, width = 10 }) => {
const theme = useTheme(); const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary; const setColor = color ? color : theme.palette.text.primary;
return ( return (
<svg <svg
width={width} width={width}
@ -14,11 +13,11 @@ export const CopyIcon = ({ color, height = 11, width = 10 }) => {
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
fill-rule="evenodd" fillRule="evenodd"
clip-rule="evenodd" clipRule="evenodd"
d="M3.92857 0.5H8.57143C9.36071 0.5 10 1.13929 10 1.92857V6.57143C10 7.36071 9.36071 8 8.57143 8H8.21429V4.42857C8.21429 3.24643 7.25357 2.28571 6.07143 2.28571H2.5V1.92857C2.5 1.13929 3.13929 0.5 3.92857 0.5ZM1.42857 3H6.07143C6.86041 3 7.5 3.63959 7.5 4.42857V9.07143C7.5 9.86041 6.86041 10.5 6.07143 10.5H1.42857C0.639593 10.5 0 9.86041 0 9.07143V4.42857C0 3.63959 0.639593 3 1.42857 3Z" d="M3.92857 0.5H8.57143C9.36071 0.5 10 1.13929 10 1.92857V6.57143C10 7.36071 9.36071 8 8.57143 8H8.21429V4.42857C8.21429 3.24643 7.25357 2.28571 6.07143 2.28571H2.5V1.92857C2.5 1.13929 3.13929 0.5 3.92857 0.5ZM1.42857 3H6.07143C6.86041 3 7.5 3.63959 7.5 4.42857V9.07143C7.5 9.86041 6.86041 10.5 6.07143 10.5H1.42857C0.639593 10.5 0 9.86041 0 9.07143V4.42857C0 3.63959 0.639593 3 1.42857 3Z"
fill={setColor} fill={setColor}
fill-opacity="0.5" fillOpacity="0.5"
/> />
</svg> </svg>
); );

View File

@ -1,6 +1,6 @@
import React from 'react';
import './customloader.css'; import './customloader.css';
import { Box, useTheme } from '@mui/material'; import { Box, useTheme } from '@mui/material';
export const CustomLoader = () => { export const CustomLoader = () => {
const theme = useTheme(); const theme = useTheme();
return ( return (

View File

@ -1,17 +1,14 @@
import React from 'react';
export const CustomSvg = ({ src, color = 'black', size = 24 }) => { export const CustomSvg = ({ src, color = 'black', size = 24 }) => {
return ( return (
<svg <svg
width={size} width={size}
height={size} height={size}
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
style={{ fill: color }} style={{ fill: color }}
> >
{src} {src}
</svg> </svg>
); );
}; };

View File

@ -36,6 +36,7 @@
left: 56px; left: 56px;
animation: lds-ellipsis3 0.6s infinite; animation: lds-ellipsis3 0.6s infinite;
} }
@keyframes lds-ellipsis1 { @keyframes lds-ellipsis1 {
0% { 0% {
transform: scale(0); transform: scale(0);

View File

@ -1,4 +1,4 @@
import React, { useCallback, useRef } from 'react'; import { useCallback } from 'react';
import { resourceDownloadControllerAtom } from '../atoms/global'; import { resourceDownloadControllerAtom } from '../atoms/global';
import { getBaseApiReact } from '../App'; import { getBaseApiReact } from '../App';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';

View File

@ -56,7 +56,7 @@ export const AppsCategoryDesktop = ({
isShow, isShow,
}) => { }) => {
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const virtuosoRef = useRef(); const virtuosoRef = useRef(null);
const theme = useTheme(); const theme = useTheme();
const categoryList = useMemo(() => { const categoryList = useMemo(() => {

View File

@ -102,7 +102,7 @@ export const AppsLibraryDesktop = ({
getQapps, getQapps,
}) => { }) => {
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const virtuosoRef = useRef(); const virtuosoRef = useRef(null);
const theme = useTheme(); const theme = useTheme();
const officialApps = useMemo(() => { const officialApps = useMemo(() => {

View File

@ -63,6 +63,7 @@ export const DownloadWallet = ({
wallet, wallet,
qortAddress: rawWallet.address0, qortAddress: rawWallet.address0,
}); });
return { return {
wallet, wallet,
qortAddress: rawWallet.address0, qortAddress: rawWallet.address0,

View File

@ -18,7 +18,7 @@ export const AnnouncementList = ({
loadMore, loadMore,
myName, myName,
}) => { }) => {
const listRef = useRef(); const listRef = useRef(null);
const [messages, setMessages] = useState(initialMessages); const [messages, setMessages] = useState(initialMessages);
useEffect(() => { useEffect(() => {

View File

@ -23,7 +23,7 @@ export const ChatList = ({
hasSecretKey, hasSecretKey,
isPrivate, isPrivate,
}) => { }) => {
const parentRef = useRef(); const parentRef = useRef(null);
const [messages, setMessages] = useState(initialMessages); const [messages, setMessages] = useState(initialMessages);
const [showScrollButton, setShowScrollButton] = useState(false); const [showScrollButton, setShowScrollButton] = useState(false);
const [showScrollDownButton, setShowScrollDownButton] = useState(false); const [showScrollDownButton, setShowScrollDownButton] = useState(false);

View File

@ -60,8 +60,8 @@ export const ChatOptions = ({
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const [selectedMember, setSelectedMember] = useState(0); const [selectedMember, setSelectedMember] = useState(0);
const theme = useTheme(); const theme = useTheme();
const parentRef = useRef(); const parentRef = useRef(null);
const parentRefMentions = useRef(); const parentRefMentions = useRef(null);
const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null); const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null);
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
const messages = useMemo(() => { const messages = useMemo(() => {

View File

@ -31,6 +31,7 @@ import {
import { RequestQueueWithPromise } from '../../utils/queue/queue'; import { RequestQueueWithPromise } from '../../utils/queue/queue';
import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group/Group'; import { addDataPublishesFunc, getDataPublishesFunc } from '../Group/Group';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 8 }); const uid = new ShortUniqueId({ length: 8 });
@ -101,6 +102,7 @@ export const decryptPublishes = async (encryptedMessages: any[], secretKey) => {
console.log(error); console.log(error);
} }
}; };
export const handleUnencryptedPublishes = (publishes) => { export const handleUnencryptedPublishes = (publishes) => {
let publishesData = []; let publishesData = [];
publishes.forEach((pub) => { publishes.forEach((pub) => {
@ -149,6 +151,7 @@ export const GroupAnnouncements = ({
editorRef.current = editorInstance; editorRef.current = editorInstance;
}; };
const [, forceUpdate] = React.useReducer((x) => x + 1, 0); const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
const { t } = useTranslation(['core', 'group']);
const triggerRerender = () => { const triggerRerender = () => {
forceUpdate(); // Trigger re-render by updating the state forceUpdate(); // Trigger re-render by updating the state
@ -209,7 +212,6 @@ export const GroupAnnouncements = ({
) )
return; return;
setIsLoading(true); setIsLoading(true);
// initWebsocketMessageGroup()
hasInitializedWebsocket.current = true; hasInitializedWebsocket.current = true;
}, [secretKey, isPrivate]); }, [secretKey, isPrivate]);
@ -287,7 +289,10 @@ export const GroupAnnouncements = ({
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
await show({ await show({
message: 'Would you like to perform a ARBITRARY transaction?', message: t('group:question.perform_transaction', {
action: 'ARBITRARY',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -329,7 +334,7 @@ export const GroupAnnouncements = ({
setTempData(selectedGroup); setTempData(selectedGroup);
clearEditorContent(); clearEditorContent();
} }
// send chat message // TODO send chat message
} catch (error) { } catch (error) {
if (!error) return; if (!error) return;
setInfoSnack({ setInfoSnack({

View File

@ -114,7 +114,7 @@ export const CoreSyncStatus = () => {
</span> </span>
<div <div
className="bottom" className="core-panel"
style={{ style={{
right: 'unset', right: 'unset',
left: '55px', left: '55px',
@ -122,29 +122,35 @@ export const CoreSyncStatus = () => {
}} }}
> >
<h3>{t('core:core.information', { postProcess: 'capitalize' })}</h3> <h3>{t('core:core.information', { postProcess: 'capitalize' })}</h3>
<h4 className="lineHeight"> <h4 className="lineHeight">
{t('core:core.version', { postProcess: 'capitalize' })}:{' '} {t('core:core.version', { postProcess: 'capitalize' })}:{' '}
<span style={{ color: '#03a9f4' }}>{buildVersion}</span> <span style={{ color: '#03a9f4' }}>{buildVersion}</span>
</h4> </h4>
<h4 className="lineHeight">{message}</h4> <h4 className="lineHeight">{message}</h4>
<h4 className="lineHeight"> <h4 className="lineHeight">
{t('core:core.block_height', { postProcess: 'capitalize' })}:{' '} {t('core:core.block_height', { postProcess: 'capitalize' })}:{' '}
<span style={{ color: '#03a9f4' }}>{height || ''}</span> <span style={{ color: '#03a9f4' }}>{height || ''}</span>
</h4> </h4>
<h4 className="lineHeight"> <h4 className="lineHeight">
{t('core:core.peers', { postProcess: 'capitalize' })}:{' '} {t('core:core.peers', { postProcess: 'capitalize' })}:{' '}
<span style={{ color: '#03a9f4' }}> <span style={{ color: '#03a9f4' }}>
{numberOfConnections || ''} {numberOfConnections || ''}
</span> </span>
</h4> </h4>
<h4 className="lineHeight"> <h4 className="lineHeight">
{t('auth:node.using_public', { postProcess: 'capitalize' })}:{' '} {t('auth:node.using_public', { postProcess: 'capitalize' })}:{' '}
<span style={{ color: '#03a9f4' }}> <span style={{ color: '#03a9f4' }}>
{isUsingGateway?.toString()} {isUsingGateway?.toString()}
</span> </span>
</h4> </h4>
<h4 className="lineHeight"> <h4 className="lineHeight">
{t('core:ui.version')}:{' '} {t('core:ui.version', { postProcess: 'capitalize' })}:{' '}
<span style={{ color: '#03a9f4' }}>{manifestData.version}</span> <span style={{ color: '#03a9f4' }}>{manifestData.version}</span>
</h4> </h4>
</div> </div>

View File

@ -3,10 +3,8 @@ import Box from '@mui/material/Box';
import { HubsIcon } from '../../assets/Icons/HubsIcon'; import { HubsIcon } from '../../assets/Icons/HubsIcon';
import { MessagingIcon } from '../../assets/Icons/MessagingIcon'; import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
import AppIcon from '../../assets/svgs/AppIcon.svg'; import AppIcon from '../../assets/svgs/AppIcon.svg';
import { HomeIcon } from '../../assets/Icons/HomeIcon'; import { HomeIcon } from '../../assets/Icons/HomeIcon';
import { Save } from '../Save/Save'; import { Save } from '../Save/Save';
import { enabledDevModeAtom } from '../../atoms/global'; import { enabledDevModeAtom } from '../../atoms/global';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';

View File

@ -1,4 +1,4 @@
import * as React from 'react'; import { useState } from 'react';
import { ButtonBase, Typography, useTheme } from '@mui/material'; import { ButtonBase, Typography, useTheme } from '@mui/material';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import { NotificationIcon2 } from '../../assets/Icons/NotificationIcon2'; import { NotificationIcon2 } from '../../assets/Icons/NotificationIcon2';
@ -81,18 +81,18 @@ export const DesktopHeader = ({
setGroupSection, setGroupSection,
isPrivate, isPrivate,
}) => { }) => {
const [value, setValue] = React.useState(0); const [value, setValue] = useState(0);
const theme = useTheme(); const theme = useTheme();
return ( return (
<Box <Box
sx={{ sx={{
width: '100%',
display: 'flex',
alignItems: 'center', alignItems: 'center',
display: 'flex',
height: '70px', // Footer height height: '70px', // Footer height
zIndex: 1,
justifyContent: 'space-between', justifyContent: 'space-between',
padding: '10px', padding: '10px',
width: '100%',
zIndex: 1,
}} }}
> >
<Box <Box
@ -126,11 +126,12 @@ export const DesktopHeader = ({
: selectedGroup?.groupName} : selectedGroup?.groupName}
</Typography> </Typography>
</Box> </Box>
<Box <Box
sx={{ sx={{
alignItems: 'center',
display: 'flex', display: 'flex',
gap: '20px', gap: '20px',
alignItems: 'center',
visibility: selectedGroup?.groupId === '0' ? 'hidden' : 'visibile', visibility: selectedGroup?.groupId === '0' ? 'hidden' : 'visibile',
}} }}
> >
@ -219,6 +220,7 @@ export const DesktopHeader = ({
/> />
</IconWrapper> </IconWrapper>
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
setOpenManageMembers(true); setOpenManageMembers(true);
@ -226,17 +228,18 @@ export const DesktopHeader = ({
> >
<IconWrapper <IconWrapper
color={theme.palette.text.secondary} color={theme.palette.text.secondary}
customHeight="55px"
label="Members" label="Members"
selected={false} selected={false}
customHeight="55px"
> >
<MembersIcon <MembersIcon
color={theme.palette.text.secondary}
height={25} height={25}
width={20} width={20}
color={theme.palette.text.secondary}
/> />
</IconWrapper> </IconWrapper>
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
setGroupSection('adminSpace'); setGroupSection('adminSpace');

View File

@ -1,5 +1,6 @@
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Drawer from '@mui/material/Drawer'; import Drawer from '@mui/material/Drawer';
export const DrawerComponent = ({ open, setOpen, children }) => { export const DrawerComponent = ({ open, setOpen, children }) => {
const toggleDrawer = (newOpen: boolean) => () => { const toggleDrawer = (newOpen: boolean) => () => {
setOpen(newOpen); setOpen(newOpen);

View File

@ -40,6 +40,7 @@ export const Explore = ({ setDesktopViewMode }) => {
}} }}
src={qTradeLogo} src={qTradeLogo}
/> />
<Typography <Typography
sx={{ sx={{
fontSize: '1rem', fontSize: '1rem',
@ -66,6 +67,7 @@ export const Explore = ({ setDesktopViewMode }) => {
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
/> />
<Typography <Typography
sx={{ sx={{
fontSize: '1rem', fontSize: '1rem',
@ -94,6 +96,7 @@ export const Explore = ({ setDesktopViewMode }) => {
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
/> />
<Typography <Typography
sx={{ sx={{
fontSize: '1rem', fontSize: '1rem',
@ -102,6 +105,7 @@ export const Explore = ({ setDesktopViewMode }) => {
{t('tutorial:initial.general_chat', { postProcess: 'capitalize' })} {t('tutorial:initial.general_chat', { postProcess: 'capitalize' })}
</Typography> </Typography>
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
sx={{ sx={{
'&:hover': { backgroundColor: theme.palette.background.paper }, '&:hover': { backgroundColor: theme.palette.background.paper },
@ -119,6 +123,7 @@ export const Explore = ({ setDesktopViewMode }) => {
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
/> />
<Typography <Typography
sx={{ sx={{
fontSize: '1rem', fontSize: '1rem',

View File

@ -1,4 +1,3 @@
import React from 'react';
import { JoinGroup } from './JoinGroup'; import { JoinGroup } from './JoinGroup';
export const GlobalActions = () => { export const GlobalActions = () => {

View File

@ -1,8 +1,7 @@
import React, { useContext, useEffect, useMemo, useState } from 'react'; import { useContext, useEffect, useMemo, useState } from 'react';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
import { import {
Box, Box,
Button,
ButtonBase, ButtonBase,
CircularProgress, CircularProgress,
Dialog, Dialog,
@ -11,13 +10,14 @@ import {
Typography, Typography,
useTheme, useTheme,
} from '@mui/material'; } from '@mui/material';
import { CustomButton, CustomButtonAccept } from '../../styles/App-styles'; import { CustomButtonAccept } from '../../styles/App-styles';
import { getBaseApiReact, MyContext } from '../../App'; import { getBaseApiReact, MyContext } from '../../App';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { FidgetSpinner } from 'react-loader-spinner'; import { FidgetSpinner } from 'react-loader-spinner';
import { useAtom, useSetAtom } from 'jotai'; import { useAtom, useSetAtom } from 'jotai';
import { memberGroupsAtom, txListAtom } from '../../atoms/global'; import { memberGroupsAtom, txListAtom } from '../../atoms/global';
import { useTranslation } from 'react-i18next';
export const JoinGroup = () => { export const JoinGroup = () => {
const { show } = useContext(MyContext); const { show } = useContext(MyContext);
@ -29,7 +29,9 @@ export const JoinGroup = () => {
const [isLoadingInfo, setIsLoadingInfo] = useState(false); const [isLoadingInfo, setIsLoadingInfo] = useState(false);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'group']);
const [isLoadingJoinGroup, setIsLoadingJoinGroup] = useState(false); const [isLoadingJoinGroup, setIsLoadingJoinGroup] = useState(false);
const handleJoinGroup = async (e) => { const handleJoinGroup = async (e) => {
setGroupInfo(null); setGroupInfo(null);
const groupId = e?.detail?.groupId; const groupId = e?.detail?.groupId;
@ -41,6 +43,7 @@ export const JoinGroup = () => {
const groupData = await response.json(); const groupData = await response.json();
setGroupInfo(groupData); setGroupInfo(groupData);
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoadingInfo(false); setIsLoadingInfo(false);
} }
@ -60,15 +63,22 @@ export const JoinGroup = () => {
(item) => +item?.groupId === +groupInfo?.groupId (item) => +item?.groupId === +groupInfo?.groupId
); );
}, [memberGroups, groupInfo]); }, [memberGroups, groupInfo]);
const joinGroup = async (group, isOpen) => { const joinGroup = async (group, isOpen) => {
try { try {
const groupId = group.groupId; const groupId = group.groupId;
const fee = await getFee('JOIN_GROUP'); const fee = await getFee('JOIN_GROUP');
await show({ await show({
message: 'Would you like to perform an JOIN_GROUP transaction?', message: t('group:question.perform_transaction', {
action: 'JOIN_GROUP',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingJoinGroup(true); setIsLoadingJoinGroup(true);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage('joinGroup', { .sendMessage('joinGroup', {
@ -78,8 +88,9 @@ export const JoinGroup = () => {
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.group_join', {
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
if (isOpen) { if (isOpen) {
@ -87,8 +98,14 @@ export const JoinGroup = () => {
{ {
...response, ...response,
type: 'joined-group', type: 'joined-group',
label: `Joined Group ${group?.groupName}: awaiting confirmation`, label: t('group:message.success.group_join_label', {
labelDone: `Joined Group ${group?.groupName}: success!`, group_name: group?.groupName,
postProcess: 'capitalize',
}),
labelDone: t('group:message.success.group_join_label', {
group_name: group?.groupName,
postProcess: 'capitalize',
}),
done: false, done: false,
groupId, groupId,
}, },
@ -99,15 +116,20 @@ export const JoinGroup = () => {
{ {
...response, ...response,
type: 'joined-group-request', type: 'joined-group-request',
label: `Requested to join Group ${group?.groupName}: awaiting confirmation`, label: t('group:message.success.group_join_request', {
labelDone: `Requested to join Group ${group?.groupName}: success!`, group_name: group?.groupName,
postProcess: 'capitalize',
}),
labelDone: t('group:message.success.group_join_outcome', {
group_name: group?.groupName,
postProcess: 'capitalize',
}),
done: false, done: false,
groupId, groupId,
}, },
...prev, ...prev,
]); ]);
} }
setOpenSnack(true); setOpenSnack(true);
res(response); res(response);
return; return;
@ -123,7 +145,9 @@ export const JoinGroup = () => {
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error.message || 'An error occurred', message:
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -131,10 +155,12 @@ export const JoinGroup = () => {
}); });
setIsLoadingJoinGroup(false); setIsLoadingJoinGroup(false);
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoadingJoinGroup(false); setIsLoadingJoinGroup(false);
} }
}; };
return ( return (
<> <>
<Dialog <Dialog
@ -146,32 +172,31 @@ export const JoinGroup = () => {
{!groupInfo && ( {!groupInfo && (
<Box <Box
sx={{ sx={{
width: '325px',
height: '150px',
display: 'flex',
alignItems: 'center', alignItems: 'center',
display: 'flex',
height: '150px',
justifyContent: 'center', justifyContent: 'center',
width: '325px',
}} }}
> >
{' '}
<CircularProgress <CircularProgress
size={25} size={25}
sx={{ sx={{
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
/>{' '} />
</Box> </Box>
)} )}
<Box <Box
sx={{ sx={{
width: '325px', alignItems: 'center',
height: 'auto',
maxHeight: '400px',
display: !groupInfo ? 'none' : 'flex', display: !groupInfo ? 'none' : 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center',
gap: '10px', gap: '10px',
height: 'auto',
maxHeight: '400px',
padding: '10px', padding: '10px',
width: '325px',
}} }}
> >
<Typography <Typography
@ -180,16 +205,20 @@ export const JoinGroup = () => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
Group name: {` ${groupInfo?.groupName}`} {t('group:group.name', { postProcess: 'capitalize' })}:{' '}
{` ${groupInfo?.groupName}`}
</Typography> </Typography>
<Typography <Typography
sx={{ sx={{
fontSize: '15px', fontSize: '15px',
fontWeight: 600, fontWeight: 600,
}} }}
> >
Number of members: {` ${groupInfo?.memberCount}`} {t('group:group.member_number', { postProcess: 'capitalize' })}:{' '}
{` ${groupInfo?.memberCount}`}
</Typography> </Typography>
{groupInfo?.description && ( {groupInfo?.description && (
<Typography <Typography
sx={{ sx={{
@ -207,7 +236,9 @@ export const JoinGroup = () => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
*You are already in this group! {t('group:message.generic.already_in_group', {
postProcess: 'capitalize',
})}
</Typography> </Typography>
)} )}
{!isInGroup && groupInfo?.isOpen === false && ( {!isInGroup && groupInfo?.isOpen === false && (
@ -217,12 +248,14 @@ export const JoinGroup = () => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
*This is a closed/private group, so you will need to wait until {t('group:message.generic.closed_group', {
an admin accepts your request postProcess: 'capitalize',
})}
</Typography> </Typography>
)} )}
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
@ -242,7 +275,9 @@ export const JoinGroup = () => {
opacity: isInGroup ? 0.1 : 1, opacity: isInGroup ? 0.1 : 1,
}} }}
> >
Join {t('core:action.join', {
postProcess: 'capitalize',
})}
</CustomButtonAccept> </CustomButtonAccept>
</ButtonBase> </ButtonBase>
@ -255,7 +290,9 @@ export const JoinGroup = () => {
}} }}
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
> >
Close {t('core:action.close', {
postProcess: 'capitalize',
})}
</CustomButtonAccept> </CustomButtonAccept>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@ -269,14 +306,14 @@ export const JoinGroup = () => {
{isLoadingJoinGroup && ( {isLoadingJoinGroup && (
<Box <Box
sx={{ sx={{
position: 'absolute', alignItems: 'center',
top: 0,
left: 0,
right: 0,
bottom: 0, bottom: 0,
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', left: 0,
position: 'absolute',
right: 0,
top: 0,
}} }}
> >
<FidgetSpinner <FidgetSpinner

View File

@ -118,7 +118,8 @@ export const AddGroup = ({ address, open, setOpen }) => {
const fee = await getFee('CREATE_GROUP'); const fee = await getFee('CREATE_GROUP');
await show({ await show({
message: t('group:question.create_group', { message: t('group:question.perform_transaction', {
action: 'CREATE_GROUP',
postProcess: 'capitalize', postProcess: 'capitalize',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
@ -330,6 +331,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
postProcess: 'capitalize', postProcess: 'capitalize',
})} })}
</Label> </Label>
<Input <Input
placeholder={t('group:group.name', { placeholder={t('group:group.name', {
postProcess: 'capitalize', postProcess: 'capitalize',
@ -338,6 +340,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
/> />
</Box> </Box>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -449,6 +452,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
<MenuItem value={100}>100%</MenuItem> <MenuItem value={100}>100%</MenuItem>
</Select> </Select>
</Box> </Box>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -461,6 +465,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
postProcess: 'capitalize', postProcess: 'capitalize',
})} })}
</Label> </Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
id="demo-simple-select" id="demo-simple-select"
@ -506,6 +511,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
</MenuItem> </MenuItem>
</Select> </Select>
</Box> </Box>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -518,6 +524,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
postProcess: 'capitalize', postProcess: 'capitalize',
})} })}
</Label> </Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
id="demo-simple-select" id="demo-simple-select"
@ -561,6 +568,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
</Select> </Select>
</Box> </Box>
</Collapse> </Collapse>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',

View File

@ -48,7 +48,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
const [groups, setGroups] = useState([]); const [groups, setGroups] = useState([]);
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
const listRef = useRef(); const listRef = useRef(null);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [filteredItems, setFilteredItems] = useState(groups); const [filteredItems, setFilteredItems] = useState(groups);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -113,7 +113,8 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
const fee = await getFee('JOIN_GROUP'); const fee = await getFee('JOIN_GROUP');
await show({ await show({
message: t('group:question.join_group', { message: t('group:question.perform_transaction', {
action: 'JOIN_GROUP',
postProcess: 'capitalize', postProcess: 'capitalize',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
@ -157,8 +158,14 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
{ {
...response, ...response,
type: 'joined-group-request', type: 'joined-group-request',
label: `Requested to join Group ${group?.groupName}: awaiting confirmation`, label: t('group:message.success.group_join_request', {
labelDone: `Requested to join Group ${group?.groupName}: success!`, group_name: group?.groupName,
postProcess: 'capitalize',
}),
labelDone: t('group:message.success.group_join_outcome', {
group_name: group?.groupName,
postProcess: 'capitalize',
}),
done: false, done: false,
groupId, groupId,
}, },

View File

@ -1,26 +1,17 @@
import React, { import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { Avatar, Box, Popover, Typography, useTheme } from '@mui/material'; import { Avatar, Box, Popover, Typography, useTheme } from '@mui/material';
// import { MAIL_SERVICE_TYPE, THREAD_SERVICE_TYPE } from "../../constants/mail";
import { Thread } from './Thread'; import { Thread } from './Thread';
import { import {
AllThreadP, AllThreadP,
ArrowDownIcon, ArrowDownIcon,
ComposeContainer, ComposeContainer,
ComposeContainerBlank, ComposeContainerBlank,
ComposeIcon,
ComposeP, ComposeP,
GroupContainer, GroupContainer,
InstanceFooter, InstanceFooter,
InstanceListContainer, InstanceListContainer,
InstanceListContainerRow, InstanceListContainerRow,
InstanceListContainerRowCheck, InstanceListContainerRowCheck,
InstanceListContainerRowCheckIcon,
InstanceListContainerRowMain, InstanceListContainerRowMain,
InstanceListContainerRowMainP, InstanceListContainerRowMainP,
InstanceListHeader, InstanceListHeader,
@ -48,7 +39,6 @@ import {
getTempPublish, getTempPublish,
handleUnencryptedPublishes, handleUnencryptedPublishes,
} from '../../Chat/GroupAnnouncements'; } from '../../Chat/GroupAnnouncements';
import CheckSVG from '../../../assets/svgs/Check.svg';
import ArrowDownSVG from '../../../assets/svgs/ArrowDown.svg'; import ArrowDownSVG from '../../../assets/svgs/ArrowDown.svg';
import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar'; import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar';
import { executeEvent } from '../../../utils/events'; import { executeEvent } from '../../../utils/events';
@ -73,9 +63,9 @@ export const GroupMail = ({
hide, hide,
isPrivate, isPrivate,
}) => { }) => {
const [viewedThreads, setViewedThreads] = React.useState<any>({}); const [viewedThreads, setViewedThreads] = useState<any>({});
const [filterMode, setFilterMode] = useState<string>('Recently active'); const [filterMode, setFilterMode] = useState<string>('Recently active');
const [currentThread, setCurrentThread] = React.useState(null); const [currentThread, setCurrentThread] = useState(null);
const [recentThreads, setRecentThreads] = useState<any[]>([]); const [recentThreads, setRecentThreads] = useState<any[]>([]);
const [allThreads, setAllThreads] = useState<any[]>([]); const [allThreads, setAllThreads] = useState<any[]>([]);
const [members, setMembers] = useState<any>(null); const [members, setMembers] = useState<any>(null);
@ -178,7 +168,10 @@ export const GroupMail = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' })
);
}); });
}); });
} catch (error) { } catch (error) {
@ -186,7 +179,7 @@ export const GroupMail = ({
} }
}; };
const getAllThreads = React.useCallback( const getAllThreads = useCallback(
async (groupId: string, mode: string, isInitial?: boolean) => { async (groupId: string, mode: string, isInitial?: boolean) => {
try { try {
setIsLoading(true); setIsLoading(true);
@ -206,7 +199,7 @@ export const GroupMail = ({
}); });
const responseData = await response.json(); const responseData = await response.json();
let fullArrayMsg = isInitial ? [] : [...allThreads]; const fullArrayMsg = isInitial ? [] : [...allThreads];
const getMessageForThreads = responseData.map(async (message: any) => { const getMessageForThreads = responseData.map(async (message: any) => {
let fullObject: any = null; let fullObject: any = null;
if (message?.metadata?.description) { if (message?.metadata?.description) {
@ -271,13 +264,12 @@ export const GroupMail = ({
} finally { } finally {
if (isInitial) { if (isInitial) {
setIsLoading(false); setIsLoading(false);
// dispatch(setIsLoadingCustom(null));
} }
} }
}, },
[allThreads, isPrivate] [allThreads, isPrivate]
); );
const getMailMessages = React.useCallback( const getMailMessages = useCallback(
async (groupId: string, members: any) => { async (groupId: string, members: any) => {
try { try {
setIsLoading(true); setIsLoading(true);
@ -315,7 +307,7 @@ export const GroupMail = ({
.sort((a, b) => b.created - a.created) .sort((a, b) => b.created - a.created)
.slice(0, 10); .slice(0, 10);
let fullThreadArray: any = []; const fullThreadArray: any = [];
const getMessageForThreads = newArray.map(async (message: any) => { const getMessageForThreads = newArray.map(async (message: any) => {
try { try {
const identifierQuery = message.threadId; const identifierQuery = message.threadId;
@ -327,6 +319,7 @@ export const GroupMail = ({
}, },
}); });
const responseData = await response.json(); const responseData = await response.json();
if (responseData.length > 0) { if (responseData.length > 0) {
const thread = responseData[0]; const thread = responseData[0];
if (thread?.metadata?.description) { if (thread?.metadata?.description) {
@ -342,7 +335,7 @@ export const GroupMail = ({
}; };
fullThreadArray.push(fullObject); fullThreadArray.push(fullObject);
} else { } else {
let threadRes = await Promise.race([ const threadRes = await Promise.race([
getEncryptedResource( getEncryptedResource(
{ {
name: thread.name, name: thread.name,
@ -377,13 +370,12 @@ export const GroupMail = ({
console.log(error); console.log(error);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
// dispatch(setIsLoadingCustom(null));
} }
}, },
[secretKey, isPrivate] [secretKey, isPrivate]
); );
const getMessages = React.useCallback(async () => { const getMessages = useCallback(async () => {
// if ( !groupId || members?.length === 0) return; // if ( !groupId || members?.length === 0) return;
if (!groupId || isPrivate === null) return; if (!groupId || isPrivate === null) return;
@ -400,7 +392,6 @@ export const GroupMail = ({
if (filterModeRef.current !== filterMode) { if (filterModeRef.current !== filterMode) {
firstMount.current = false; firstMount.current = false;
} }
// if (groupId && !firstMount.current && members.length > 0) {
if (groupId && !firstMount.current && isPrivate !== null) { if (groupId && !firstMount.current && isPrivate !== null) {
if (filterMode === 'Recently active') { if (filterMode === 'Recently active') {
getMessages(); getMessages();
@ -427,11 +418,6 @@ export const GroupMail = ({
if (groupData && Array.isArray(groupData?.members)) { if (groupData && Array.isArray(groupData?.members)) {
for (const member of groupData.members) { for (const member of groupData.members) {
if (member.member) { if (member.member) {
// const res = await getNameInfo(member.member);
// const resAddress = await qortalRequest({
// action: "GET_ACCOUNT_DATA",
// address: member.member,
// });
const name = res; const name = res;
const publicKey = resAddress.publicKey; const publicKey = resAddress.publicKey;
if (name) { if (name) {
@ -465,16 +451,6 @@ export const GroupMail = ({
[filterMode] [filterMode]
); );
// useEffect(()=> {
// if(user?.name){
// const threads = JSON.parse(
// localStorage.getItem(`qmail_threads_viewedtimestamp_${user.name}`) || "{}"
// );
// setViewedThreads(threads)
// }
// }, [user?.name, currentThread])
const handleCloseThreadFilterList = () => { const handleCloseThreadFilterList = () => {
setIsOpenFilterList(false); setIsOpenFilterList(false);
}; };
@ -596,7 +572,7 @@ export const GroupMail = ({
padding: '0px', padding: '0px',
}} }}
> >
<InstanceListHeader></InstanceListHeader> <InstanceListHeader />
<InstanceListContainer> <InstanceListContainer>
{filterOptions?.map((filter) => { {filterOptions?.map((filter) => {
return ( return (
@ -621,6 +597,7 @@ export const GroupMail = ({
/> />
)} )}
</InstanceListContainerRowCheck> </InstanceListContainerRowCheck>
<InstanceListContainerRowMain> <InstanceListContainerRowMain>
<InstanceListContainerRowMainP> <InstanceListContainerRowMainP>
{filter} {filter}
@ -630,9 +607,10 @@ export const GroupMail = ({
); );
})} })}
</InstanceListContainer> </InstanceListContainer>
<InstanceFooter></InstanceFooter> <InstanceFooter />
</InstanceListParent> </InstanceListParent>
</Popover> </Popover>
<ThreadContainerFullWidth> <ThreadContainerFullWidth>
<ThreadContainer> <ThreadContainer>
<Box <Box
@ -674,7 +652,9 @@ export const GroupMail = ({
)} )}
</ComposeContainerBlank> </ComposeContainerBlank>
</Box> </Box>
<Spacer height="30px" /> <Spacer height="30px" />
<Box <Box
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
@ -700,10 +680,12 @@ export const GroupMail = ({
viewedThreads[ viewedThreads[
`qmail_threads_${thread?.threadData?.groupId}_${thread?.threadId}` `qmail_threads_${thread?.threadData?.groupId}_${thread?.threadId}`
]; ];
const shouldAppearLighter = const shouldAppearLighter =
hasViewedRecent && hasViewedRecent &&
filterMode === 'Recently active' && filterMode === 'Recently active' &&
thread?.threadData?.createdAt < hasViewedRecent?.timestamp; thread?.threadData?.createdAt < hasViewedRecent?.timestamp;
return ( return (
<SingleThreadParent <SingleThreadParent
sx={{ sx={{
@ -771,13 +753,17 @@ export const GroupMail = ({
> >
<ThreadSingleLastMessageP> <ThreadSingleLastMessageP>
<ThreadSingleLastMessageSpanP> <ThreadSingleLastMessageSpanP>
last message:{' '} {t('group:last_message', {
postProcess: 'capitalize',
})}
:{' '}
</ThreadSingleLastMessageSpanP> </ThreadSingleLastMessageSpanP>
{formatDate(thread?.created)} {formatDate(thread?.created)}
</ThreadSingleLastMessageP> </ThreadSingleLastMessageP>
</div> </div>
)} )}
</div> </div>
<CustomButton <CustomButton
onClick={() => { onClick={() => {
setTimeout(() => { setTimeout(() => {
@ -834,6 +820,7 @@ export const GroupMail = ({
</Box> </Box>
</ThreadContainer> </ThreadContainer>
</ThreadContainerFullWidth> </ThreadContainerFullWidth>
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react'; import { useContext, useEffect, useRef, useState } from 'react';
import { Box, CircularProgress, Input, useTheme } from '@mui/material'; import { Box, CircularProgress, Input, useTheme } from '@mui/material';
import ShortUniqueId from 'short-unique-id'; import ShortUniqueId from 'short-unique-id';
import { import {
@ -8,7 +8,6 @@ import {
InstanceFooter, InstanceFooter,
InstanceListContainer, InstanceListContainer,
InstanceListHeader, InstanceListHeader,
NewMessageCloseImg,
NewMessageHeaderP, NewMessageHeaderP,
NewMessageInputRow, NewMessageInputRow,
NewMessageSendButton, NewMessageSendButton,
@ -143,13 +142,13 @@ export const NewThread = ({
isPrivate, isPrivate,
}: NewMessageProps) => { }: NewMessageProps) => {
const { t } = useTranslation(['core', 'group']); const { t } = useTranslation(['core', 'group']);
const { show } = React.useContext(MyContext); const { show } = useContext(MyContext);
const [isOpen, setIsOpen] = useState<boolean>(false); const [isOpen, setIsOpen] = useState<boolean>(false);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const [isSending, setIsSending] = useState(false); const [isSending, setIsSending] = useState(false);
const [threadTitle, setThreadTitle] = useState<string>(''); const [threadTitle, setThreadTitle] = useState<string>('');
const [openSnack, setOpenSnack] = React.useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = React.useState(null); const [infoSnack, setInfoSnack] = useState(null);
const editorRef = useRef(null); const editorRef = useRef(null);
const theme = useTheme(); const theme = useTheme();
const setEditorRef = (editorInstance) => { const setEditorRef = (editorInstance) => {
@ -203,31 +202,37 @@ export const NewThread = ({
// if (!description) missingFields.push('subject') // if (!description) missingFields.push('subject')
if (missingFields.length > 0) { if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', '); const missingFieldsString = missingFields.join(', ');
const errMsg = `Missing: ${missingFieldsString}`; const errMsg = t('group:message.error.missing_field', {
errorMsg = errMsg; // TODO translate field: missingFieldsString,
postProcess: 'capitalize',
});
errorMsg = errMsg;
} }
if (errorMsg) { if (errorMsg) {
// dispatch(
// setNotification({
// msg: errorMsg,
// alertType: "error",
// })
// );
throw new Error(errorMsg); throw new Error(errorMsg);
} }
const htmlContent = editorRef.current.getHTML(); const htmlContent = editorRef.current.getHTML();
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') {
throw new Error('Please provide a first message to the thread'); const errMsg = t('group:message.generic.provide_message', {
postProcess: 'capitalize',
});
throw new Error(errMsg);
}
const fee = await getFee('ARBITRARY'); const fee = await getFee('ARBITRARY');
let feeToShow = fee.fee; let feeToShow = fee.fee;
if (!isMessage) { if (!isMessage) {
feeToShow = +feeToShow * 2; feeToShow = +feeToShow * 2;
} }
await show({ await show({
message: 'Would you like to perform a ARBITRARY transaction?', message: t('group:question.perform_transaction', {
action: 'ARBITRARY',
postProcess: 'capitalize',
}),
publishFee: feeToShow + ' QORT', publishFee: feeToShow + ' QORT',
}); });
@ -238,6 +243,7 @@ export const NewThread = ({
delete reply.reply; delete reply.reply;
} }
} }
const mailObject: any = { const mailObject: any = {
createdAt: Date.now(), createdAt: Date.now(),
version: 1, version: 1,
@ -250,7 +256,10 @@ export const NewThread = ({
const secretKey = const secretKey =
isPrivate === false ? null : await getSecretKey(false, true); isPrivate === false ? null : await getSecretKey(false, true);
if (!secretKey && isPrivate) { if (!secretKey && isPrivate) {
throw new Error('Cannot get group secret key'); const errMsg = t('group:message.error.group_secret_key', {
postProcess: 'capitalize',
});
throw new Error(errMsg);
} }
if (!isMessage) { if (!isMessage) {
@ -273,17 +282,18 @@ export const NewThread = ({
isPrivate === false isPrivate === false
? threadToBase64 ? threadToBase64
: await encryptSingleFunc(threadToBase64, secretKey); : await encryptSingleFunc(threadToBase64, secretKey);
let identifierThread = `grp-${groupInfo.groupId}-thread-${idThread}`; const identifierThread = `grp-${groupInfo.groupId}-thread-${idThread}`;
await publishGroupEncryptedResource({ await publishGroupEncryptedResource({
identifier: identifierThread, identifier: identifierThread,
encryptedData: encryptSingleThread, encryptedData: encryptSingleThread,
}); });
let identifierPost = `thmsg-${identifierThread}-${idMsg}`; const identifierPost = `thmsg-${identifierThread}-${idMsg}`;
await publishGroupEncryptedResource({ await publishGroupEncryptedResource({
identifier: identifierPost, identifier: identifierPost,
encryptedData: encryptSingleFirstPost, encryptedData: encryptSingleFirstPost,
}); });
const dataToSaveToStorage = { const dataToSaveToStorage = {
name: myName, name: myName,
identifier: identifierThread, identifier: identifierThread,
@ -292,6 +302,7 @@ export const NewThread = ({
created: Date.now(), created: Date.now(),
groupId: groupInfo.groupId, groupId: groupInfo.groupId,
}; };
const dataToSaveToStoragePost = { const dataToSaveToStoragePost = {
name: myName, name: myName,
identifier: identifierPost, identifier: identifierPost,
@ -300,6 +311,7 @@ export const NewThread = ({
created: Date.now(), created: Date.now(),
threadId: identifierThread, threadId: identifierThread,
}; };
await saveTempPublish({ data: dataToSaveToStorage, key: 'thread' }); await saveTempPublish({ data: dataToSaveToStorage, key: 'thread' });
await saveTempPublish({ await saveTempPublish({
data: dataToSaveToStoragePost, data: dataToSaveToStoragePost,
@ -307,36 +319,32 @@ export const NewThread = ({
}); });
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.thread_creation', {
'Successfully created thread. It may take some time for the publish to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
// dispatch(
// setNotification({
// msg: "Message sent",
// alertType: "success",
// })
// );
if (publishCallback) { if (publishCallback) {
publishCallback(); publishCallback();
} }
closeModal(); closeModal();
} else { } else {
if (!currentThread) throw new Error('unable to locate thread Id'); if (!currentThread) {
const errMsg = t('group:message.error.thread_id', {
postProcess: 'capitalize',
});
throw new Error(errMsg);
}
const idThread = currentThread.threadId; const idThread = currentThread.threadId;
const messageToBase64 = await objectToBase64(mailObject); const messageToBase64 = await objectToBase64(mailObject);
const encryptSinglePost = const encryptSinglePost =
isPrivate === false isPrivate === false
? messageToBase64 ? messageToBase64
: await encryptSingleFunc(messageToBase64, secretKey); : await encryptSingleFunc(messageToBase64, secretKey);
const idMsg = uid.rnd();
let identifier = `thmsg-${idThread}-${idMsg}`;
const res = await publishGroupEncryptedResource({
identifier: identifier,
encryptedData: encryptSinglePost,
});
const idMsg = uid.rnd();
const identifier = `thmsg-${idThread}-${idMsg}`;
const dataToSaveToStoragePost = { const dataToSaveToStoragePost = {
threadId: idThread, threadId: idThread,
name: myName, name: myName,
@ -349,32 +357,17 @@ export const NewThread = ({
data: dataToSaveToStoragePost, data: dataToSaveToStoragePost,
key: 'thread-post', key: 'thread-post',
}); });
// await qortalRequest(multiplePublishMsg);
// dispatch(
// setNotification({
// msg: "Message sent",
// alertType: "success",
// })
// );
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.post_creation', {
'Successfully created post. It may take some time for the publish to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
if (publishCallback) { if (publishCallback) {
publishCallback(); publishCallback();
} }
// messageCallback({
// identifier,
// id: identifier,
// name,
// service: MAIL_SERVICE_TYPE,
// created: Date.now(),
// ...mailObject,
// });
} }
closeModal(); closeModal();
} catch (error: any) { } catch (error: any) {
if (error?.message) { if (error?.message) {
@ -393,6 +386,7 @@ export const NewThread = ({
const sendMail = () => { const sendMail = () => {
publishQDNResource(); publishQDNResource();
}; };
return ( return (
<Box <Box
sx={{ sx={{
@ -407,7 +401,15 @@ export const NewThread = ({
onClick={() => setIsOpen(true)} onClick={() => setIsOpen(true)}
> >
<ComposeIcon /> <ComposeIcon />
<ComposeP>{currentThread ? 'New Post' : 'New Thread'}</ComposeP> <ComposeP>
{currentThread
? t('core:action.new.post', {
postProcess: 'capitalize',
})
: t('core:action.new.thread', {
postProcess: 'capitalize',
})}
</ComposeP>
</ComposeContainer> </ComposeContainer>
<ReusableModal <ReusableModal
@ -433,8 +435,15 @@ export const NewThread = ({
}} }}
> >
<NewMessageHeaderP> <NewMessageHeaderP>
{isMessage ? 'Post Message' : 'New Thread'} {isMessage
? t('core:action.post_message', {
postProcess: 'capitalize',
})
: t('core:action.new.thread', {
postProcess: 'capitalize',
})}
</NewMessageHeaderP> </NewMessageHeaderP>
<CloseContainer <CloseContainer
sx={{ sx={{
height: '40px', height: '40px',
@ -448,6 +457,7 @@ export const NewThread = ({
/> />
</CloseContainer> </CloseContainer>
</InstanceListHeader> </InstanceListHeader>
<InstanceListContainer <InstanceListContainer
sx={{ sx={{
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
@ -459,6 +469,7 @@ export const NewThread = ({
{!isMessage && ( {!isMessage && (
<> <>
<Spacer height="10px" /> <Spacer height="10px" />
<NewMessageInputRow> <NewMessageInputRow>
<Input <Input
id="standard-adornment-name" id="standard-adornment-name"
@ -516,28 +527,27 @@ export const NewThread = ({
overrideMobile overrideMobile
customEditorHeight="240px" customEditorHeight="240px"
/> />
</Box> </Box>
</InstanceListContainer> </InstanceListContainer>
<InstanceFooter <InstanceFooter
sx={{ sx={{
backgroundColor: theme.palette.background.paper,
padding: '20px 42px',
alignItems: 'center', alignItems: 'center',
backgroundColor: theme.palette.background.paper,
height: '90px', height: '90px',
padding: '20px 42px',
}} }}
> >
<NewMessageSendButton onClick={sendMail}> <NewMessageSendButton onClick={sendMail}>
{isSending && ( {isSending && (
<Box <Box
sx={{ sx={{
alignItems: 'center',
display: 'flex',
height: '100%', height: '100%',
justifyContent: 'center',
position: 'absolute', position: 'absolute',
width: '100%', width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}} }}
> >
<CircularProgress <CircularProgress
@ -550,7 +560,13 @@ export const NewThread = ({
)} )}
<NewMessageSendP> <NewMessageSendP>
{isMessage ? 'Post' : 'Create Thread'} {isMessage
? t('core:action.post', {
postProcess: 'capitalize',
})
: t('core:action.create_thread', {
postProcess: 'capitalize',
})}
</NewMessageSendP> </NewMessageSendP>
{isMessage ? ( {isMessage ? (

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { createEditor } from 'slate'; import { createEditor } from 'slate';
import { import {
withReact, withReact,
@ -96,7 +96,7 @@ interface ReadOnlySlateProps {
content: any; content: any;
mode?: string; mode?: string;
} }
const ReadOnlySlate: React.FC<ReadOnlySlateProps> = ({ content, mode }) => { const ReadOnlySlate: FC<ReadOnlySlateProps> = ({ content, mode }) => {
const [load, setLoad] = useState(false); const [load, setLoad] = useState(false);
const editor = useMemo(() => withReact(createEditor()), []); const editor = useMemo(() => withReact(createEditor()), []);
const value = useMemo(() => content, [content]); const value = useMemo(() => content, [content]);

View File

@ -1,4 +1,4 @@
import React from 'react'; import { FC } from 'react';
import { Box, Modal, useTheme } from '@mui/material'; import { Box, Modal, useTheme } from '@mui/material';
interface MyModalProps { interface MyModalProps {
@ -9,7 +9,7 @@ interface MyModalProps {
customStyles?: any; customStyles?: any;
} }
export const ReusableModal: React.FC<MyModalProps> = ({ export const ReusableModal: FC<MyModalProps> = ({
open, open,
onClose, onClose,
onSubmit, onSubmit,

View File

@ -118,27 +118,6 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
width: 'auto', width: 'auto',
}} }}
> >
{/* <FileElement
fileInfo={{ ...file, mimeTypeSaved: file?.type }}
title={file?.filename}
mode="mail"
otherUser={message?.user}
>
<MailAttachmentImg src={AttachmentMailSVG} />
<Typography
sx={{
fontSize: "16px",
transition: '0.2s all',
"&:hover": {
color: 'rgba(255, 255, 255, 0.90)',
textDecoration: 'underline'
}
}}
>
{file?.originalFilename || file?.filename}
</Typography>
</FileElement> */}
{message?.attachments?.length > 1 && isFirst && ( {message?.attachments?.length > 1 && isFirst && (
<Box <Box
sx={{ sx={{

View File

@ -1,10 +1,4 @@
import React, { import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { import {
Avatar, Avatar,
Box, Box,
@ -18,7 +12,6 @@ import {
ComposeP, ComposeP,
GroupContainer, GroupContainer,
GroupNameP, GroupNameP,
MailIconImg,
ShowMessageReturnButton, ShowMessageReturnButton,
SingleThreadParent, SingleThreadParent,
ThreadContainer, ThreadContainer,
@ -222,7 +215,7 @@ export const Thread = ({
} }
}; };
const getMailMessages = React.useCallback( const getMailMessages = useCallback(
async (groupInfo: any, before, after, isReverse, groupId) => { async (groupInfo: any, before, after, isReverse, groupId) => {
try { try {
setTempPublishedList([]); setTempPublishedList([]);
@ -328,7 +321,7 @@ export const Thread = ({
}, },
[messages, secretKey] [messages, secretKey]
); );
const getMessages = React.useCallback(async () => { const getMessages = useCallback(async () => {
if ( if (
!currentThread || !currentThread ||
(!secretKey && isPrivate) || (!secretKey && isPrivate) ||
@ -410,7 +403,7 @@ export const Thread = ({
const interval = useRef<any>(null); const interval = useRef<any>(null);
const checkNewMessages = React.useCallback( const checkNewMessages = useCallback(
async (groupInfo: any) => { async (groupInfo: any) => {
try { try {
let threadId = groupInfo.threadId; let threadId = groupInfo.threadId;
@ -494,7 +487,7 @@ export const Thread = ({
firstMount.current = true; firstMount.current = true;
}; };
React.useEffect(() => { useEffect(() => {
subscribeToEvent('threadFetchMode', threadFetchModeFunc); subscribeToEvent('threadFetchMode', threadFetchModeFunc);
return () => { return () => {
@ -656,6 +649,7 @@ export const Thread = ({
<div ref={threadBeginningRef} /> <div ref={threadBeginningRef} />
<ThreadContainer> <ThreadContainer>
<Spacer height={'30px'} /> <Spacer height={'30px'} />
<Box <Box
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
@ -715,6 +709,7 @@ export const Thread = ({
> >
{t('core:page.previous', { postProcess: 'capitalize' })} {t('core:page.previous', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
textTransformation: 'capitalize', textTransformation: 'capitalize',
@ -733,6 +728,7 @@ export const Thread = ({
> >
{t('core:page.next', { postProcess: 'capitalize' })} {t('core:page.next', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
textTransformation: 'capitalize', textTransformation: 'capitalize',
@ -1006,6 +1002,7 @@ export const Thread = ({
> >
{t('core:page.first', { postProcess: 'capitalize' })} {t('core:page.first', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
textTransformation: 'capitalize', textTransformation: 'capitalize',
@ -1024,6 +1021,7 @@ export const Thread = ({
> >
{t('core:page.previous', { postProcess: 'capitalize' })} {t('core:page.previous', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
textTransformation: 'capitalize', textTransformation: 'capitalize',
@ -1042,6 +1040,7 @@ export const Thread = ({
> >
{t('core:page.next', { postProcess: 'capitalize' })} {t('core:page.next', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
textTransformation: 'capitalize', textTransformation: 'capitalize',
@ -1061,12 +1060,14 @@ export const Thread = ({
{t('core:page.last', { postProcess: 'capitalize' })} {t('core:page.last', { postProcess: 'capitalize' })}
</Button> </Button>
</Box> </Box>
<Spacer height="30px" /> <Spacer height="30px" />
</Box> </Box>
<div ref={containerRef} /> <div ref={containerRef} />
</ThreadContainer> </ThreadContainer>
</ThreadContainerFullWidth> </ThreadContainerFullWidth>
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{

View File

@ -9,20 +9,12 @@ import {
Typography, Typography,
useTheme, useTheme,
} from '@mui/material'; } from '@mui/material';
import React, { import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { ChatGroup } from '../Chat/ChatGroup'; import { ChatGroup } from '../Chat/ChatGroup';
import { CreateCommonSecret } from '../Chat/CreateCommonSecret'; import { CreateCommonSecret } from '../Chat/CreateCommonSecret';
import { base64ToUint8Array } from '../../qdn/encryption/group-encryption'; import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
import CampaignIcon from '@mui/icons-material/Campaign';
import { AddGroup } from './AddGroup'; import { AddGroup } from './AddGroup';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import CreateIcon from '@mui/icons-material/Create'; import CreateIcon from '@mui/icons-material/Create';
import { import {
AuthenticatedContainerInnerRight, AuthenticatedContainerInnerRight,
@ -52,7 +44,6 @@ import {
import { RequestQueueWithPromise } from '../../utils/queue/queue'; import { RequestQueueWithPromise } from '../../utils/queue/queue';
import { WebSocketActive } from './WebsocketActive'; import { WebSocketActive } from './WebsocketActive';
import { useMessageQueue } from '../../MessageQueueContext'; import { useMessageQueue } from '../../MessageQueueContext';
import { ContextMenu } from '../ContextMenu';
import { HomeDesktop } from './HomeDesktop'; import { HomeDesktop } from './HomeDesktop';
import { IconWrapper } from '../Desktop/DesktopFooter'; import { IconWrapper } from '../Desktop/DesktopFooter';
import { DesktopHeader } from '../Desktop/DesktopHeader'; import { DesktopHeader } from '../Desktop/DesktopHeader';
@ -63,7 +54,6 @@ import { HubsIcon } from '../../assets/Icons/HubsIcon';
import { MessagingIcon } from '../../assets/Icons/MessagingIcon'; import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
import { formatEmailDate } from './QMailMessages'; import { formatEmailDate } from './QMailMessages';
import { AdminSpace } from '../Chat/AdminSpace'; import { AdminSpace } from '../Chat/AdminSpace';
import { import {
addressInfoControllerAtom, addressInfoControllerAtom,
groupAnnouncementsAtom, groupAnnouncementsAtom,
@ -77,9 +67,6 @@ import {
timestampEnterDataAtom, timestampEnterDataAtom,
} from '../../atoms/global'; } from '../../atoms/global';
import { sortArrayByTimestampAndGroupName } from '../../utils/time'; import { sortArrayByTimestampAndGroupName } from '../../utils/time';
import PersonOffIcon from '@mui/icons-material/PersonOff';
import LockIcon from '@mui/icons-material/Lock';
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
import { BlockedUsersModal } from './BlockedUsersModal'; import { BlockedUsersModal } from './BlockedUsersModal';
import { WalletsAppWrapper } from './WalletsAppWrapper'; import { WalletsAppWrapper } from './WalletsAppWrapper';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -92,6 +79,7 @@ export const getPublishesFromAdmins = async (admins: string[], groupId) => {
groupId groupId
}&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true`; }&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true`;
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
throw new Error('network error'); throw new Error('network error');
} }
@ -100,9 +88,11 @@ export const getPublishesFromAdmins = async (admins: string[], groupId) => {
const filterId = adminData.filter( const filterId = adminData.filter(
(data: any) => data.identifier === `symmetric-qchat-group-${groupId}` (data: any) => data.identifier === `symmetric-qchat-group-${groupId}`
); );
if (filterId?.length === 0) { if (filterId?.length === 0) {
return false; return false;
} }
const sortedData = filterId.sort((a: any, b: any) => { const sortedData = filterId.sort((a: any, b: any) => {
// Get the most recent date for both a and b // Get the most recent date for both a and b
const dateA = a.updated ? new Date(a.updated) : new Date(a.created); const dateA = a.updated ? new Date(a.updated) : new Date(a.created);
@ -114,24 +104,18 @@ export const getPublishesFromAdmins = async (admins: string[], groupId) => {
return sortedData[0]; return sortedData[0];
}; };
interface GroupProps { interface GroupProps {
myAddress: string;
isFocused: boolean;
userInfo: any;
balance: number; balance: number;
isFocused: boolean;
myAddress: string;
userInfo: any;
} }
export const timeDifferenceForNotificationChats = 900000; export const timeDifferenceForNotificationChats = 900000;
export const requestQueueMemberNames = new RequestQueueWithPromise(5); export const requestQueueMemberNames = new RequestQueueWithPromise(5);
export const requestQueueAdminMemberNames = new RequestQueueWithPromise(5); export const requestQueueAdminMemberNames = new RequestQueueWithPromise(5);
// const audio = new Audio(chrome.runtime?.getURL("msg-not1.wav"));
export const getGroupAdminsAddress = async (groupNumber: number) => { export const getGroupAdminsAddress = async (groupNumber: number) => {
// const validApi = await findUsableApi();
const response = await fetch( const response = await fetch(
`${getBaseApiReact()}/groups/members/${groupNumber}?limit=0&onlyAdmins=true` `${getBaseApiReact()}/groups/members/${groupNumber}?limit=0&onlyAdmins=true`
); );
@ -422,27 +406,24 @@ export const Group = ({
const [chatMode, setChatMode] = useState('groups'); const [chatMode, setChatMode] = useState('groups');
const [newChat, setNewChat] = useState(false); const [newChat, setNewChat] = useState(false);
const [openSnack, setOpenSnack] = React.useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = React.useState(null); const [infoSnack, setInfoSnack] = useState(null);
const [isLoadingNotifyAdmin, setIsLoadingNotifyAdmin] = React.useState(false); const [isLoadingNotifyAdmin, setIsLoadingNotifyAdmin] = useState(false);
const [isLoadingGroups, setIsLoadingGroups] = React.useState(true); const [isLoadingGroups, setIsLoadingGroups] = useState(true);
const [isLoadingGroup, setIsLoadingGroup] = React.useState(false); const [isLoadingGroup, setIsLoadingGroup] = useState(false);
const [firstSecretKeyInCreation, setFirstSecretKeyInCreation] = const [firstSecretKeyInCreation, setFirstSecretKeyInCreation] =
React.useState(false); useState(false);
const [groupSection, setGroupSection] = React.useState('home'); const [groupSection, setGroupSection] = useState('home');
const [groupAnnouncements, setGroupAnnouncements] = useAtom( const [groupAnnouncements, setGroupAnnouncements] = useAtom(
groupAnnouncementsAtom groupAnnouncementsAtom
); );
const [defaultThread, setDefaultThread] = useState(null);
const [defaultThread, setDefaultThread] = React.useState(null); const [isOpenDrawer, setIsOpenDrawer] = useState(false);
const [isOpenDrawer, setIsOpenDrawer] = React.useState(false);
const setIsOpenBlockedUserModal = useSetAtom(isOpenBlockedModalAtom); const setIsOpenBlockedUserModal = useSetAtom(isOpenBlockedModalAtom);
const [hideCommonKeyPopup, setHideCommonKeyPopup] = useState(false);
const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false); const [isLoadingGroupMessage, setIsLoadingGroupMessage] = useState('');
const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState(''); const [drawerMode, setDrawerMode] = useState('groups');
const [drawerMode, setDrawerMode] = React.useState('groups');
const setMutedGroups = useSetAtom(mutedGroupsAtom); const setMutedGroups = useSetAtom(mutedGroupsAtom);
const [mobileViewMode, setMobileViewMode] = useState('home'); const [mobileViewMode, setMobileViewMode] = useState('home');
const [mobileViewModeKeepOpen, setMobileViewModeKeepOpen] = useState(''); const [mobileViewModeKeepOpen, setMobileViewModeKeepOpen] = useState('');
const isFocusedRef = useRef(true); const isFocusedRef = useRef(true);
@ -531,7 +512,10 @@ export const Group = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' })
);
}); });
}); });
} catch (error) { } catch (error) {
@ -557,7 +541,10 @@ export const Group = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' })
);
}); });
}); });
} catch (error) { } catch (error) {
@ -586,7 +573,10 @@ export const Group = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' })
);
}); });
}); });
} catch (error) { } catch (error) {
@ -1106,7 +1096,10 @@ export const Group = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' })
);
}); });
}); });
setInfoSnack({ setInfoSnack({

View File

@ -16,7 +16,8 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
try { try {
const fee = await getFee('GROUP_INVITE'); const fee = await getFee('GROUP_INVITE');
await show({ await show({
message: t('group:question.group_invite', { message: t('group:question.perform_transaction', {
action: 'GROUP_INVITE',
postProcess: 'capitalize', postProcess: 'capitalize',
}), }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',

View File

@ -54,7 +54,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
const [bans, setBans] = useState([]); const [bans, setBans] = useState([]);
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
const listRef = useRef(); const listRef = useRef(null);
const [isLoadingUnban, setIsLoadingUnban] = useState(false); const [isLoadingUnban, setIsLoadingUnban] = useState(false);
const { t } = useTranslation(['core', 'group']); const { t } = useTranslation(['core', 'group']);
@ -88,7 +88,10 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
try { try {
const fee = await getFee('CANCEL_GROUP_BAN'); const fee = await getFee('CANCEL_GROUP_BAN');
await show({ await show({
message: t('group:question.cancel_ban', { postProcess: 'capitalize' }), message: t('group:question.perform_transaction', {
action: 'CANCEL_GROUP_BAN',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingUnban(true); setIsLoadingUnban(true);
@ -165,13 +168,13 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
> >
<Box <Box
sx={{ sx={{
width: '325px', alignItems: 'center',
height: '250px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center',
gap: '10px', gap: '10px',
height: '250px',
padding: '10px', padding: '10px',
width: '325px',
}} }}
> >
<LoadingButton <LoadingButton
@ -214,12 +217,12 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
<p>{t('group:ban_list', { postProcess: 'capitalize' })}</p> <p>{t('group:ban_list', { postProcess: 'capitalize' })}</p>
<div <div
style={{ style={{
position: 'relative',
height: '500px',
width: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexShrink: 1, flexShrink: 1,
height: '500px',
position: 'relative',
width: '100%',
}} }}
> >
<AutoSizer> <AutoSizer>

View File

@ -51,6 +51,11 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { useAtom, useSetAtom } from 'jotai'; import { useAtom, useSetAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
const THIRTY_MINUTES = 30 * 60 * 1000; // 30 minutes in milliseconds
const uid = new ShortUniqueId({ length: 8 });
export const requestQueuePromos = new RequestQueueWithPromise(3); export const requestQueuePromos = new RequestQueueWithPromise(3);
export function utf8ToBase64(inputString: string): string { export function utf8ToBase64(inputString: string): string {
@ -65,13 +70,11 @@ export function utf8ToBase64(inputString: string): string {
return base64String; return base64String;
} }
const uid = new ShortUniqueId({ length: 8 });
export function getGroupId(str) { export function getGroupId(str) {
const match = str.match(/group-(\d+)-/); const match = str.match(/group-(\d+)-/);
return match ? match[1] : null; return match ? match[1] : null;
} }
const THIRTY_MINUTES = 30 * 60 * 1000; // 30 minutes in milliseconds
export const ListOfGroupPromotions = () => { export const ListOfGroupPromotions = () => {
const [popoverAnchor, setPopoverAnchor] = useState(null); const [popoverAnchor, setPopoverAnchor] = useState(null);
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); const [openPopoverIndex, setOpenPopoverIndex] = useState(null);
@ -98,7 +101,8 @@ export const ListOfGroupPromotions = () => {
const setTxList = useSetAtom(txListAtom); const setTxList = useSetAtom(txListAtom);
const theme = useTheme(); const theme = useTheme();
const listRef = useRef(); const { t } = useTranslation(['core', 'group']);
const listRef = useRef(null);
const rowVirtualizer = useVirtualizer({ const rowVirtualizer = useVirtualizer({
count: promotions.length, count: promotions.length,
getItemKey: React.useCallback( getItemKey: React.useCallback(
@ -120,6 +124,7 @@ export const ListOfGroupPromotions = () => {
console.log(error); console.log(error);
} }
}, []); }, []);
const getPromotions = useCallback(async () => { const getPromotions = useCallback(async () => {
try { try {
setPromotionTimeInterval(Date.now()); setPromotionTimeInterval(Date.now());
@ -135,6 +140,7 @@ export const ListOfGroupPromotions = () => {
let data: any[] = []; let data: any[] = [];
const uniqueGroupIds = new Set(); const uniqueGroupIds = new Set();
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000; const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
const getPromos = responseData?.map(async (promo: any) => { const getPromos = responseData?.map(async (promo: any) => {
if (promo?.size < 200 && promo.created > oneWeekAgo) { if (promo?.size < 200 && promo.created > oneWeekAgo) {
const name = await requestQueuePromos.enqueue(async () => { const name = await requestQueuePromos.enqueue(async () => {
@ -213,6 +219,7 @@ export const ListOfGroupPromotions = () => {
setPopoverAnchor(null); setPopoverAnchor(null);
setOpenPopoverIndex(null); setOpenPopoverIndex(null);
}; };
const publishPromo = async () => { const publishPromo = async () => {
try { try {
setIsLoadingPublish(true); setIsLoadingPublish(true);
@ -235,9 +242,12 @@ export const ListOfGroupPromotions = () => {
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' })
);
}); });
}); // TODO translate });
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message:
@ -264,7 +274,10 @@ export const ListOfGroupPromotions = () => {
const groupId = group.groupId; const groupId = group.groupId;
const fee = await getFee('JOIN_GROUP'); const fee = await getFee('JOIN_GROUP');
await show({ await show({
message: 'Would you like to perform an JOIN_GROUP transaction?', message: t('group:question.perform_transaction', {
action: 'JOIN_GROUP',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingJoinGroup(true); setIsLoadingJoinGroup(true);
@ -331,6 +344,7 @@ export const ListOfGroupPromotions = () => {
}); });
setIsLoadingJoinGroup(false); setIsLoadingJoinGroup(false);
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoadingJoinGroup(false); setIsLoadingJoinGroup(false);
} }
@ -339,30 +353,30 @@ export const ListOfGroupPromotions = () => {
return ( return (
<Box <Box
sx={{ sx={{
width: '100%',
display: 'flex',
marginTop: '20px',
flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center', justifyContent: 'center',
marginTop: '20px',
width: '100%',
}} }}
> >
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
gap: '20px', gap: '20px',
width: '100%',
justifyContent: 'space-between', justifyContent: 'space-between',
width: '100%',
}} }}
> >
<ButtonBase <ButtonBase
sx={{ sx={{
alignSelf: isExpanded && 'flex-start',
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
padding: `0px ${isExpanded ? '24px' : '20px'}`,
gap: '10px', gap: '10px',
justifyContent: 'flex-start', justifyContent: 'flex-start',
alignSelf: isExpanded && 'flex-start', padding: `0px ${isExpanded ? '24px' : '20px'}`,
}} }}
onClick={() => setIsExpanded((prev) => !prev)} onClick={() => setIsExpanded((prev) => !prev)}
> >
@ -374,6 +388,7 @@ export const ListOfGroupPromotions = () => {
Group promotions{' '} Group promotions{' '}
{promotions.length > 0 && ` (${promotions.length})`} {promotions.length > 0 && ` (${promotions.length})`}
</Typography> </Typography>
{isExpanded ? ( {isExpanded ? (
<ExpandLessIcon <ExpandLessIcon
sx={{ sx={{
@ -400,19 +415,19 @@ export const ListOfGroupPromotions = () => {
<> <>
<Box <Box
sx={{ sx={{
width: '750px',
maxWidth: '90%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
maxWidth: '90%',
padding: '0px 20px', padding: '0px 20px',
width: '750px',
}} }}
> >
<Box <Box
sx={{ sx={{
width: '100%', alignItems: 'center',
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', width: '100%',
}} }}
> >
<Typography <Typography

View File

@ -18,6 +18,7 @@ import { getNameInfo } from './Group';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { getBaseApiReact } from '../../App'; import { getBaseApiReact } from '../../App';
import { useTranslation } from 'react-i18next';
export const getMemberInvites = async (groupNumber) => { export const getMemberInvites = async (groupNumber) => {
const response = await fetch( const response = await fetch(
@ -59,8 +60,8 @@ export const ListOfInvites = ({
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
const [isLoadingCancelInvite, setIsLoadingCancelInvite] = useState(false); const [isLoadingCancelInvite, setIsLoadingCancelInvite] = useState(false);
const { t } = useTranslation(['core', 'group']);
const listRef = useRef(); const listRef = useRef(null);
const getInvites = async (groupId) => { const getInvites = async (groupId) => {
try { try {
@ -90,13 +91,18 @@ export const ListOfInvites = ({
const handleCancelInvitation = async (address) => { const handleCancelInvitation = async (address) => {
try { try {
// TODO translate
const fee = await getFee('CANCEL_GROUP_INVITE'); const fee = await getFee('CANCEL_GROUP_INVITE');
await show({ await show({
message: 'Would you like to perform a CANCEL_GROUP_INVITE transaction?', message: t('group:question.perform_transaction', {
action: 'CANCEL_GROUP_INVITE',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingCancelInvite(true); setIsLoadingCancelInvite(true);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage('cancelInvitationToGroup', { .sendMessage('cancelInvitationToGroup', {
@ -107,8 +113,9 @@ export const ListOfInvites = ({
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.invitation_cancellation', {
'Successfully canceled invitation. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -126,13 +133,18 @@ export const ListOfInvites = ({
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error.message || 'An error occurred', message:
error.message ||
t('core:message.error.generic', {
postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
}); });
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoadingCancelInvite(false); setIsLoadingCancelInvite(false);
} }
@ -168,13 +180,13 @@ export const ListOfInvites = ({
> >
<Box <Box
sx={{ sx={{
width: '325px', alignItems: 'center',
height: '250px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center',
gap: '10px', gap: '10px',
height: '250px',
padding: '10px', padding: '10px',
width: '325px',
}} }}
> >
<LoadingButton <LoadingButton
@ -183,10 +195,13 @@ export const ListOfInvites = ({
variant="contained" variant="contained"
onClick={() => handleCancelInvitation(member?.invitee)} onClick={() => handleCancelInvitation(member?.invitee)}
> >
Cancel Invitation {t('core:action.cancel_invitation', {
postProcess: 'capitalize',
})}
</LoadingButton> </LoadingButton>
</Box> </Box>
</Popover> </Popover>
<ListItemButton <ListItemButton
onClick={(event) => handlePopoverOpen(event, index)} onClick={(event) => handlePopoverOpen(event, index)}
> >
@ -200,6 +215,7 @@ export const ListOfInvites = ({
} }
/> />
</ListItemAvatar> </ListItemAvatar>
<ListItemText primary={member?.name || member?.invitee} /> <ListItemText primary={member?.name || member?.invitee} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
@ -211,15 +227,19 @@ export const ListOfInvites = ({
return ( return (
<div> <div>
<p>Invitees list</p> <p>
{t('group:invitees_list', {
postProcess: 'capitalize',
})}
</p>
<div <div
style={{ style={{
position: 'relative',
height: '500px',
width: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexShrink: 1, flexShrink: 1,
height: '500px',
position: 'relative',
width: '100%',
}} }}
> >
<AutoSizer> <AutoSizer>

View File

@ -15,11 +15,12 @@ import {
List, List,
} from 'react-virtualized'; } from 'react-virtualized';
import { getNameInfo } from './Group'; import { getNameInfo } from './Group';
import { getBaseApi, getFee } from '../../background'; import { getFee } from '../../background';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { getBaseApiReact } from '../../App'; import { getBaseApiReact } from '../../App';
import { txListAtom } from '../../atoms/global'; import { txListAtom } from '../../atoms/global';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
export const getMemberInvites = async (groupNumber) => { export const getMemberInvites = async (groupNumber) => {
const response = await fetch( const response = await fetch(
@ -59,11 +60,11 @@ export const ListOfJoinRequests = ({
}) => { }) => {
const [invites, setInvites] = useState([]); const [invites, setInvites] = useState([]);
const [txList, setTxList] = useAtom(txListAtom); const [txList, setTxList] = useAtom(txListAtom);
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
const listRef = useRef(); const listRef = useRef(null);
const [isLoadingAccept, setIsLoadingAccept] = useState(false); const [isLoadingAccept, setIsLoadingAccept] = useState(false);
const { t } = useTranslation(['core', 'group']);
const getInvites = async (groupId) => { const getInvites = async (groupId) => {
try { try {
@ -93,12 +94,18 @@ export const ListOfJoinRequests = ({
const handleAcceptJoinRequest = async (address) => { const handleAcceptJoinRequest = async (address) => {
try { try {
const fee = await getFee('GROUP_INVITE'); // TODO translate const fee = await getFee('GROUP_INVITE');
await show({ await show({
message: 'Would you like to perform a GROUP_INVITE transaction?', message: t('group:question.perform_transaction', {
action: 'GROUP_INVITE',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingAccept(true); setIsLoadingAccept(true);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage('inviteToGroup', { .sendMessage('inviteToGroup', {
@ -111,19 +118,23 @@ export const ListOfJoinRequests = ({
setIsLoadingAccept(false); setIsLoadingAccept(false);
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success,group_join', {
'Successfully accepted join request. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
res(response); res(response);
setTxList((prev) => [ setTxList((prev) => [
{ {
...response, ...response,
type: 'join-request-accept', type: 'join-request-accept',
label: `Accepted join request: awaiting confirmation`, label: t('group:message.success,invitation_request', {
labelDone: `User successfully joined!`, postProcess: 'capitalize',
}),
labelDone: t('group:message.success,user_joined', {
postProcess: 'capitalize',
}),
done: false, done: false,
groupId, groupId,
qortalAddress: address, qortalAddress: address,
@ -144,13 +155,16 @@ export const ListOfJoinRequests = ({
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error?.message || 'An error occurred', message:
error?.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
}); });
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoadingAccept(false); setIsLoadingAccept(false);
} }
@ -158,13 +172,15 @@ export const ListOfJoinRequests = ({
const rowRenderer = ({ index, key, parent, style }) => { const rowRenderer = ({ index, key, parent, style }) => {
const member = invites[index]; const member = invites[index];
const findJoinRequsetInTxList = txList?.find( const findJoinRequestInTxList = txList?.find(
(tx) => (tx) =>
tx?.groupId === groupId && tx?.groupId === groupId &&
tx?.qortalAddress === member?.joiner && tx?.qortalAddress === member?.joiner &&
tx?.type === 'join-request-accept' tx?.type === 'join-request-accept'
); );
if (findJoinRequsetInTxList) return null;
if (findJoinRequestInTxList) return null;
return ( return (
<CellMeasurer <CellMeasurer
key={key} key={key}
@ -207,10 +223,11 @@ export const ListOfJoinRequests = ({
variant="contained" variant="contained"
onClick={() => handleAcceptJoinRequest(member?.joiner)} onClick={() => handleAcceptJoinRequest(member?.joiner)}
> >
Accept {t('core:action.accept', { postProcess: 'capitalize' })}
</LoadingButton> </LoadingButton>
</Box> </Box>
</Popover> </Popover>
<ListItemButton <ListItemButton
onClick={(event) => handlePopoverOpen(event, index)} onClick={(event) => handlePopoverOpen(event, index)}
> >
@ -235,7 +252,7 @@ export const ListOfJoinRequests = ({
return ( return (
<div> <div>
<p>Join request list</p> <p>{t('core:list.join_request', { postProcess: 'capitalize' })}</p>
<div <div
style={{ style={{
position: 'relative', position: 'relative',

View File

@ -19,6 +19,7 @@ import {
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { getBaseApiReact } from '../../App'; import { getBaseApiReact } from '../../App';
import { useTranslation } from 'react-i18next';
const cache = new CellMeasurerCache({ const cache = new CellMeasurerCache({
fixedWidth: true, fixedWidth: true,
@ -41,7 +42,8 @@ const ListOfMembers = ({
const [isLoadingMakeAdmin, setIsLoadingMakeAdmin] = useState(false); const [isLoadingMakeAdmin, setIsLoadingMakeAdmin] = useState(false);
const [isLoadingRemoveAdmin, setIsLoadingRemoveAdmin] = useState(false); const [isLoadingRemoveAdmin, setIsLoadingRemoveAdmin] = useState(false);
const theme = useTheme(); const theme = useTheme();
const listRef = useRef(); const { t } = useTranslation(['core', 'group']);
const listRef = useRef(null);
const handlePopoverOpen = (event, index) => { const handlePopoverOpen = (event, index) => {
setPopoverAnchor(event.currentTarget); setPopoverAnchor(event.currentTarget);
@ -57,7 +59,10 @@ const ListOfMembers = ({
try { try {
const fee = await getFee('GROUP_KICK'); const fee = await getFee('GROUP_KICK');
await show({ await show({
message: 'Would you like to perform a GROUP_KICK transaction?', message: t('group:question.perform_transaction', {
action: 'GROUP_KICK',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -72,8 +77,9 @@ const ListOfMembers = ({
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.group_kick', {
'Successfully kicked member from group. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -90,7 +96,9 @@ const ListOfMembers = ({
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error.message || 'An error occurred', message:
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
@ -104,12 +112,18 @@ const ListOfMembers = ({
}; };
const handleBan = async (address) => { const handleBan = async (address) => {
try { try {
const fee = await getFee('GROUP_BAN'); // TODO translate const fee = await getFee('GROUP_BAN');
await show({ await show({
message: 'Would you like to perform a GROUP_BAN transaction?', message: t('group:question.perform_transaction', {
action: 'GROUP_BAN',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingBan(true); setIsLoadingBan(true);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage('banFromGroup', { .sendMessage('banFromGroup', {
@ -121,8 +135,9 @@ const ListOfMembers = ({
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.group_ban', {
'Successfully banned member from group. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -139,13 +154,16 @@ const ListOfMembers = ({
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error.message || 'An error occurred', message:
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
}); });
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoadingBan(false); setIsLoadingBan(false);
} }
@ -155,7 +173,10 @@ const ListOfMembers = ({
try { try {
const fee = await getFee('ADD_GROUP_ADMIN'); const fee = await getFee('ADD_GROUP_ADMIN');
await show({ await show({
message: 'Would you like to perform a ADD_GROUP_ADMIN transaction?', message: t('group:question.perform_transaction', {
action: 'ADD_GROUP_ADMIN',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingMakeAdmin(true); setIsLoadingMakeAdmin(true);
@ -169,8 +190,9 @@ const ListOfMembers = ({
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.group_member_admin', {
'Successfully made member an admin. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -187,13 +209,16 @@ const ListOfMembers = ({
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error.message || 'An error occurred', message:
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
}); });
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoadingMakeAdmin(false); setIsLoadingMakeAdmin(false);
} }
@ -203,7 +228,10 @@ const ListOfMembers = ({
try { try {
const fee = await getFee('REMOVE_GROUP_ADMIN'); const fee = await getFee('REMOVE_GROUP_ADMIN');
await show({ await show({
message: 'Would you like to perform a REMOVE_GROUP_ADMIN transaction?', message: t('group:question.perform_transaction', {
action: 'REMOVE_GROUP_ADMIN',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingRemoveAdmin(true); setIsLoadingRemoveAdmin(true);
@ -217,8 +245,9 @@ const ListOfMembers = ({
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.group_remove_member', {
'Successfully removed member as an admin. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -235,13 +264,16 @@ const ListOfMembers = ({
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error.message || 'An error occurred', message:
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
}); });
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoadingRemoveAdmin(false); setIsLoadingRemoveAdmin(false);
} }
@ -276,13 +308,13 @@ const ListOfMembers = ({
> >
<Box <Box
sx={{ sx={{
width: '325px', alignItems: 'center',
height: '250px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center',
gap: '10px', gap: '10px',
height: '250px',
padding: '10px', padding: '10px',
width: '325px',
}} }}
> >
{isOwner && ( {isOwner && (
@ -293,48 +325,49 @@ const ListOfMembers = ({
variant="contained" variant="contained"
onClick={() => handleKick(member?.member)} onClick={() => handleKick(member?.member)}
> >
Kick member from group {t('group:action.kick_member', {
postProcess: 'capitalize',
})}
</LoadingButton> </LoadingButton>
<LoadingButton <LoadingButton
loading={isLoadingBan} loading={isLoadingBan}
loadingPosition="start" loadingPosition="start"
variant="contained" variant="contained"
onClick={() => handleBan(member?.member)} onClick={() => handleBan(member?.member)}
> >
Ban member from group {t('group:action.ban', {
postProcess: 'capitalize',
})}
</LoadingButton> </LoadingButton>
<LoadingButton <LoadingButton
loading={isLoadingMakeAdmin} loading={isLoadingMakeAdmin}
loadingPosition="start" loadingPosition="start"
variant="contained" variant="contained"
onClick={() => makeAdmin(member?.member)} onClick={() => makeAdmin(member?.member)}
> >
Make an admin {t('group:action.make_admin', {
postProcess: 'capitalize',
})}
</LoadingButton> </LoadingButton>
<LoadingButton <LoadingButton
loading={isLoadingRemoveAdmin} loading={isLoadingRemoveAdmin}
loadingPosition="start" loadingPosition="start"
variant="contained" variant="contained"
onClick={() => removeAdmin(member?.member)} onClick={() => removeAdmin(member?.member)}
> >
Remove as admin {t('group:action.remove_admin', {
postProcess: 'capitalize',
})}
</LoadingButton> </LoadingButton>
</> </>
)} )}
</Box> </Box>
</Popover> </Popover>
<ListItem
key={member?.member} <ListItem key={member?.member} disablePadding>
// secondaryAction={
// <Checkbox
// edge="end"
// onChange={handleToggle(value)}
// checked={checked.indexOf(value) !== -1}
// inputProps={{ 'aria-labelledby': labelId }}
// />
// }
disablePadding
>
<ListItemButton <ListItemButton
onClick={(event) => handlePopoverOpen(event, index)} onClick={(event) => handlePopoverOpen(event, index)}
> >
@ -348,6 +381,7 @@ const ListOfMembers = ({
} }
/> />
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
id={''} id={''}
primary={member?.name || member?.member} primary={member?.name || member?.member}
@ -359,7 +393,9 @@ const ListOfMembers = ({
marginLeft: 'auto', marginLeft: 'auto',
}} }}
> >
Admin {t('core:admin', {
postProcess: 'capitalize',
})}
</Typography> </Typography>
)} )}
</ListItemButton> </ListItemButton>
@ -372,28 +408,31 @@ const ListOfMembers = ({
return ( return (
<div> <div>
<p>Member list</p> <p>
{t('core:list.member', {
postProcess: 'capitalize',
})}
</p>
<div <div
style={{ style={{
position: 'relative',
height: '500px',
width: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexShrink: 1, flexShrink: 1,
height: '500px',
position: 'relative',
width: '100%',
}} }}
> >
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<List <List
ref={listRef} deferredMeasurementCache={cache}
width={width}
height={height} height={height}
ref={listRef}
rowCount={members.length} rowCount={members.length}
rowHeight={cache.rowHeight} rowHeight={cache.rowHeight}
rowRenderer={rowRenderer} rowRenderer={rowRenderer}
// onScroll={handleScroll} width={width}
deferredMeasurementCache={cache}
/> />
)} )}
</AutoSizer> </AutoSizer>

View File

@ -1,4 +1,4 @@
import * as React from 'react'; import { useEffect, useState } from 'react';
import List from '@mui/material/List'; import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem'; import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton'; import ListItemButton from '@mui/material/ListItemButton';
@ -9,10 +9,12 @@ import { Box, Typography } from '@mui/material';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { CustomLoader } from '../../common/CustomLoader'; import { CustomLoader } from '../../common/CustomLoader';
import VisibilityIcon from '@mui/icons-material/Visibility'; import VisibilityIcon from '@mui/icons-material/Visibility';
import { useTranslation } from 'react-i18next';
export const ListOfThreadPostsWatched = () => { export const ListOfThreadPostsWatched = () => {
const [posts, setPosts] = React.useState([]); const [posts, setPosts] = useState([]);
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = useState(true);
const { t } = useTranslation(['core', 'group']);
const getPosts = async () => { const getPosts = async () => {
try { try {
@ -42,34 +44,38 @@ export const ListOfThreadPostsWatched = () => {
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); // TODO translate rej(
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' })
);
}); });
}); });
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
React.useEffect(() => { useEffect(() => {
getPosts(); getPosts();
}, []); }, []);
return ( return (
<Box <Box
sx={{ sx={{
width: '100%', alignItems: 'center',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', width: '100%',
}} }}
> >
<Box <Box
sx={{ sx={{
width: '322px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
padding: '0px 20px', padding: '0px 20px',
width: '322px',
}} }}
> >
<Typography <Typography
@ -78,8 +84,12 @@ export const ListOfThreadPostsWatched = () => {
fontWeight: 600, fontWeight: 600,
}} }}
> >
New Thread Posts: {t('group:thread_posts', {
postProcess: 'capitalize',
})}
:
</Typography> </Typography>
<Spacer height="10px" /> <Spacer height="10px" />
</Box> </Box>
@ -97,9 +107,9 @@ export const ListOfThreadPostsWatched = () => {
{loading && posts.length === 0 && ( {loading && posts.length === 0 && (
<Box <Box
sx={{ sx={{
width: '100%',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
width: '100%',
}} }}
> >
<CustomLoader /> <CustomLoader />
@ -108,11 +118,11 @@ export const ListOfThreadPostsWatched = () => {
{!loading && posts.length === 0 && ( {!loading && posts.length === 0 && (
<Box <Box
sx={{ sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
display: 'flex',
height: '100%', height: '100%',
justifyContent: 'center',
width: '100%',
}} }}
> >
<Typography <Typography
@ -122,7 +132,9 @@ export const ListOfThreadPostsWatched = () => {
color: 'rgba(255, 255, 255, 0.2)', color: 'rgba(255, 255, 255, 0.2)',
}} }}
> >
Nothing to display {t('group:message.generic.no_display', {
postProcess: 'capitalize',
})}
</Typography> </Typography>
</Box> </Box>
)} )}
@ -130,11 +142,11 @@ export const ListOfThreadPostsWatched = () => {
<List <List
className="scrollable-container" className="scrollable-container"
sx={{ sx={{
width: '100%',
maxWidth: 360,
bgcolor: 'background.paper', bgcolor: 'background.paper',
maxHeight: '300px', maxHeight: '300px',
maxWidth: 360,
overflow: 'auto', overflow: 'auto',
width: '100%',
}} }}
> >
{posts?.map((post) => { {posts?.map((post) => {

View File

@ -1,4 +1,14 @@
import * as React from 'react'; import {
forwardRef,
Fragment,
ReactElement,
Ref,
SyntheticEvent,
useCallback,
useContext,
useEffect,
useState,
} from 'react';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';
import AppBar from '@mui/material/AppBar'; import AppBar from '@mui/material/AppBar';
@ -25,6 +35,7 @@ import { Spacer } from '../../common/Spacer';
import InsertLinkIcon from '@mui/icons-material/InsertLink'; import InsertLinkIcon from '@mui/icons-material/InsertLink';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { txListAtom } from '../../atoms/global'; import { txListAtom } from '../../atoms/global';
import { useTranslation } from 'react-i18next';
function a11yProps(index: number) { function a11yProps(index: number) {
return { return {
@ -33,37 +44,35 @@ function a11yProps(index: number) {
}; };
} }
const Transition = React.forwardRef(function Transition( const Transition = forwardRef(function Transition(
props: TransitionProps & { props: TransitionProps & {
children: React.ReactElement; children: ReactElement;
}, },
ref: React.Ref<unknown> ref: Ref<unknown>
) { ) {
return <Slide direction="up" ref={ref} {...props} />; return <Slide direction="up" ref={ref} {...props} />;
}); });
export const ManageMembers = ({ export const ManageMembers = ({
address,
open, open,
setOpen, setOpen,
selectedGroup, selectedGroup,
isAdmin, isAdmin,
isOwner, isOwner,
}) => { }) => {
const [membersWithNames, setMembersWithNames] = React.useState([]); const [membersWithNames, setMembersWithNames] = useState([]);
const [tab, setTab] = React.useState('create'); const [value, setValue] = useState(0);
const [value, setValue] = React.useState(0); const [openSnack, setOpenSnack] = useState(false);
const [openSnack, setOpenSnack] = React.useState(false); const [infoSnack, setInfoSnack] = useState(null);
const [infoSnack, setInfoSnack] = React.useState(null); const [isLoadingMembers, setIsLoadingMembers] = useState(false);
const [isLoadingMembers, setIsLoadingMembers] = React.useState(false); const [isLoadingLeave, setIsLoadingLeave] = useState(false);
const [isLoadingLeave, setIsLoadingLeave] = React.useState(false); const [groupInfo, setGroupInfo] = useState(null);
const [groupInfo, setGroupInfo] = React.useState(null); const handleChange = (event: SyntheticEvent, newValue: number) => {
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue); setValue(newValue);
}; };
const theme = useTheme(); const theme = useTheme();
const { show } = React.useContext(MyContext); const { t } = useTranslation(['core', 'group']);
const { show } = useContext(MyContext);
const setTxList = useSetAtom(txListAtom); const setTxList = useSetAtom(txListAtom);
const handleClose = () => { const handleClose = () => {
@ -75,7 +84,10 @@ export const ManageMembers = ({
setIsLoadingLeave(true); setIsLoadingLeave(true);
const fee = await getFee('LEAVE_GROUP'); const fee = await getFee('LEAVE_GROUP');
await show({ await show({
message: 'Would you like to perform an LEAVE_GROUP transaction?', message: t('group:question.perform_transaction', {
action: 'LEAVE_GROUP',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -90,8 +102,14 @@ export const ManageMembers = ({
{ {
...response, ...response,
type: 'leave-group', type: 'leave-group',
label: `Left Group ${selectedGroup?.groupName}: awaiting confirmation`, label: t('group:message.success.group_leave_name', {
labelDone: `Left Group ${selectedGroup?.groupName}: success!`, group_name: selectedGroup?.groupName,
postProcess: 'capitalize',
}),
labelDone: t('group:message.success.group_leave_label', {
group_name: selectedGroup?.groupName,
postProcess: 'capitalize',
}),
done: false, done: false,
groupId: selectedGroup?.groupId, groupId: selectedGroup?.groupId,
}, },
@ -100,8 +118,9 @@ export const ManageMembers = ({
res(response); res(response);
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.group_leave', {
'Successfully requested to leave group. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
return; return;
@ -109,7 +128,10 @@ export const ManageMembers = ({
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); // TODO translate rej(
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' })
);
}); });
}); });
} catch (error) { } catch (error) {
@ -119,7 +141,7 @@ export const ManageMembers = ({
} }
}; };
const getMembersWithNames = React.useCallback(async (groupId) => { const getMembersWithNames = useCallback(async (groupId) => {
try { try {
setIsLoadingMembers(true); setIsLoadingMembers(true);
const res = await getGroupMembers(groupId); const res = await getGroupMembers(groupId);
@ -139,6 +161,7 @@ export const ManageMembers = ({
console.log(error); console.log(error);
} }
}; };
const getGroupInfo = async (groupId) => { const getGroupInfo = async (groupId) => {
try { try {
const response = await fetch(`${getBaseApiReact()}/groups/${groupId}`); const response = await fetch(`${getBaseApiReact()}/groups/${groupId}`);
@ -149,7 +172,7 @@ export const ManageMembers = ({
} }
}; };
React.useEffect(() => { useEffect(() => {
if (selectedGroup?.groupId) { if (selectedGroup?.groupId) {
getMembers(selectedGroup?.groupId); getMembers(selectedGroup?.groupId);
getGroupInfo(selectedGroup?.groupId); getGroupInfo(selectedGroup?.groupId);
@ -160,7 +183,7 @@ export const ManageMembers = ({
setValue(4); setValue(4);
}; };
React.useEffect(() => { useEffect(() => {
subscribeToEvent('openGroupJoinRequest', openGroupJoinRequestFunc); subscribeToEvent('openGroupJoinRequest', openGroupJoinRequestFunc);
return () => { return () => {
@ -169,7 +192,7 @@ export const ManageMembers = ({
}, []); }, []);
return ( return (
<React.Fragment> <Fragment>
<Dialog <Dialog
fullScreen fullScreen
open={open} open={open}
@ -184,18 +207,20 @@ export const ManageMembers = ({
> >
<Toolbar> <Toolbar>
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div"> <Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
Manage Members {t('group:action.manage_members', { postProcess: 'capitalize' })}
</Typography> </Typography>
<IconButton <IconButton
edge="start"
color="inherit"
onClick={handleClose}
aria-label="close" aria-label="close"
color="inherit"
edge="start"
onClick={handleClose}
> >
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Box <Box
sx={{ sx={{
bgcolor: theme.palette.background.default, bgcolor: theme.palette.background.default,
@ -284,12 +309,19 @@ export const ManageMembers = ({
}} }}
> >
<Box> <Box>
<Typography>GroupId: {groupInfo?.groupId}</Typography> <Typography>
{t('group:group.id', { postProcess: 'capitalize' })}:{' '}
<Typography>GroupName: {groupInfo?.groupName}</Typography> {groupInfo?.groupId}
</Typography>
<Typography> <Typography>
Number of members: {groupInfo?.memberCount} {t('group:group.name', { postProcess: 'capitalize' })}:{' '}
{groupInfo?.groupName}
</Typography>
<Typography>
{t('group:group.member_number', { postProcess: 'capitalize' })}:{' '}
{groupInfo?.memberCount}
</Typography> </Typography>
<ButtonBase <ButtonBase
@ -301,7 +333,11 @@ export const ManageMembers = ({
await navigator.clipboard.writeText(link); await navigator.clipboard.writeText(link);
}} }}
> >
<InsertLinkIcon /> <Typography>Join Group Link</Typography> <InsertLinkIcon />
<Typography>
{t('group:join_link', { postProcess: 'capitalize' })}
</Typography>
</ButtonBase> </ButtonBase>
</Box> </Box>
@ -315,10 +351,11 @@ export const ManageMembers = ({
variant="contained" variant="contained"
onClick={handleLeaveGroup} onClick={handleLeaveGroup}
> >
Leave Group {t('group:action.leave_group', { postProcess: 'capitalize' })}
</LoadingButton> </LoadingButton>
)} )}
</Card> </Card>
{value === 0 && ( {value === 0 && (
<Box <Box
sx={{ sx={{
@ -331,7 +368,7 @@ export const ManageMembers = ({
variant="contained" variant="contained"
onClick={() => getMembersWithNames(selectedGroup?.groupId)} onClick={() => getMembersWithNames(selectedGroup?.groupId)}
> >
Load members with names {t('group:action.load_members', { postProcess: 'capitalize' })}
</Button> </Button>
<Spacer height="10px" /> <Spacer height="10px" />
@ -347,6 +384,7 @@ export const ManageMembers = ({
/> />
</Box> </Box>
)} )}
{value === 1 && ( {value === 1 && (
<Box <Box
sx={{ sx={{
@ -426,10 +464,12 @@ export const ManageMembers = ({
<LoadingSnackbar <LoadingSnackbar
open={isLoadingMembers} open={isLoadingMembers}
info={{ info={{
message: 'Loading member list with names... please wait.', message: t('group:message.generic.loading_members', {
postProcess: 'capitalize',
}),
}} }}
/> />
</Dialog> </Dialog>
</React.Fragment> </Fragment>
); );
}; };

View File

@ -11,12 +11,12 @@ import MailIcon from '@mui/icons-material/Mail';
import MailOutlineIcon from '@mui/icons-material/MailOutline'; import MailOutlineIcon from '@mui/icons-material/MailOutline';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { CustomLoader } from '../../common/CustomLoader'; import { CustomLoader } from '../../common/CustomLoader';
import { mailsAtom, qMailLastEnteredTimestampAtom } from '../../atoms/global'; import { mailsAtom, qMailLastEnteredTimestampAtom } from '../../atoms/global';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import MarkEmailUnreadIcon from '@mui/icons-material/MarkEmailUnread'; import MarkEmailUnreadIcon from '@mui/icons-material/MarkEmailUnread';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
export const isLessThanOneWeekOld = (timestamp) => { export const isLessThanOneWeekOld = (timestamp) => {
// Current time in milliseconds // Current time in milliseconds
@ -54,6 +54,7 @@ export const QMailMessages = ({ userName, userAddress }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'group']);
const getMails = useCallback(async () => { const getMails = useCallback(async () => {
try { try {
@ -89,7 +90,10 @@ export const QMailMessages = ({ userName, userAddress }) => {
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); // TODO translate rej(
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' })
);
}); });
}); });
} catch (error) { } catch (error) {
@ -129,20 +133,20 @@ export const QMailMessages = ({ userName, userAddress }) => {
return ( return (
<Box <Box
sx={{ sx={{
width: '100%', alignItems: 'center',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', width: '100%',
}} }}
> >
<ButtonBase <ButtonBase
sx={{ sx={{
width: '322px',
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
gap: '10px', gap: '10px',
padding: '0px 20px',
justifyContent: 'flex-start', justifyContent: 'flex-start',
padding: '0px 20px',
width: '322px',
}} }}
onClick={() => setIsExpanded((prev) => !prev)} onClick={() => setIsExpanded((prev) => !prev)}
> >
@ -151,8 +155,9 @@ export const QMailMessages = ({ userName, userAddress }) => {
fontSize: '1rem', fontSize: '1rem',
}} }}
> >
Latest Q-Mails {t('group:latest_mails', { postProcess: 'capitalize' })}
</Typography> </Typography>
<MarkEmailUnreadIcon <MarkEmailUnreadIcon
sx={{ sx={{
color: anyUnread color: anyUnread
@ -195,9 +200,9 @@ export const QMailMessages = ({ userName, userAddress }) => {
{loading && mails.length === 0 && ( {loading && mails.length === 0 && (
<Box <Box
sx={{ sx={{
width: '100%',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
width: '100%',
}} }}
> >
<CustomLoader /> <CustomLoader />
@ -206,11 +211,11 @@ export const QMailMessages = ({ userName, userAddress }) => {
{!loading && mails.length === 0 && ( {!loading && mails.length === 0 && (
<Box <Box
sx={{ sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
display: 'flex',
height: '100%', height: '100%',
justifyContent: 'center',
width: '100%',
}} }}
> >
<Typography <Typography
@ -220,7 +225,9 @@ export const QMailMessages = ({ userName, userAddress }) => {
color: theme.palette.primary, color: theme.palette.primary,
}} }}
> >
Nothing to display {t('group:message.generic.no_display', {
postProcess: 'capitalize',
})}
</Typography> </Typography>
</Box> </Box>
)} )}

View File

@ -20,7 +20,6 @@ import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import { import {
Box, Box,
Button, Button,
ButtonBase,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogContentText, DialogContentText,
@ -32,7 +31,6 @@ import {
useTheme, useTheme,
} from '@mui/material'; } from '@mui/material';
import { enabledDevModeAtom } from '../../atoms/global'; import { enabledDevModeAtom } from '../../atoms/global';
import ThemeManager from '../Theme/ThemeManager'; import ThemeManager from '../Theme/ThemeManager';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { decryptStoredWallet } from '../../utils/decryptWallet'; import { decryptStoredWallet } from '../../utils/decryptWallet';
@ -41,6 +39,7 @@ import PhraseWallet from '../../utils/generateWallet/phrase-wallet';
import { walletVersion } from '../../background'; import { walletVersion } from '../../background';
import Base58 from '../../deps/Base58'; import Base58 from '../../deps/Base58';
import { MyContext } from '../../App'; import { MyContext } from '../../App';
import { useTranslation } from 'react-i18next';
const LocalNodeSwitch = styled(Switch)(({ theme }) => ({ const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
padding: 8, padding: 8,
@ -87,8 +86,8 @@ const Transition = forwardRef(function Transition(
export const Settings = ({ open, setOpen, rawWallet }) => { export const Settings = ({ open, setOpen, rawWallet }) => {
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false);
const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom); const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'group']);
const handleChange = (event: ChangeEvent<HTMLInputElement>) => { const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setChecked(event.target.checked); setChecked(event.target.checked);
@ -103,7 +102,7 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
if (response?.error) { if (response?.error) {
console.error('Error adding user settings:', response.error); console.error('Error adding user settings:', response.error);
} else { } else {
console.log('User settings added successfully'); // TODO translate console.log('User settings added successfully');
} }
}) })
.catch((error) => { .catch((error) => {
@ -134,7 +133,10 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || 'An error occurred'); rej(
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' })
);
}); });
}); });
} catch (error) { } catch (error) {
@ -157,7 +159,9 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
<AppBar sx={{ position: 'relative' }}> <AppBar sx={{ position: 'relative' }}>
<Toolbar> <Toolbar>
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div"> <Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
General Settings {t('core:general_settings', {
postProcess: 'capitalize',
})}
</Typography> </Typography>
<IconButton <IconButton
@ -173,13 +177,13 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
<Box <Box
sx={{ sx={{
flexGrow: 1,
overflowY: 'auto',
color: theme.palette.text.primary, color: theme.palette.text.primary,
padding: '20px',
flexDirection: 'column',
display: 'flex', display: 'flex',
flexDirection: 'column',
flexGrow: 1,
gap: '20px', gap: '20px',
overflowY: 'auto',
padding: '20px',
}} }}
> >
<FormControlLabel <FormControlLabel
@ -189,7 +193,9 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
control={ control={
<LocalNodeSwitch checked={checked} onChange={handleChange} /> <LocalNodeSwitch checked={checked} onChange={handleChange} />
} }
label="Disable all push notifications" label={t('group:action.disable_push_notifications', {
postProcess: 'capitalize',
})}
/> />
{window?.electronAPI && ( {window?.electronAPI && (
<FormControlLabel <FormControlLabel
@ -205,7 +211,9 @@ export const Settings = ({ open, setOpen, rawWallet }) => {
}} }}
/> />
} }
label="Enable dev mode" label={t('group:action.enable_dev_mode', {
postProcess: 'capitalize',
})}
/> />
)} )}
{isEnabledDevMode && <ExportPrivateKey rawWallet={rawWallet} />} {isEnabledDevMode && <ExportPrivateKey rawWallet={rawWallet} />}
@ -220,13 +228,16 @@ const ExportPrivateKey = ({ rawWallet }) => {
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [privateKey, setPrivateKey] = useState(''); const [privateKey, setPrivateKey] = useState('');
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { setOpenSnackGlobal, setInfoSnackCustom } = useContext(MyContext); const { setOpenSnackGlobal, setInfoSnackCustom } = useContext(MyContext);
const { t } = useTranslation(['core', 'group']);
const exportPrivateKeyFunc = async () => { const exportPrivateKeyFunc = async () => {
try { try {
setInfoSnackCustom({ setInfoSnackCustom({
type: 'info', type: 'info',
message: 'Decrypting wallet...', message: t('group:message.generic.descrypt_wallet', {
postProcess: 'capitalize',
}),
}); });
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
@ -247,13 +258,19 @@ const ExportPrivateKey = ({ rawWallet }) => {
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: error?.message message: error?.message
? `Error decrypting wallet: ${error?.message}` ? t('group:message.error.decrypt_wallet', {
: 'Error decrypting wallet', errorMessage: error?.message,
postProcess: 'capitalize',
})
: t('group:message.error.descrypt_wallet', {
postProcess: 'capitalize',
}),
}); });
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
} }
}; };
return ( return (
<> <>
<Button <Button
@ -263,14 +280,22 @@ const ExportPrivateKey = ({ rawWallet }) => {
}} }}
onClick={() => setIsOpen(true)} onClick={() => setIsOpen(true)}
> >
Export private key {t('group:action.export_private_key', {
postProcess: 'capitalize',
})}
</Button> </Button>
<Dialog <Dialog
open={isOpen} open={isOpen}
aria-labelledby="alert-dialog-title" aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle id="alert-dialog-title">Export password</DialogTitle> <DialogTitle id="alert-dialog-title">
{t('group:action.export_password', {
postProcess: 'capitalize',
})}
</DialogTitle>
<DialogContent <DialogContent
sx={{ sx={{
flexDirection: 'column', flexDirection: 'column',
@ -279,9 +304,13 @@ const ExportPrivateKey = ({ rawWallet }) => {
}} }}
> >
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Keep your private key in a secure place. Do not share! {t('group:message.generic.secure_place', {
postProcess: 'capitalize',
})}
</DialogContentText> </DialogContentText>
<Spacer height="20px" /> <Spacer height="20px" />
<TextField <TextField
autoFocus autoFocus
type="password" type="password"
@ -296,17 +325,22 @@ const ExportPrivateKey = ({ rawWallet }) => {
navigator.clipboard.writeText(privateKey); navigator.clipboard.writeText(privateKey);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'success', type: 'success',
message: 'Copied privated key', message: t('group:message.generic.private_key_copied', {
postProcess: 'capitalize',
}),
}); });
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
}} }}
> >
{`Copy private key `} {t('group:action.copy_private_key', {
postProcess: 'capitalize',
})}{' '}
<ContentCopyIcon color="primary" /> <ContentCopyIcon color="primary" />
</Button> </Button>
)} )}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button
variant="contained" variant="contained"
@ -316,10 +350,15 @@ const ExportPrivateKey = ({ rawWallet }) => {
setPrivateKey(''); setPrivateKey('');
}} }}
> >
Cancel {t('group:action.cancel', {
postProcess: 'capitalize',
})}
</Button> </Button>
<Button variant="contained" onClick={exportPrivateKeyFunc}> <Button variant="contained" onClick={exportPrivateKeyFunc}>
Decrypt {t('group:action.decrypt', {
postProcess: 'capitalize',
})}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -1,4 +1,4 @@
import * as React from 'react'; import { useEffect, useMemo, useState } from 'react';
import List from '@mui/material/List'; import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem'; import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton'; import ListItemButton from '@mui/material/ListItemButton';
@ -17,27 +17,27 @@ export const ThingsToDoInitial = ({
balance, balance,
userInfo, userInfo,
}) => { }) => {
const [checked1, setChecked1] = React.useState(false); const [checked1, setChecked1] = useState(false);
const [checked2, setChecked2] = React.useState(false); const [checked2, setChecked2] = useState(false);
const { t } = useTranslation(['core', 'tutorial']); const { t } = useTranslation(['core', 'tutorial']);
const theme = useTheme(); const theme = useTheme();
React.useEffect(() => { useEffect(() => {
if (balance && +balance >= 6) { if (balance && +balance >= 6) {
setChecked1(true); setChecked1(true);
} }
}, [balance]); }, [balance]);
React.useEffect(() => { useEffect(() => {
if (name) setChecked2(true); if (name) setChecked2(true);
}, [name]); }, [name]);
const isLoaded = React.useMemo(() => { const isLoaded = useMemo(() => {
if (userInfo !== null) return true; if (userInfo !== null) return true;
return false; return false;
}, [userInfo]); }, [userInfo]);
const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(() => { const hasDoneNameAndBalanceAndIsLoaded = useMemo(() => {
if (isLoaded && checked1 && checked2) return true; if (isLoaded && checked1 && checked2) return true;
return false; return false;
}, [checked1, isLoaded, checked2]); }, [checked1, isLoaded, checked2]);
@ -55,18 +55,18 @@ export const ThingsToDoInitial = ({
return ( return (
<Box <Box
sx={{ sx={{
width: '100%', alignItems: 'center',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', width: '100%',
}} }}
> >
<Box <Box
sx={{ sx={{
width: '322px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
padding: '0px 20px', padding: '0px 20px',
width: '322px',
}} }}
> >
<Typography <Typography
@ -125,6 +125,7 @@ export const ThingsToDoInitial = ({
postProcess: 'capitalize', postProcess: 'capitalize',
})} })}
/> />
<ListItemIcon <ListItemIcon
sx={{ sx={{
justifyContent: 'flex-end', justifyContent: 'flex-end',
@ -144,6 +145,7 @@ export const ThingsToDoInitial = ({
</ListItemIcon> </ListItemIcon>
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
<ListItem <ListItem
sx={{ sx={{
marginBottom: '20px', marginBottom: '20px',

View File

@ -22,6 +22,7 @@ import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmail
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { txListAtom } from '../../atoms/global'; import { txListAtom } from '../../atoms/global';
import { useTranslation } from 'react-i18next';
const cache = new CellMeasurerCache({ const cache = new CellMeasurerCache({
fixedWidth: true, fixedWidth: true,
@ -60,9 +61,10 @@ export const UserListOfInvites = ({
const [invites, setInvites] = useState<any[]>([]); const [invites, setInvites] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(['core', 'group']);
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
const listRef = useRef(); const listRef = useRef(null);
const getRequests = async () => { const getRequests = async () => {
try { try {
@ -94,9 +96,13 @@ export const UserListOfInvites = ({
const handleJoinGroup = async (groupId, groupName) => { const handleJoinGroup = async (groupId, groupName) => {
try { try {
const fee = await getFee('JOIN_GROUP'); // TODO translate const fee = await getFee('JOIN_GROUP');
await show({ await show({
message: 'Would you like to perform an JOIN_GROUP transaction?', message: t('group:question.perform_transaction', {
action: 'JOIN_GROUP',
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -123,8 +129,9 @@ export const UserListOfInvites = ({
res(response); res(response);
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.group_join', {
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
handlePopoverClose(); handlePopoverClose();
@ -140,13 +147,16 @@ export const UserListOfInvites = ({
.catch((error) => { .catch((error) => {
setInfoSnack({ setInfoSnack({
type: 'error', type: 'error',
message: error.message || 'An error occurred', message:
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }),
}); });
setOpenSnack(true); setOpenSnack(true);
rej(error); rej(error);
}); });
}); });
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@ -182,16 +192,22 @@ export const UserListOfInvites = ({
> >
<Box <Box
sx={{ sx={{
width: '325px', alignItems: 'center',
height: '250px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center',
gap: '10px', gap: '10px',
height: '250px',
padding: '10px', padding: '10px',
width: '325px',
}} }}
> >
<Typography>Join {invite?.groupName}</Typography> <Typography>
{t('core:action.join', {
postProcess: 'capitalize',
})}{' '}
{invite?.groupName}
</Typography>
<LoadingButton <LoadingButton
loading={isLoading} loading={isLoading}
loadingPosition="start" loadingPosition="start"
@ -200,10 +216,13 @@ export const UserListOfInvites = ({
handleJoinGroup(invite?.groupId, invite?.groupName) handleJoinGroup(invite?.groupId, invite?.groupName)
} }
> >
Join group {t('group:action.join_group', {
postProcess: 'capitalize',
})}
</LoadingButton> </LoadingButton>
</Box> </Box>
</Popover> </Popover>
<ListItemButton <ListItemButton
onClick={(event) => handlePopoverOpen(event, index)} onClick={(event) => handlePopoverOpen(event, index)}
> >
@ -221,7 +240,9 @@ export const UserListOfInvites = ({
}} }}
/> />
)} )}
<Spacer width="15px" /> <Spacer width="15px" />
<ListItemText <ListItemText
primary={invite?.groupName} primary={invite?.groupName}
secondary={invite?.description} secondary={invite?.description}
@ -242,14 +263,19 @@ export const UserListOfInvites = ({
flexGrow: 1, flexGrow: 1,
}} }}
> >
<p>Invite list</p> <p>
{t('core:list.invite', {
postProcess: 'capitalize',
})}
</p>
<div <div
style={{ style={{
position: 'relative',
width: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexGrow: 1, flexGrow: 1,
position: 'relative',
width: '100%',
}} }}
> >
<AutoSizer> <AutoSizer>

View File

@ -1,5 +1,5 @@
import { Box, ButtonBase, Divider, Typography, useTheme } from '@mui/material';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Box, ButtonBase, Divider, Typography, useTheme } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close'; import CloseIcon from '@mui/icons-material/Close';
import AppViewerContainer from '../Apps/AppViewerContainer'; import AppViewerContainer from '../Apps/AppViewerContainer';
import { import {
@ -19,7 +19,6 @@ export const WalletsAppWrapper = () => {
const [navigationController, setNavigationController] = useAtom( const [navigationController, setNavigationController] = useAtom(
navigationControllerAtom navigationControllerAtom
); );
const [selectedTab, setSelectedTab] = useState({ const [selectedTab, setSelectedTab] = useState({
tabId: '5558589', tabId: '5558589',
name: 'Q-Wallets', name: 'Q-Wallets',
@ -60,17 +59,17 @@ export const WalletsAppWrapper = () => {
{isOpen && ( {isOpen && (
<Box <Box
sx={{ sx={{
position: 'fixed', backgroundColor: theme.palette.background.paper,
height: '100vh',
width: '100vw',
backgroundColor: theme.palette.background.paper, // TODO: set color theme
zIndex: 100,
bottom: 0,
right: 0,
overflow: 'hidden',
borderTopLeftRadius: '10px', borderTopLeftRadius: '10px',
borderTopRightRadius: '10px', borderTopRightRadius: '10px',
bottom: 0,
boxShadow: 4, boxShadow: 4,
height: '100vh',
overflow: 'hidden',
position: 'fixed',
right: 0,
width: '100vw',
zIndex: 100,
}} }}
> >
<Box <Box
@ -85,11 +84,11 @@ export const WalletsAppWrapper = () => {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
padding: '5px', padding: '5px',
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
> >
<Typography>Q-Wallets</Typography> <Typography>Q-Wallets</Typography>
<ButtonBase onClick={handleClose}> <ButtonBase onClick={handleClose}>
<CloseIcon <CloseIcon
sx={{ sx={{
@ -108,6 +107,7 @@ export const WalletsAppWrapper = () => {
ref={iframeRef} ref={iframeRef}
skipAuth={true} skipAuth={true}
/> />
<AppsNavBarParent> <AppsNavBarParent>
<AppsNavBarLeft <AppsNavBarLeft
sx={{ sx={{
@ -126,6 +126,7 @@ export const WalletsAppWrapper = () => {
> >
<NavBack /> <NavBack />
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
if (selectedTab?.refreshFunc) { if (selectedTab?.refreshFunc) {

View File

@ -164,6 +164,7 @@ export const useBlockedAddresses = () => {
}); });
}); });
} }
if (address) { if (address) {
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window

View File

@ -1,4 +1,4 @@
import React, { useCallback, useContext, useEffect, useState } from 'react'; import { useCallback, useContext, useEffect, useState } from 'react';
import Logo2 from '../assets/svgs/Logo2.svg'; import Logo2 from '../assets/svgs/Logo2.svg';
import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App'; import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App';
import { import {

View File

@ -36,7 +36,7 @@ export const NewUsersCTA = ({ balance }) => {
textAlign: 'center', textAlign: 'center',
}} }}
> >
{t('core:new_user', { postProcess: 'capitalize' })} {t('core:question.new_user', { postProcess: 'capitalize' })}
</Typography> </Typography>
<Spacer height="20px" /> <Spacer height="20px" />

View File

@ -168,6 +168,7 @@ export const QortPrice = () => {
)} )}
</Box> </Box>
</Tooltip> </Tooltip>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -198,6 +199,7 @@ export const QortPrice = () => {
</Typography> </Typography>
)} )}
</Box> </Box>
<Tooltip <Tooltip
title={ title={
<span style={{ fontSize: '14px', fontWeight: 700 }}> <span style={{ fontSize: '14px', fontWeight: 700 }}>

View File

@ -1,9 +1,9 @@
import React from 'react' import { Box, CircularProgress } from '@mui/material';
import { Box, CircularProgress } from "@mui/material";
export const Loader = () => { export const Loader = () => {
return ( return (
<Box sx={{ <Box
sx={{
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
@ -11,13 +11,14 @@ export const Loader = () => {
height: '100%', height: '100%',
position: 'fixed', position: 'fixed',
top: '0px', top: '0px',
left:'0px', left: '0px',
right: '0px', right: '0px',
bottom: '0px', bottom: '0px',
zIndex: 10, zIndex: 10,
background: 'rgba(0, 0, 0, 0.4)' background: 'rgba(0, 0, 0, 0.4)',
}}> }}
<CircularProgress color="success" size={25} /> >
<CircularProgress color="success" size={25} />
</Box> </Box>
) );
} };

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react'; import { useContext, useEffect, useState } from 'react';
import Logo2 from '../assets/svgs/Logo2.svg'; import Logo2 from '../assets/svgs/Logo2.svg';
import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App'; import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App';
import { import {

View File

@ -1,33 +1,22 @@
import React, { useCallback, useContext, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { import {
Avatar,
Box, Box,
Button, Button,
ButtonBase,
Collapse,
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogContentText,
DialogTitle, DialogTitle,
Input,
ListItem, ListItem,
ListItemAvatar,
ListItemButton,
ListItemIcon, ListItemIcon,
ListItemText, ListItemText,
List, List,
MenuItem,
Popover,
Select,
TextField, TextField,
Typography, Typography,
useTheme, useTheme,
} from '@mui/material'; } from '@mui/material';
import { Label } from './Group/AddGroup'; import { Label } from './Group/AddGroup';
import { Spacer } from '../common/Spacer'; import { Spacer } from '../common/Spacer';
import { LoadingButton } from '@mui/lab'; import { getBaseApiReact } from '../App';
import { getBaseApiReact, MyContext } from '../App';
import { getFee } from '../background'; import { getFee } from '../background';
import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked'; import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked';
import { subscribeToEvent, unsubscribeFromEvent } from '../utils/events'; import { subscribeToEvent, unsubscribeFromEvent } from '../utils/events';
@ -43,6 +32,7 @@ enum Availability {
AVAILABLE = 'available', AVAILABLE = 'available',
NOT_AVAILABLE = 'not-available', NOT_AVAILABLE = 'not-available',
} }
export const RegisterName = ({ export const RegisterName = ({
setOpenSnack, setOpenSnack,
setInfoSnack, setInfoSnack,
@ -77,7 +67,6 @@ export const RegisterName = ({
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
} }
}; };
// Debounce logic // Debounce logic
@ -195,21 +184,22 @@ export const RegisterName = ({
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle id="alert-dialog-title">{'Register name'}</DialogTitle> <DialogTitle id="alert-dialog-title">{'Register name'}</DialogTitle>
<DialogContent> <DialogContent>
<Box <Box
sx={{ sx={{
width: '400px', alignItems: 'center',
maxWidth: '90vw',
height: '500px',
maxHeight: '90vh',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center',
gap: '10px', gap: '10px',
height: '500px',
maxHeight: '90vh',
maxWidth: '90vw',
padding: '10px', padding: '10px',
width: '400px',
}} }}
> >
<Label>Choose a name</Label> <Label>Choose a name</Label> // TODO: translate
<TextField <TextField
autoComplete="off" autoComplete="off"
autoFocus autoFocus
@ -237,6 +227,7 @@ export const RegisterName = ({
requires a {nameFee} QORT fee requires a {nameFee} QORT fee
</Typography> </Typography>
</Box> </Box>
<Spacer height="10px" /> <Spacer height="10px" />
</> </>
)} )}
@ -307,6 +298,7 @@ export const RegisterName = ({
</ListItemIcon> </ListItemIcon>
<ListItemText primary="Publish data to Qortal: anything from apps to videos. Fully decentralized!" /> <ListItemText primary="Publish data to Qortal: anything from apps to videos. Fully decentralized!" />
</ListItem> </ListItem>
<ListItem disablePadding> <ListItem disablePadding>
<ListItemIcon> <ListItemIcon>
<RadioButtonCheckedIcon <RadioButtonCheckedIcon
@ -320,6 +312,7 @@ export const RegisterName = ({
</List> </List>
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button
disabled={isLoadingRegisterName} disabled={isLoadingRegisterName}
@ -331,6 +324,7 @@ export const RegisterName = ({
> >
Close Close
</Button> </Button>
<Button <Button
disabled={ disabled={
!registerNameValue.trim() || !registerNameValue.trim() ||

View File

@ -8,15 +8,14 @@
text-align: left; text-align: left;
} }
.tooltip .bottom { .tooltip .core-panel {
border-radius: 8px; border-radius: 8px;
border: 1px solid var(--black); border: 1px solid var(--black);
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5); box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
box-sizing: border-box; box-sizing: border-box;
font-size: 13px; font-size: 13px;
font-weight: normal; font-weight: normal;
max-width: 250px; width: max-content;
min-width: 225px;
opacity: 0; opacity: 0;
padding: 10px 10px; padding: 10px 10px;
position: absolute; position: absolute;
@ -27,23 +26,23 @@
z-index: 99999999; z-index: 99999999;
} }
.tooltip[data-theme='light'] .bottom { .tooltip[data-theme='light'] .core-panel {
background-color: #f1f1f1; background-color: #f1f1f1;
color: #000000; color: #000000;
} }
.tooltip[data-theme='dark'] .bottom { .tooltip[data-theme='dark'] .core-panel {
background-color: var(--bg-2); background-color: var(--bg-2);
color: var(--black); color: var(--black);
} }
.tooltip:hover .bottom { .tooltip:hover .core-panel {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
z-index: 100; z-index: 100;
} }
.tooltip .bottom i { .tooltip .core-panel i {
bottom: 100%; bottom: 100%;
height: 12px; height: 12px;
left: 50%; left: 50%;

View File

@ -56,6 +56,7 @@ const commonThemeOptions = {
xl: 1536, xl: 1536,
}, },
}, },
components: { components: {
MuiButton: { MuiButton: {
styleOverrides: { styleOverrides: {
@ -72,6 +73,7 @@ const commonThemeOptions = {
disableRipple: true, disableRipple: true,
}, },
}, },
MuiModal: { MuiModal: {
styleOverrides: { styleOverrides: {
root: { root: {

View File

@ -48,6 +48,7 @@ export const darkThemeOptions: ThemeOptions = {
}, },
}, },
}, },
MuiCssBaseline: { MuiCssBaseline: {
styleOverrides: (theme) => ({ styleOverrides: (theme) => ({
':root': { ':root': {
@ -61,13 +62,16 @@ export const darkThemeOptions: ThemeOptions = {
'--background-paper': theme.palette.background.paper, '--background-paper': theme.palette.background.paper,
'--background-surface': theme.palette.background.surface, '--background-surface': theme.palette.background.surface,
}, },
'*, *::before, *::after': { '*, *::before, *::after': {
boxSizing: 'border-box', boxSizing: 'border-box',
}, },
html: { html: {
padding: 0, padding: 0,
margin: 0, margin: 0,
}, },
body: { body: {
padding: 0, padding: 0,
margin: 0, margin: 0,
@ -100,6 +104,7 @@ export const darkThemeOptions: ThemeOptions = {
}, },
}), }),
}, },
MuiIcon: { MuiIcon: {
defaultProps: { defaultProps: {
style: { style: {
@ -108,6 +113,7 @@ export const darkThemeOptions: ThemeOptions = {
}, },
}, },
}, },
MuiDialog: { MuiDialog: {
styleOverrides: { styleOverrides: {
paper: { paper: {
@ -115,6 +121,7 @@ export const darkThemeOptions: ThemeOptions = {
}, },
}, },
}, },
MuiPopover: { MuiPopover: {
styleOverrides: { styleOverrides: {
paper: { paper: {

View File

@ -32,6 +32,7 @@ export const lightThemeOptions: ThemeOptions = {
unread: 'rgb(66, 151, 226)', unread: 'rgb(66, 151, 226)',
}, },
}, },
components: { components: {
MuiCard: { MuiCard: {
styleOverrides: { styleOverrides: {
@ -48,6 +49,7 @@ export const lightThemeOptions: ThemeOptions = {
}, },
}, },
}, },
MuiCssBaseline: { MuiCssBaseline: {
styleOverrides: (theme) => ({ styleOverrides: (theme) => ({
':root': { ':root': {
@ -113,6 +115,7 @@ export const lightThemeOptions: ThemeOptions = {
}, },
}, },
}, },
MuiDialog: { MuiDialog: {
styleOverrides: { styleOverrides: {
paper: { paper: {
@ -120,6 +123,7 @@ export const lightThemeOptions: ThemeOptions = {
}, },
}, },
}, },
MuiPopover: { MuiPopover: {
styleOverrides: { styleOverrides: {
paper: { paper: {