mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-05-15 22:26:58 +00:00
Merge branch 'develop' into chat-image
This commit is contained in:
commit
380e7d2387
58
i18n.js
58
i18n.js
@ -1,58 +0,0 @@
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import HttpBackend from 'i18next-http-backend';
|
||||
import LocalStorageBackend from 'i18next-localstorage-backend';
|
||||
import HttpApi from 'i18next-http-backend';
|
||||
import i18n from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
// Detect environment
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
// Register custom postProcessor: it capitalizes the first letter of a translation-
|
||||
// Usage:
|
||||
// t('greeting', { postProcess: 'capitalize' })
|
||||
const capitalize = {
|
||||
type: 'postProcessor',
|
||||
name: 'capitalize',
|
||||
process: (value) => {
|
||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||
},
|
||||
};
|
||||
|
||||
export const supportedLanguages = {
|
||||
de: { name: 'Deutsch', flag: '🇩🇪' },
|
||||
en: { name: 'English', flag: '🇺🇸' },
|
||||
es: { name: 'Español', flag: '🇪🇸' },
|
||||
fr: { name: 'Français', flag: '🇫🇷' },
|
||||
it: { name: 'Italiano', flag: '🇮🇹' },
|
||||
ru: { name: 'Русский', flag: '🇷🇺' },
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(HttpApi)
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.use(capitalize)
|
||||
.init({
|
||||
backend: {
|
||||
backends: [LocalStorageBackend, HttpBackend],
|
||||
backendOptions: [
|
||||
{
|
||||
expirationTime: 7 * 24 * 60 * 60 * 1000, // 7 days
|
||||
},
|
||||
{
|
||||
loadPath: '/locales/{{lng}}/{{ns}}.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
debug: isDev,
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
lng: navigator.language,
|
||||
ns: ['auth', 'core', 'group', 'tutorial'],
|
||||
supportedLngs: Object.keys(supportedLanguages),
|
||||
});
|
||||
|
||||
export default i18n;
|
@ -1,71 +0,0 @@
|
||||
{
|
||||
"add": "hinzufügen",
|
||||
"cancel": "abbrechen",
|
||||
"choose": "auswählen",
|
||||
"close": "schließen",
|
||||
"continue": "fortfahren",
|
||||
"core": {
|
||||
"block_height": "Blockhöhe",
|
||||
"information": "Kerninformationen",
|
||||
"peers": "verbundene Peers",
|
||||
"version": "Kernversion"
|
||||
},
|
||||
"description": "Beschreibung",
|
||||
"edit": "bearbeiten",
|
||||
"export": "exportieren",
|
||||
"import": "importieren",
|
||||
"last_height": "letzte Höhe",
|
||||
"loading": "Lade...",
|
||||
"logout": "abmelden",
|
||||
"minting_status": "Präge-Status",
|
||||
"payment_notification": "Zahlungsbenachrichtigung",
|
||||
"price": "Preis",
|
||||
"q_mail": "Q-Mail",
|
||||
"result": {
|
||||
"error": {
|
||||
"generic": "Ein Fehler ist aufgetreten",
|
||||
"incorrect_password": "Falsches Passwort",
|
||||
"save_qdn": "Speichern in QDN nicht möglich"
|
||||
},
|
||||
"status": {
|
||||
"minting": "(Prägung)",
|
||||
"not_minting": "(keine Prägung)",
|
||||
"synchronized": "synchronisiert",
|
||||
"synchronizing": "synchronisiere"
|
||||
},
|
||||
"success": {
|
||||
"publish_qdn": "Erfolgreich in QDN veröffentlicht"
|
||||
}
|
||||
},
|
||||
"save_options": {
|
||||
"no_pinned_changes": "Derzeit keine Änderungen an Ihren angehefteten Apps",
|
||||
"overwrite_changes": "Die App konnte Ihre vorhandenen in QDN gespeicherten angehefteten Apps nicht herunterladen. Möchten Sie diese Änderungen überschreiben?",
|
||||
"overwrite_qdn": "In QDN überschreiben",
|
||||
"publish_qdn": "Möchten Sie Ihre Einstellungen in QDN (verschlüsselt) veröffentlichen?",
|
||||
"qdn": "QDN-Speicherung verwenden",
|
||||
"register_name": "Sie benötigen einen registrierten Qortal-Namen, um Ihre 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_qdn": "Gefällt Ihnen Ihre aktuellen lokalen Änderungen nicht? Möchten Sie zu Ihren in QDN gespeicherten Anheftungen zurückkehren?",
|
||||
"revert_default": "Auf Standard zurücksetzen",
|
||||
"revert_qdn": "Auf QDN zurücksetzen",
|
||||
"save_qdn": "In QDN speichern",
|
||||
"save": "speichern",
|
||||
"settings": "Sie verwenden die Export/Import-Methode zum Speichern von Einstellungen.",
|
||||
"unsaved_changes": "Sie haben nicht gespeicherte Änderungen an Ihren angehefteten Apps. Speichern Sie sie in QDN."
|
||||
},
|
||||
"settings": "Einstellungen",
|
||||
"supply": "Angebot",
|
||||
"theme": {
|
||||
"dark": "Dunkelmodus",
|
||||
"light": "Hellmodus"
|
||||
},
|
||||
"title": "Titel",
|
||||
"tutorial": "Tutorial",
|
||||
"user_lookup": "Benutzersuche",
|
||||
"wallet": {
|
||||
"backup_wallet": "Wallet sichern",
|
||||
"wallet": "Wallet",
|
||||
"wallet_other": "Wallets"
|
||||
},
|
||||
"welcome": "Willkommen"
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
{
|
||||
"action": {
|
||||
"cancel_ban": "cancel ban",
|
||||
"create_group": "create group",
|
||||
"find_group": "find group",
|
||||
"join_group": "join group",
|
||||
"invite_member": "invite member",
|
||||
"refetch_page": "refetch page",
|
||||
"return_to_thread": "return to threads"
|
||||
},
|
||||
"advanced_options": "advanced options",
|
||||
"approval_threshold": "group Approval Threshold (number / percentage of Admins that must approve a transaction)",
|
||||
"ban_list": "ban list",
|
||||
"block_delay": {
|
||||
"minimum": "minimum Block delay for Group Transaction Approvals",
|
||||
"maximum": "maximum Block delay for Group Transaction Approvals"
|
||||
},
|
||||
"group": {
|
||||
"closed": "closed (private) - users need permission to join",
|
||||
"description": "description of group",
|
||||
"invites": "group invites",
|
||||
"management": "group management",
|
||||
"name": "name of group",
|
||||
"open": "open (public)",
|
||||
"type": "group type"
|
||||
},
|
||||
"invitation_expiry": "invitation Expiry Time",
|
||||
"join_requests": "join requests",
|
||||
"question": {
|
||||
"cancel_ban": "would you like to perform a CANCEL_GROUP_BAN transaction?",
|
||||
"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": {
|
||||
"generic": {
|
||||
"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",
|
||||
"no_display": "nothing to display",
|
||||
"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.",
|
||||
"only_encrypted": "only unencrypted messages will be displayed.",
|
||||
"setting_group": "setting up group... please wait."
|
||||
},
|
||||
"error": {
|
||||
"access_name": "cannot send a message without a access to your name",
|
||||
"description_required": "please provide a description",
|
||||
"group_info": "cannot access group information",
|
||||
"name_required": "please provide a name",
|
||||
"notify_admins": "try notifying an admin from the list of admins below:"
|
||||
},
|
||||
"success": {
|
||||
"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_label": "created group {{name}}: success!",
|
||||
"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_name": "joined group {{group_name}}: awaiting confirmation",
|
||||
"group_join_label": "joined group {{name}}: success!",
|
||||
"loading_threads": "loading threads... please wait.",
|
||||
"unbanned_user": "successfully unbanned user. It may take a couple of minutes for the changes to propagate"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
{
|
||||
"add": "agregar",
|
||||
"cancel": "cancelar",
|
||||
"choose": "elegir",
|
||||
"close": "cerrar",
|
||||
"continue": "continuar",
|
||||
"core": {
|
||||
"block_height": "altura de bloque",
|
||||
"information": "información del núcleo",
|
||||
"peers": "pares conectados",
|
||||
"version": "versión del núcleo"
|
||||
},
|
||||
"description": "descripción",
|
||||
"edit": "editar",
|
||||
"export": "exportar",
|
||||
"import": "importar",
|
||||
"last_height": "última altura",
|
||||
"loading": "cargando...",
|
||||
"logout": "cerrar sesión",
|
||||
"minting_status": "estado de acuñación",
|
||||
"payment_notification": "notificación de pago",
|
||||
"price": "precio",
|
||||
"q_mail": "q-mail",
|
||||
"result": {
|
||||
"error": {
|
||||
"generic": "ocurrió un error",
|
||||
"incorrect_password": "contraseña incorrecta",
|
||||
"save_qdn": "no se pudo guardar en QDN"
|
||||
},
|
||||
"status": {
|
||||
"minting": "(acuñando)",
|
||||
"not_minting": "(no acuñando)",
|
||||
"synchronized": "sincronizado",
|
||||
"synchronizing": "sincronizando"
|
||||
},
|
||||
"success": {
|
||||
"publish_qdn": "publicado exitosamente en QDN"
|
||||
}
|
||||
},
|
||||
"save_options": {
|
||||
"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_qdn": "sobrescribir en QDN",
|
||||
"publish_qdn": "¿Deseas publicar tus configuraciones en QDN (cifrado)?",
|
||||
"qdn": "usar guardado en QDN",
|
||||
"register_name": "necesitas un nombre 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_qdn": "¿No te gustan tus cambios locales actuales? ¿Deseas restablecer tus aplicaciones fijadas guardadas en QDN?",
|
||||
"revert_default": "restablecer a predeterminado",
|
||||
"revert_qdn": "restablecer a QDN",
|
||||
"save_qdn": "guardar en QDN",
|
||||
"save": "guardar",
|
||||
"settings": "estás utilizando el método de exportación/importación para guardar configuraciones.",
|
||||
"unsaved_changes": "tienes cambios no guardados en tus aplicaciones fijadas. Guárdalos en QDN."
|
||||
},
|
||||
"settings": "configuraciones",
|
||||
"supply": "suministro",
|
||||
"theme": {
|
||||
"dark": "modo oscuro",
|
||||
"light": "modo claro"
|
||||
},
|
||||
"title": "título",
|
||||
"tutorial": "tutorial",
|
||||
"user_lookup": "búsqueda de usuario",
|
||||
"wallet": {
|
||||
"backup_wallet": "respaldar billetera",
|
||||
"wallet": "billetera",
|
||||
"wallet_other": "billeteras"
|
||||
},
|
||||
"welcome": "bienvenido"
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
{
|
||||
"add": "ajouter",
|
||||
"cancel": "annuler",
|
||||
"choose": "choisir",
|
||||
"close": "fermer",
|
||||
"continue": "continuer",
|
||||
"core": {
|
||||
"block_height": "hauteur de bloc",
|
||||
"information": "informations du noyau",
|
||||
"peers": "pairs connectés",
|
||||
"version": "version du noyau"
|
||||
},
|
||||
"description": "description",
|
||||
"edit": "éditer",
|
||||
"export": "exporter",
|
||||
"import": "importer",
|
||||
"last_height": "dernière hauteur",
|
||||
"loading": "chargement...",
|
||||
"logout": "se déconnecter",
|
||||
"minting_status": "statut de frappe",
|
||||
"payment_notification": "notification de paiement",
|
||||
"price": "prix",
|
||||
"q_mail": "q-mail",
|
||||
"result": {
|
||||
"error": {
|
||||
"generic": "une erreur s'est produite",
|
||||
"incorrect_password": "mot de passe incorrect",
|
||||
"save_qdn": "impossible d'enregistrer dans QDN"
|
||||
},
|
||||
"status": {
|
||||
"minting": "(frappe en cours)",
|
||||
"not_minting": "(pas de frappe)",
|
||||
"synchronized": "synchronisé",
|
||||
"synchronizing": "synchronisation en cours"
|
||||
},
|
||||
"success": {
|
||||
"publish_qdn": "publié avec succès dans QDN"
|
||||
}
|
||||
},
|
||||
"save_options": {
|
||||
"no_pinned_changes": "vous n'avez actuellement aucune modification de 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_qdn": "écraser dans QDN",
|
||||
"publish_qdn": "souhaitez-vous publier vos paramètres dans QDN (chiffré) ?",
|
||||
"qdn": "utiliser l'enregistrement QDN",
|
||||
"register_name": "vous devez avoir un nom Qortal enregistré pour enregistrer vos applications épinglées dans QDN.",
|
||||
"reset_pinned": "vous n'aimez pas vos modifications locales actuelles ? Voulez-vous réinitialiser les applications épinglées 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 ?",
|
||||
"revert_default": "revenir aux paramètres par défaut",
|
||||
"revert_qdn": "revenir à QDN",
|
||||
"save_qdn": "enregistrer dans QDN",
|
||||
"save": "enregistrer",
|
||||
"settings": "vous utilisez la méthode d'exportation/importation pour enregistrer les paramètres.",
|
||||
"unsaved_changes": "vous avez des modifications non enregistrées de vos applications épinglées. Enregistrez-les dans QDN."
|
||||
},
|
||||
"settings": "paramètres",
|
||||
"supply": "approvisionnement",
|
||||
"theme": {
|
||||
"dark": "mode sombre",
|
||||
"light": "mode clair"
|
||||
},
|
||||
"title": "titre",
|
||||
"tutorial": "tutoriel",
|
||||
"user_lookup": "recherche d'utilisateur",
|
||||
"wallet": {
|
||||
"backup_wallet": "sauvegarder le portefeuille",
|
||||
"wallet": "portefeuille",
|
||||
"wallet_other": "portefeuilles"
|
||||
},
|
||||
"welcome": "bienvenue"
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
{
|
||||
"action": {
|
||||
"cancel_ban": "annulla ban",
|
||||
"create_group": "crea gruppo",
|
||||
"find_group": "trova gruppo",
|
||||
"join_group": "unisciti al gruppo",
|
||||
"invite_member": "invita membro",
|
||||
"refetch_page": "ricarica pagina",
|
||||
"return_to_thread": "torna ai thread"
|
||||
},
|
||||
"advanced_options": "opzioni avanzate",
|
||||
"approval_threshold": "soglia di Approvazione del gruppo (numero/percentuale di Admin che devono approvare una transazione)",
|
||||
"ban_list": "lista ban",
|
||||
"block_delay": {
|
||||
"minimum": "ritardo minimo dei blocchi per Approvazione di Transazioni di Gruppo",
|
||||
"maximum": "ritardo massimo dei blocchi per Approvazione di Transazioni di Gruppo"
|
||||
},
|
||||
"group": {
|
||||
"closed": "chiuso (privato) - gli utenti necessitano di permesso per unirsi",
|
||||
"description": "descrizione del gruppo",
|
||||
"invites": "inviti del gruppo",
|
||||
"management": "gestione del gruppo",
|
||||
"name": "nome del gruppo",
|
||||
"open": "aperto (pubblico)",
|
||||
"type": "tipo di gruppo"
|
||||
},
|
||||
"invitation_expiry": "tempo di scadenza dell'invito",
|
||||
"join_requests": "richieste di adesione",
|
||||
"question": {
|
||||
"cancel_ban": "vuoi eseguire una transazione CANCEL_GROUP_BAN?",
|
||||
"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": {
|
||||
"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...",
|
||||
"group_invited_you": "{{group}} ti ha invitato",
|
||||
"no_display": "niente da visualizzare",
|
||||
"no_selection": "nessun gruppo selezionato",
|
||||
"not_part_group": "non fai parte del gruppo cifrato dei membri. Attendi che un amministratore ri-codifichi le chiavi.",
|
||||
"only_encrypted": "verranno visualizzati solo i messaggi non cifrati.",
|
||||
"setting_group": "configurazione del gruppo... attendere prego."
|
||||
},
|
||||
"error": {
|
||||
"access_name": "impossibile inviare un messaggio senza accesso al tuo nome",
|
||||
"description_required": "per favore fornisci una descrizione",
|
||||
"group_info": "impossibile accedere alle informazioni del gruppo",
|
||||
"name_required": "per favore fornisci un nome",
|
||||
"notify_admins": "prova a notificare un admin dalla lista di amministratori qui sotto:"
|
||||
},
|
||||
"success": {
|
||||
"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_label": "gruppo {{name}} creato: successo!",
|
||||
"group_invite": "invito inviato con successo a {{value}}. 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_name": "entrato nel gruppo {{group_name}}: in attesa di conferma",
|
||||
"group_join_label": "entrato nel gruppo {{name}}: successo!",
|
||||
"loading_threads": "caricamento thread... attendere prego.",
|
||||
"unbanned_user": "utente sbannato con successo. Potrebbero volerci alcuni minuti affinché le modifiche si propaghino"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
{
|
||||
"add": "добавить",
|
||||
"cancel": "отмена",
|
||||
"choose": "выбрать",
|
||||
"close": "закрыть",
|
||||
"continue": "продолжить",
|
||||
"core": {
|
||||
"block_height": "высота блока",
|
||||
"information": "информация ядра",
|
||||
"peers": "подключенные узлы",
|
||||
"version": "версия ядра"
|
||||
},
|
||||
"description": "описание",
|
||||
"edit": "редактировать",
|
||||
"export": "экспорт",
|
||||
"import": "импорт",
|
||||
"last_height": "последняя высота",
|
||||
"loading": "загрузка...",
|
||||
"logout": "выйти",
|
||||
"minting_status": "статус чеканки",
|
||||
"payment_notification": "уведомление о платеже",
|
||||
"price": "цена",
|
||||
"q_mail": "q-mail",
|
||||
"result": {
|
||||
"error": {
|
||||
"generic": "произошла ошибка",
|
||||
"incorrect_password": "неверный пароль",
|
||||
"save_qdn": "не удалось сохранить в QDN"
|
||||
},
|
||||
"status": {
|
||||
"minting": "(чеканка)",
|
||||
"not_minting": "(не чеканится)",
|
||||
"synchronized": "синхронизировано",
|
||||
"synchronizing": "синхронизация"
|
||||
},
|
||||
"success": {
|
||||
"publish_qdn": "успешно опубликовано в QDN"
|
||||
}
|
||||
},
|
||||
"save_options": {
|
||||
"no_pinned_changes": "у вас нет изменений в закреплённых приложениях",
|
||||
"overwrite_changes": "приложению не удалось загрузить ваши закреплённые приложения, сохранённые в QDN. Хотите перезаписать эти изменения?",
|
||||
"overwrite_qdn": "перезаписать в QDN",
|
||||
"publish_qdn": "хотите опубликовать свои настройки в QDN (зашифровано)?",
|
||||
"qdn": "использовать сохранение в QDN",
|
||||
"register_name": "вам необходимо зарегистрированное имя Qortal для сохранения закреплённых приложений в QDN.",
|
||||
"reset_pinned": "не устраивают текущие локальные изменения? Хотите сбросить до приложений по умолчанию?",
|
||||
"reset_qdn": "не устраивают текущие локальные изменения? Хотите сбросить до сохранённых в QDN приложений?",
|
||||
"revert_default": "сбросить до стандартных",
|
||||
"revert_qdn": "сбросить до QDN",
|
||||
"save_qdn": "сохранить в QDN",
|
||||
"save": "сохранить",
|
||||
"settings": "вы используете метод экспорта/импорта для сохранения настроек.",
|
||||
"unsaved_changes": "у вас есть несохранённые изменения в закреплённых приложениях. Сохраните их в QDN."
|
||||
},
|
||||
"settings": "настройки",
|
||||
"supply": "предложение",
|
||||
"theme": {
|
||||
"dark": "тёмная тема",
|
||||
"light": "светлая тема"
|
||||
},
|
||||
"title": "заголовок",
|
||||
"tutorial": "учебник",
|
||||
"user_lookup": "поиск пользователя",
|
||||
"wallet": {
|
||||
"backup_wallet": "резервная копия кошелька",
|
||||
"wallet": "кошелёк",
|
||||
"wallet_other": "кошельки"
|
||||
},
|
||||
"welcome": "добро пожаловать"
|
||||
}
|
42
src/App.tsx
42
src/App.tsx
@ -1028,12 +1028,7 @@ function App() {
|
||||
|
||||
const logoutFunc = useCallback(async () => {
|
||||
try {
|
||||
if (hasSettingsChanged) {
|
||||
await showUnsavedChanges({
|
||||
message:
|
||||
'Your settings have changed. If you logout you will lose your changes. Click on the save button in the header to keep your changed settings.',
|
||||
}); // TODO translate
|
||||
} else if (extState === 'authenticated') {
|
||||
if (extState === 'authenticated') {
|
||||
await showUnsavedChanges({
|
||||
message: 'Are you sure you would like to logout?',
|
||||
});
|
||||
@ -2074,6 +2069,8 @@ function App() {
|
||||
lineHeight: 1.2,
|
||||
maxWidth: '90%',
|
||||
textAlign: 'center',
|
||||
fontSize: '16px',
|
||||
marginBottom: '10px',
|
||||
}}
|
||||
>
|
||||
{messageQortalRequest?.text1}
|
||||
@ -3012,13 +3009,16 @@ function App() {
|
||||
})}
|
||||
</TextP>
|
||||
<Spacer height="100px" />
|
||||
<CustomButton
|
||||
<ButtonBase
|
||||
autoFocus
|
||||
onClick={() => {
|
||||
returnToMain();
|
||||
}}
|
||||
>
|
||||
{t('core:action.continue', { postProcess: 'capitalize' })}
|
||||
</CustomButton>
|
||||
<CustomButton>
|
||||
{t('core:action.continue', { postProcess: 'capitalize' })}
|
||||
</CustomButton>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
)}
|
||||
{extState === 'transfer-success-request' && (
|
||||
@ -3268,6 +3268,8 @@ function App() {
|
||||
lineHeight: 1.2,
|
||||
maxWidth: '90%',
|
||||
textAlign: 'center',
|
||||
fontSize: '16px',
|
||||
marginBottom: '10px',
|
||||
}}
|
||||
>
|
||||
{messageQortalRequestExtension?.text1}
|
||||
@ -3314,8 +3316,8 @@ function App() {
|
||||
>
|
||||
{messageQortalRequestExtension?.text3}
|
||||
</TextP>
|
||||
<Spacer height="15px" />
|
||||
</Box>
|
||||
<Spacer height="15px" />
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -3340,11 +3342,15 @@ function App() {
|
||||
)}
|
||||
|
||||
{messageQortalRequestExtension?.html && (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: messageQortalRequestExtension?.html,
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<Spacer height="15px" />
|
||||
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: messageQortalRequestExtension?.html,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Spacer height="15px" />
|
||||
|
||||
@ -3564,7 +3570,11 @@ function App() {
|
||||
</Dialog>
|
||||
)}
|
||||
{isSettingsOpen && (
|
||||
<Settings open={isSettingsOpen} setOpen={setIsSettingsOpen} />
|
||||
<Settings
|
||||
open={isSettingsOpen}
|
||||
setOpen={setIsSettingsOpen}
|
||||
rawWallet={rawWallet}
|
||||
/>
|
||||
)}
|
||||
<CustomizedSnackbars
|
||||
open={openSnack}
|
||||
|
@ -33,7 +33,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import LanguageSelector from '../components/Language/LanguageSelector';
|
||||
import { MyContext } from '../App';
|
||||
|
||||
const manifestData = {
|
||||
export const manifestData = {
|
||||
version: '0.5.4',
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { useTheme } from '@mui/material';
|
||||
import React from 'react';
|
||||
|
||||
export const CopyIcon = ({ color, height = 11, width = 10 }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const setColor = color ? color : theme.palette.text.primary;
|
||||
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
@ -14,11 +13,11 @@ export const CopyIcon = ({ color, height = 11, width = 10 }) => {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="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"
|
||||
fill={setColor}
|
||||
fill-opacity="0.5"
|
||||
fillOpacity="0.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
@ -937,6 +937,29 @@ export async function getBalanceInfo() {
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getAssetBalanceInfo(assetId: number) {
|
||||
const wallet = await getSaveWallet();
|
||||
const address = wallet.address0;
|
||||
const validApi = await getBaseApi();
|
||||
const response = await fetch(
|
||||
validApi +
|
||||
`/assets/balances?address=${address}&assetid=${assetId}&ordering=ASSET_BALANCE_ACCOUNT&limit=1`
|
||||
);
|
||||
|
||||
if (!response?.ok) throw new Error('Cannot fetch asset balance');
|
||||
const data = await response.json();
|
||||
return +data?.[0]?.balance;
|
||||
}
|
||||
|
||||
export async function getAssetInfo(assetId: number) {
|
||||
const validApi = await getBaseApi();
|
||||
const response = await fetch(validApi + `/assets/info?assetId=${assetId}`);
|
||||
|
||||
if (!response?.ok) throw new Error('Cannot fetch asset info');
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getLTCBalance() {
|
||||
const wallet = await getSaveWallet();
|
||||
let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`;
|
||||
@ -2288,6 +2311,34 @@ export async function kickFromGroup({
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function transferAsset({ amount, recipient, assetId }) {
|
||||
const lastReference = await getLastRef();
|
||||
const resKeyPair = await getKeyPair();
|
||||
const parsedData = resKeyPair;
|
||||
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
||||
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
||||
const keyPair = {
|
||||
privateKey: uint8PrivateKey,
|
||||
publicKey: uint8PublicKey,
|
||||
};
|
||||
const feeres = await getFee('TRANSFER_ASSET');
|
||||
|
||||
const tx = await createTransaction(12, keyPair, {
|
||||
fee: feeres.fee,
|
||||
recipient: recipient,
|
||||
amount: amount,
|
||||
assetId: assetId,
|
||||
lastReference: lastReference,
|
||||
});
|
||||
|
||||
const signedBytes = Base58.encode(tx.signedBytes);
|
||||
|
||||
const res = await processTransactionVersion2(signedBytes);
|
||||
if (!res?.signature)
|
||||
throw new Error(res?.message || 'Transaction was not able to be processed');
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function createGroup({
|
||||
groupName,
|
||||
groupDescription,
|
||||
@ -3638,7 +3689,7 @@ export const checkThreads = async (bringBack) => {
|
||||
dataToBringBack.push(thread);
|
||||
}
|
||||
} catch (error) {
|
||||
conosle.log({ error });
|
||||
console.log({ error });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import './customloader.css';
|
||||
import { Box, useTheme } from '@mui/material';
|
||||
|
||||
export const CustomLoader = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
|
@ -1,17 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
export const CustomSvg = ({ src, color = 'black', size = 24 }) => {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ fill: color }}
|
||||
>
|
||||
{src}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ fill: color }}
|
||||
>
|
||||
{src}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
@ -36,6 +36,7 @@
|
||||
left: 56px;
|
||||
animation: lds-ellipsis3 0.6s infinite;
|
||||
}
|
||||
|
||||
@keyframes lds-ellipsis1 {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { resourceDownloadControllerAtom } from '../atoms/global';
|
||||
import { getBaseApiReact } from '../App';
|
||||
import { useSetAtom } from 'jotai';
|
||||
|
@ -56,7 +56,7 @@ export const AppsCategoryDesktop = ({
|
||||
isShow,
|
||||
}) => {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const virtuosoRef = useRef();
|
||||
const virtuosoRef = useRef(null);
|
||||
const theme = useTheme();
|
||||
|
||||
const categoryList = useMemo(() => {
|
||||
|
@ -414,8 +414,23 @@ export const AppsDesktop = ({
|
||||
setDesktopViewMode('dev');
|
||||
}}
|
||||
>
|
||||
<IconWrapper label="Dev" disableWidth>
|
||||
<AppsIcon height={30} />
|
||||
<IconWrapper
|
||||
color={
|
||||
desktopViewMode === 'dev'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
label="Dev"
|
||||
disableWidth
|
||||
>
|
||||
<AppsIcon
|
||||
color={
|
||||
desktopViewMode === 'dev'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
height={30}
|
||||
/>
|
||||
</IconWrapper>
|
||||
</ButtonBase>
|
||||
)}
|
||||
|
@ -180,7 +180,7 @@ export const AppsDevModeNavBar = () => {
|
||||
>
|
||||
<RefreshIcon
|
||||
sx={{
|
||||
color: 'rgba(250, 250, 250, 0.5)',
|
||||
color: theme.palette.text.primary,
|
||||
width: '40px',
|
||||
height: 'auto',
|
||||
}}
|
||||
|
@ -102,7 +102,7 @@ export const AppsLibraryDesktop = ({
|
||||
getQapps,
|
||||
}) => {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const virtuosoRef = useRef();
|
||||
const virtuosoRef = useRef(null);
|
||||
const theme = useTheme();
|
||||
|
||||
const officialApps = useMemo(() => {
|
||||
|
@ -253,6 +253,9 @@ export const listOfAllQortalRequests = [
|
||||
'SELL_NAME',
|
||||
'CANCEL_SELL_NAME',
|
||||
'BUY_NAME',
|
||||
'SIGN_FOREIGN_FEES',
|
||||
'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
|
||||
'TRANSFER_ASSET',
|
||||
];
|
||||
|
||||
export const UIQortalRequests = [
|
||||
@ -313,6 +316,9 @@ export const UIQortalRequests = [
|
||||
'SELL_NAME',
|
||||
'CANCEL_SELL_NAME',
|
||||
'BUY_NAME',
|
||||
'SIGN_FOREIGN_FEES',
|
||||
'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
|
||||
'TRANSFER_ASSET',
|
||||
];
|
||||
|
||||
async function retrieveFileFromIndexedDB(fileId) {
|
||||
|
@ -63,6 +63,7 @@ export const DownloadWallet = ({
|
||||
wallet,
|
||||
qortAddress: rawWallet.address0,
|
||||
});
|
||||
|
||||
return {
|
||||
wallet,
|
||||
qortAddress: rawWallet.address0,
|
||||
|
@ -18,7 +18,7 @@ export const AnnouncementList = ({
|
||||
loadMore,
|
||||
myName,
|
||||
}) => {
|
||||
const listRef = useRef();
|
||||
const listRef = useRef(null);
|
||||
const [messages, setMessages] = useState(initialMessages);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -23,7 +23,7 @@ export const ChatList = ({
|
||||
hasSecretKey,
|
||||
isPrivate,
|
||||
}) => {
|
||||
const parentRef = useRef();
|
||||
const parentRef = useRef(null);
|
||||
const [messages, setMessages] = useState(initialMessages);
|
||||
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||
const [showScrollDownButton, setShowScrollDownButton] = useState(false);
|
||||
|
@ -60,8 +60,8 @@ export const ChatOptions = ({
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [selectedMember, setSelectedMember] = useState(0);
|
||||
const theme = useTheme();
|
||||
const parentRef = useRef();
|
||||
const parentRefMentions = useRef();
|
||||
const parentRef = useRef(null);
|
||||
const parentRefMentions = useRef(null);
|
||||
const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null);
|
||||
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
|
||||
const messages = useMemo(() => {
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
import { RequestQueueWithPromise } from '../../utils/queue/queue';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group/Group';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
@ -101,6 +102,7 @@ export const decryptPublishes = async (encryptedMessages: any[], secretKey) => {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const handleUnencryptedPublishes = (publishes) => {
|
||||
let publishesData = [];
|
||||
publishes.forEach((pub) => {
|
||||
@ -149,6 +151,7 @@ export const GroupAnnouncements = ({
|
||||
editorRef.current = editorInstance;
|
||||
};
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
|
||||
const triggerRerender = () => {
|
||||
forceUpdate(); // Trigger re-render by updating the state
|
||||
@ -209,7 +212,6 @@ export const GroupAnnouncements = ({
|
||||
)
|
||||
return;
|
||||
setIsLoading(true);
|
||||
// initWebsocketMessageGroup()
|
||||
hasInitializedWebsocket.current = true;
|
||||
}, [secretKey, isPrivate]);
|
||||
|
||||
@ -287,7 +289,10 @@ export const GroupAnnouncements = ({
|
||||
const fee = await getFee('ARBITRARY');
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
@ -329,7 +334,7 @@ export const GroupAnnouncements = ({
|
||||
setTempData(selectedGroup);
|
||||
clearEditorContent();
|
||||
}
|
||||
// send chat message
|
||||
// TODO send chat message
|
||||
} catch (error) {
|
||||
if (!error) return;
|
||||
setInfoSnack({
|
||||
|
@ -6,6 +6,7 @@ import { getBaseApiReact } from '../App';
|
||||
import '../styles/CoreSyncStatus.css';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { manifestData } from '../ExtStates/NotAuthenticated';
|
||||
|
||||
export const CoreSyncStatus = () => {
|
||||
const [nodeInfos, setNodeInfos] = useState({});
|
||||
@ -75,7 +76,9 @@ export const CoreSyncStatus = () => {
|
||||
: '';
|
||||
|
||||
let imagePath = syncingImg;
|
||||
let message = t('core:status.synchronizing', { postProcess: 'capitalize' });
|
||||
let message = t('core:message.status.synchronizing', {
|
||||
postProcess: 'capitalize',
|
||||
});
|
||||
|
||||
if (isMintingPossible && !isUsingGateway) {
|
||||
imagePath = syncedMintingImg;
|
||||
@ -111,7 +114,7 @@ export const CoreSyncStatus = () => {
|
||||
</span>
|
||||
|
||||
<div
|
||||
className="bottom"
|
||||
className="core-panel"
|
||||
style={{
|
||||
right: 'unset',
|
||||
left: '55px',
|
||||
@ -119,27 +122,37 @@ export const CoreSyncStatus = () => {
|
||||
}}
|
||||
>
|
||||
<h3>{t('core:core.information', { postProcess: 'capitalize' })}</h3>
|
||||
|
||||
<h4 className="lineHeight">
|
||||
{t('core:core.version', { postProcess: 'capitalize' })}:{' '}
|
||||
<span style={{ color: '#03a9f4' }}>{buildVersion}</span>
|
||||
</h4>
|
||||
|
||||
<h4 className="lineHeight">{message}</h4>
|
||||
|
||||
<h4 className="lineHeight">
|
||||
{t('core:core.block_height', { postProcess: 'capitalize' })}:{' '}
|
||||
<span style={{ color: '#03a9f4' }}>{height || ''}</span>
|
||||
</h4>
|
||||
|
||||
<h4 className="lineHeight">
|
||||
{t('core:core.peers', { postProcess: 'capitalize' })}:{' '}
|
||||
<span style={{ color: '#03a9f4' }}>
|
||||
{numberOfConnections || ''}
|
||||
</span>
|
||||
</h4>
|
||||
|
||||
<h4 className="lineHeight">
|
||||
{t('auth:node.using_public', { postProcess: 'capitalize' })}:{' '}
|
||||
<span style={{ color: '#03a9f4' }}>
|
||||
{isUsingGateway?.toString()}
|
||||
</span>
|
||||
</h4>
|
||||
|
||||
<h4 className="lineHeight">
|
||||
{t('core:ui.version', { postProcess: 'capitalize' })}:{' '}
|
||||
<span style={{ color: '#03a9f4' }}>{manifestData.version}</span>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -3,10 +3,8 @@ import Box from '@mui/material/Box';
|
||||
import { HubsIcon } from '../../assets/Icons/HubsIcon';
|
||||
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
|
||||
import AppIcon from '../../assets/svgs/AppIcon.svg';
|
||||
|
||||
import { HomeIcon } from '../../assets/Icons/HomeIcon';
|
||||
import { Save } from '../Save/Save';
|
||||
|
||||
import { enabledDevModeAtom } from '../../atoms/global';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { ButtonBase, Typography, useTheme } from '@mui/material';
|
||||
import Box from '@mui/material/Box';
|
||||
import { NotificationIcon2 } from '../../assets/Icons/NotificationIcon2';
|
||||
@ -81,18 +81,18 @@ export const DesktopHeader = ({
|
||||
setGroupSection,
|
||||
isPrivate,
|
||||
}) => {
|
||||
const [value, setValue] = React.useState(0);
|
||||
const [value, setValue] = useState(0);
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
height: '70px', // Footer height
|
||||
zIndex: 1,
|
||||
justifyContent: 'space-between',
|
||||
padding: '10px',
|
||||
width: '100%',
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -126,11 +126,12 @@ export const DesktopHeader = ({
|
||||
: selectedGroup?.groupName}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
alignItems: 'center',
|
||||
visibility: selectedGroup?.groupId === '0' ? 'hidden' : 'visibile',
|
||||
}}
|
||||
>
|
||||
@ -219,6 +220,7 @@ export const DesktopHeader = ({
|
||||
/>
|
||||
</IconWrapper>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setOpenManageMembers(true);
|
||||
@ -226,17 +228,18 @@ export const DesktopHeader = ({
|
||||
>
|
||||
<IconWrapper
|
||||
color={theme.palette.text.secondary}
|
||||
customHeight="55px"
|
||||
label="Members"
|
||||
selected={false}
|
||||
customHeight="55px"
|
||||
>
|
||||
<MembersIcon
|
||||
color={theme.palette.text.secondary}
|
||||
height={25}
|
||||
width={20}
|
||||
color={theme.palette.text.secondary}
|
||||
/>
|
||||
</IconWrapper>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setGroupSection('adminSpace');
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
|
||||
export const DrawerComponent = ({ open, setOpen, children }) => {
|
||||
const toggleDrawer = (newOpen: boolean) => () => {
|
||||
setOpen(newOpen);
|
||||
|
@ -40,6 +40,7 @@ export const Explore = ({ setDesktopViewMode }) => {
|
||||
}}
|
||||
src={qTradeLogo}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '1rem',
|
||||
@ -66,6 +67,7 @@ export const Explore = ({ setDesktopViewMode }) => {
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '1rem',
|
||||
@ -94,6 +96,7 @@ export const Explore = ({ setDesktopViewMode }) => {
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '1rem',
|
||||
@ -102,6 +105,7 @@ export const Explore = ({ setDesktopViewMode }) => {
|
||||
{t('tutorial:initial.general_chat', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
sx={{
|
||||
'&:hover': { backgroundColor: theme.palette.background.paper },
|
||||
@ -119,6 +123,7 @@ export const Explore = ({ setDesktopViewMode }) => {
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '1rem',
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { JoinGroup } from './JoinGroup';
|
||||
|
||||
export const GlobalActions = () => {
|
||||
|
@ -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 {
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
@ -11,13 +10,14 @@ import {
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { CustomButton, CustomButtonAccept } from '../../styles/App-styles';
|
||||
import { CustomButtonAccept } from '../../styles/App-styles';
|
||||
import { getBaseApiReact, MyContext } from '../../App';
|
||||
import { getFee } from '../../background';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { FidgetSpinner } from 'react-loader-spinner';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { memberGroupsAtom, txListAtom } from '../../atoms/global';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const JoinGroup = () => {
|
||||
const { show } = useContext(MyContext);
|
||||
@ -29,7 +29,9 @@ export const JoinGroup = () => {
|
||||
const [isLoadingInfo, setIsLoadingInfo] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const [isLoadingJoinGroup, setIsLoadingJoinGroup] = useState(false);
|
||||
|
||||
const handleJoinGroup = async (e) => {
|
||||
setGroupInfo(null);
|
||||
const groupId = e?.detail?.groupId;
|
||||
@ -41,6 +43,7 @@ export const JoinGroup = () => {
|
||||
const groupData = await response.json();
|
||||
setGroupInfo(groupData);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingInfo(false);
|
||||
}
|
||||
@ -60,15 +63,22 @@ export const JoinGroup = () => {
|
||||
(item) => +item?.groupId === +groupInfo?.groupId
|
||||
);
|
||||
}, [memberGroups, groupInfo]);
|
||||
|
||||
const joinGroup = async (group, isOpen) => {
|
||||
try {
|
||||
const groupId = group.groupId;
|
||||
const fee = await getFee('JOIN_GROUP');
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
setIsLoadingJoinGroup(true);
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage('joinGroup', {
|
||||
@ -78,8 +88,9 @@ export const JoinGroup = () => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.group_join', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
|
||||
if (isOpen) {
|
||||
@ -87,8 +98,14 @@ export const JoinGroup = () => {
|
||||
{
|
||||
...response,
|
||||
type: 'joined-group',
|
||||
label: `Joined Group ${group?.groupName}: awaiting confirmation`,
|
||||
labelDone: `Joined Group ${group?.groupName}: success!`,
|
||||
label: t('group:message.success.group_join_label', {
|
||||
group_name: group?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
labelDone: t('group:message.success.group_join_label', {
|
||||
group_name: group?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
done: false,
|
||||
groupId,
|
||||
},
|
||||
@ -99,15 +116,20 @@ export const JoinGroup = () => {
|
||||
{
|
||||
...response,
|
||||
type: 'joined-group-request',
|
||||
label: `Requested to join Group ${group?.groupName}: awaiting confirmation`,
|
||||
labelDone: `Requested to join Group ${group?.groupName}: success!`,
|
||||
label: t('group:message.success.group_join_request', {
|
||||
group_name: group?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
labelDone: t('group:message.success.group_join_outcome', {
|
||||
group_name: group?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
done: false,
|
||||
groupId,
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
}
|
||||
|
||||
setOpenSnack(true);
|
||||
res(response);
|
||||
return;
|
||||
@ -123,7 +145,9 @@ export const JoinGroup = () => {
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: 'error',
|
||||
message: error.message || 'An error occurred',
|
||||
message:
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' }),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
@ -131,10 +155,12 @@ export const JoinGroup = () => {
|
||||
});
|
||||
setIsLoadingJoinGroup(false);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingJoinGroup(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
@ -146,32 +172,31 @@ export const JoinGroup = () => {
|
||||
{!groupInfo && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '325px',
|
||||
height: '150px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
height: '150px',
|
||||
justifyContent: 'center',
|
||||
width: '325px',
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
<CircularProgress
|
||||
size={25}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>{' '}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
width: '325px',
|
||||
height: 'auto',
|
||||
maxHeight: '400px',
|
||||
alignItems: 'center',
|
||||
display: !groupInfo ? 'none' : 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
height: 'auto',
|
||||
maxHeight: '400px',
|
||||
padding: '10px',
|
||||
width: '325px',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -180,16 +205,20 @@ export const JoinGroup = () => {
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Group name: {` ${groupInfo?.groupName}`}
|
||||
{t('group:group.name', { postProcess: 'capitalize' })}:{' '}
|
||||
{` ${groupInfo?.groupName}`}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '15px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Number of members: {` ${groupInfo?.memberCount}`}
|
||||
{t('group:group.member_number', { postProcess: 'capitalize' })}:{' '}
|
||||
{` ${groupInfo?.memberCount}`}
|
||||
</Typography>
|
||||
|
||||
{groupInfo?.description && (
|
||||
<Typography
|
||||
sx={{
|
||||
@ -207,7 +236,9 @@ export const JoinGroup = () => {
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
*You are already in this group!
|
||||
{t('group:message.generic.already_in_group', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
{!isInGroup && groupInfo?.isOpen === false && (
|
||||
@ -217,12 +248,14 @@ export const JoinGroup = () => {
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
*This is a closed/private group, so you will need to wait until
|
||||
an admin accepts your request
|
||||
{t('group:message.generic.closed_group', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
@ -242,7 +275,9 @@ export const JoinGroup = () => {
|
||||
opacity: isInGroup ? 0.1 : 1,
|
||||
}}
|
||||
>
|
||||
Join
|
||||
{t('core:action.join', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</CustomButtonAccept>
|
||||
</ButtonBase>
|
||||
|
||||
@ -255,7 +290,9 @@ export const JoinGroup = () => {
|
||||
}}
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Close
|
||||
{t('core:action.close', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</CustomButtonAccept>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@ -269,14 +306,14 @@ export const JoinGroup = () => {
|
||||
{isLoadingJoinGroup && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
alignItems: 'center',
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
}}
|
||||
>
|
||||
<FidgetSpinner
|
||||
|
@ -118,7 +118,8 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
const fee = await getFee('CREATE_GROUP');
|
||||
|
||||
await show({
|
||||
message: t('group:question.create_group', {
|
||||
message: t('group:question.perform_transaction', {
|
||||
action: 'CREATE_GROUP',
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
publishFee: fee.fee + ' QORT',
|
||||
@ -159,6 +160,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
setName('');
|
||||
setDescription('');
|
||||
setGroupType('1');
|
||||
res(response);
|
||||
return;
|
||||
}
|
||||
@ -327,6 +331,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Label>
|
||||
|
||||
<Input
|
||||
placeholder={t('group:group.name', {
|
||||
postProcess: 'capitalize',
|
||||
@ -335,6 +340,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@ -430,12 +436,12 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
onChange={handleChangeApprovalThreshold}
|
||||
>
|
||||
<MenuItem value={0}>
|
||||
{t('core.count.none', {
|
||||
{t('core:count.none', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</MenuItem>
|
||||
<MenuItem value={1}>
|
||||
{t('core.count.one', {
|
||||
{t('core:count.one', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</MenuItem>
|
||||
@ -446,6 +452,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
<MenuItem value={100}>100%</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@ -454,10 +461,11 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}}
|
||||
>
|
||||
<Label>
|
||||
{t('group.block_delay.minimum', {
|
||||
{t('group:block_delay.minimum', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Label>
|
||||
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
@ -466,43 +474,44 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
onChange={handleChangeMinBlock}
|
||||
>
|
||||
<MenuItem value={5}>
|
||||
{t('core.time.minute', { count: 5 })}
|
||||
{t('core:time.minute', { count: 5 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>
|
||||
{t('core.time.minute', { count: 10 })}
|
||||
{t('core:time.minute', { count: 10 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={30}>
|
||||
{t('core.time.minute', { count: 30 })}
|
||||
{t('core:time.minute', { count: 30 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={60}>
|
||||
{t('core.time.hour', { count: 1 })}
|
||||
{t('core:time.hour', { count: 1 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={180}>
|
||||
{t('core.time.hour', { count: 3 })}
|
||||
{t('core:time.hour', { count: 3 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={300}>
|
||||
{t('core.time.hour', { count: 5 })}
|
||||
{t('core:time.hour', { count: 5 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={420}>
|
||||
{t('core.time.hour', { count: 7 })}
|
||||
{t('core:time.hour', { count: 7 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={720}>
|
||||
{t('core.time.hour', { count: 12 })}
|
||||
{t('core:time.hour', { count: 12 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={1440}>
|
||||
{t('core.time.day', { count: 1 })}
|
||||
{t('core:time.day', { count: 1 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={4320}>
|
||||
{t('core.time.day', { count: 3 })}
|
||||
{t('core:time.day', { count: 3 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={7200}>
|
||||
{t('core.time.day', { count: 5 })}
|
||||
{t('core:time.day', { count: 5 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={10080}>
|
||||
{t('core.time.day', { count: 7 })}
|
||||
{t('core:time.day', { count: 7 })}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@ -511,10 +520,11 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}}
|
||||
>
|
||||
<Label>
|
||||
{t('group.block_delay.maximum', {
|
||||
{t('group:block_delay.maximum', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Label>
|
||||
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
@ -523,41 +533,42 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
onChange={handleChangeMaxBlock}
|
||||
>
|
||||
<MenuItem value={60}>
|
||||
{t('core.time.hour', { count: 1 })}
|
||||
{t('core:time.hour', { count: 1 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={180}>
|
||||
3{t('core.time.hour', { count: 3 })}
|
||||
3{t('core:time.hour', { count: 3 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={300}>
|
||||
{t('core.time.hour', { count: 5 })}
|
||||
{t('core:time.hour', { count: 5 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={420}>
|
||||
{t('core.time.hour', { count: 7 })}
|
||||
{t('core:time.hour', { count: 7 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={720}>
|
||||
{t('core.time.hour', { count: 12 })}
|
||||
{t('core:time.hour', { count: 12 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={1440}>
|
||||
{t('core.time.day', { count: 1 })}
|
||||
{t('core:time.day', { count: 1 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={4320}>
|
||||
{t('core.time.day', { count: 3 })}
|
||||
{t('core:time.day', { count: 3 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={7200}>
|
||||
{t('core.time.day', { count: 5 })}
|
||||
{t('core:time.day', { count: 5 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={10080}>
|
||||
{t('core.time.day', { count: 7 })}
|
||||
{t('core:time.day', { count: 7 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={14400}>
|
||||
{t('core.time.day', { count: 10 })}
|
||||
{t('core:time.day', { count: 10 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={21600}>
|
||||
{t('core.time.day', { count: 15 })}
|
||||
{t('core:time.day', { count: 15 })}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
</Collapse>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@ -570,7 +581,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
color="primary"
|
||||
onClick={handleCreateGroup}
|
||||
>
|
||||
{t('group.action.create', {
|
||||
{t('group:action.create_group', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
|
@ -48,7 +48,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
const [groups, setGroups] = useState([]);
|
||||
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 listRef = useRef();
|
||||
const listRef = useRef(null);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [filteredItems, setFilteredItems] = useState(groups);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@ -113,7 +113,8 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
const fee = await getFee('JOIN_GROUP');
|
||||
|
||||
await show({
|
||||
message: t('group:question.join_group', {
|
||||
message: t('group:question.perform_transaction', {
|
||||
action: 'JOIN_GROUP',
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
publishFee: fee.fee + ' QORT',
|
||||
@ -157,8 +158,14 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
{
|
||||
...response,
|
||||
type: 'joined-group-request',
|
||||
label: `Requested to join Group ${group?.groupName}: awaiting confirmation`,
|
||||
labelDone: `Requested to join Group ${group?.groupName}: success!`,
|
||||
label: t('group:message.success.group_join_request', {
|
||||
group_name: group?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
labelDone: t('group:message.success.group_join_outcome', {
|
||||
group_name: group?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
done: false,
|
||||
groupId,
|
||||
},
|
||||
|
@ -1,26 +1,17 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Avatar, Box, Popover, Typography, useTheme } from '@mui/material';
|
||||
// import { MAIL_SERVICE_TYPE, THREAD_SERVICE_TYPE } from "../../constants/mail";
|
||||
import { Thread } from './Thread';
|
||||
import {
|
||||
AllThreadP,
|
||||
ArrowDownIcon,
|
||||
ComposeContainer,
|
||||
ComposeContainerBlank,
|
||||
ComposeIcon,
|
||||
ComposeP,
|
||||
GroupContainer,
|
||||
InstanceFooter,
|
||||
InstanceListContainer,
|
||||
InstanceListContainerRow,
|
||||
InstanceListContainerRowCheck,
|
||||
InstanceListContainerRowCheckIcon,
|
||||
InstanceListContainerRowMain,
|
||||
InstanceListContainerRowMainP,
|
||||
InstanceListHeader,
|
||||
@ -48,7 +39,6 @@ import {
|
||||
getTempPublish,
|
||||
handleUnencryptedPublishes,
|
||||
} from '../../Chat/GroupAnnouncements';
|
||||
import CheckSVG from '../../../assets/svgs/Check.svg';
|
||||
import ArrowDownSVG from '../../../assets/svgs/ArrowDown.svg';
|
||||
import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar';
|
||||
import { executeEvent } from '../../../utils/events';
|
||||
@ -73,9 +63,9 @@ export const GroupMail = ({
|
||||
hide,
|
||||
isPrivate,
|
||||
}) => {
|
||||
const [viewedThreads, setViewedThreads] = React.useState<any>({});
|
||||
const [viewedThreads, setViewedThreads] = useState<any>({});
|
||||
const [filterMode, setFilterMode] = useState<string>('Recently active');
|
||||
const [currentThread, setCurrentThread] = React.useState(null);
|
||||
const [currentThread, setCurrentThread] = useState(null);
|
||||
const [recentThreads, setRecentThreads] = useState<any[]>([]);
|
||||
const [allThreads, setAllThreads] = useState<any[]>([]);
|
||||
const [members, setMembers] = useState<any>(null);
|
||||
@ -178,7 +168,10 @@ export const GroupMail = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -186,7 +179,7 @@ export const GroupMail = ({
|
||||
}
|
||||
};
|
||||
|
||||
const getAllThreads = React.useCallback(
|
||||
const getAllThreads = useCallback(
|
||||
async (groupId: string, mode: string, isInitial?: boolean) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@ -206,7 +199,7 @@ export const GroupMail = ({
|
||||
});
|
||||
const responseData = await response.json();
|
||||
|
||||
let fullArrayMsg = isInitial ? [] : [...allThreads];
|
||||
const fullArrayMsg = isInitial ? [] : [...allThreads];
|
||||
const getMessageForThreads = responseData.map(async (message: any) => {
|
||||
let fullObject: any = null;
|
||||
if (message?.metadata?.description) {
|
||||
@ -271,13 +264,12 @@ export const GroupMail = ({
|
||||
} finally {
|
||||
if (isInitial) {
|
||||
setIsLoading(false);
|
||||
// dispatch(setIsLoadingCustom(null));
|
||||
}
|
||||
}
|
||||
},
|
||||
[allThreads, isPrivate]
|
||||
);
|
||||
const getMailMessages = React.useCallback(
|
||||
const getMailMessages = useCallback(
|
||||
async (groupId: string, members: any) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@ -315,7 +307,7 @@ export const GroupMail = ({
|
||||
.sort((a, b) => b.created - a.created)
|
||||
.slice(0, 10);
|
||||
|
||||
let fullThreadArray: any = [];
|
||||
const fullThreadArray: any = [];
|
||||
const getMessageForThreads = newArray.map(async (message: any) => {
|
||||
try {
|
||||
const identifierQuery = message.threadId;
|
||||
@ -327,6 +319,7 @@ export const GroupMail = ({
|
||||
},
|
||||
});
|
||||
const responseData = await response.json();
|
||||
|
||||
if (responseData.length > 0) {
|
||||
const thread = responseData[0];
|
||||
if (thread?.metadata?.description) {
|
||||
@ -342,7 +335,7 @@ export const GroupMail = ({
|
||||
};
|
||||
fullThreadArray.push(fullObject);
|
||||
} else {
|
||||
let threadRes = await Promise.race([
|
||||
const threadRes = await Promise.race([
|
||||
getEncryptedResource(
|
||||
{
|
||||
name: thread.name,
|
||||
@ -377,13 +370,12 @@ export const GroupMail = ({
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
// dispatch(setIsLoadingCustom(null));
|
||||
}
|
||||
},
|
||||
[secretKey, isPrivate]
|
||||
);
|
||||
|
||||
const getMessages = React.useCallback(async () => {
|
||||
const getMessages = useCallback(async () => {
|
||||
// if ( !groupId || members?.length === 0) return;
|
||||
if (!groupId || isPrivate === null) return;
|
||||
|
||||
@ -400,7 +392,6 @@ export const GroupMail = ({
|
||||
if (filterModeRef.current !== filterMode) {
|
||||
firstMount.current = false;
|
||||
}
|
||||
// if (groupId && !firstMount.current && members.length > 0) {
|
||||
if (groupId && !firstMount.current && isPrivate !== null) {
|
||||
if (filterMode === 'Recently active') {
|
||||
getMessages();
|
||||
@ -427,11 +418,6 @@ export const GroupMail = ({
|
||||
if (groupData && Array.isArray(groupData?.members)) {
|
||||
for (const member of groupData.members) {
|
||||
if (member.member) {
|
||||
// const res = await getNameInfo(member.member);
|
||||
// const resAddress = await qortalRequest({
|
||||
// action: "GET_ACCOUNT_DATA",
|
||||
// address: member.member,
|
||||
// });
|
||||
const name = res;
|
||||
const publicKey = resAddress.publicKey;
|
||||
if (name) {
|
||||
@ -465,16 +451,6 @@ export const GroupMail = ({
|
||||
[filterMode]
|
||||
);
|
||||
|
||||
// useEffect(()=> {
|
||||
// if(user?.name){
|
||||
// const threads = JSON.parse(
|
||||
// localStorage.getItem(`qmail_threads_viewedtimestamp_${user.name}`) || "{}"
|
||||
// );
|
||||
// setViewedThreads(threads)
|
||||
|
||||
// }
|
||||
// }, [user?.name, currentThread])
|
||||
|
||||
const handleCloseThreadFilterList = () => {
|
||||
setIsOpenFilterList(false);
|
||||
};
|
||||
@ -596,7 +572,7 @@ export const GroupMail = ({
|
||||
padding: '0px',
|
||||
}}
|
||||
>
|
||||
<InstanceListHeader></InstanceListHeader>
|
||||
<InstanceListHeader />
|
||||
<InstanceListContainer>
|
||||
{filterOptions?.map((filter) => {
|
||||
return (
|
||||
@ -621,6 +597,7 @@ export const GroupMail = ({
|
||||
/>
|
||||
)}
|
||||
</InstanceListContainerRowCheck>
|
||||
|
||||
<InstanceListContainerRowMain>
|
||||
<InstanceListContainerRowMainP>
|
||||
{filter}
|
||||
@ -630,9 +607,10 @@ export const GroupMail = ({
|
||||
);
|
||||
})}
|
||||
</InstanceListContainer>
|
||||
<InstanceFooter></InstanceFooter>
|
||||
<InstanceFooter />
|
||||
</InstanceListParent>
|
||||
</Popover>
|
||||
|
||||
<ThreadContainerFullWidth>
|
||||
<ThreadContainer>
|
||||
<Box
|
||||
@ -674,7 +652,9 @@ export const GroupMail = ({
|
||||
)}
|
||||
</ComposeContainerBlank>
|
||||
</Box>
|
||||
|
||||
<Spacer height="30px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
@ -700,10 +680,12 @@ export const GroupMail = ({
|
||||
viewedThreads[
|
||||
`qmail_threads_${thread?.threadData?.groupId}_${thread?.threadId}`
|
||||
];
|
||||
|
||||
const shouldAppearLighter =
|
||||
hasViewedRecent &&
|
||||
filterMode === 'Recently active' &&
|
||||
thread?.threadData?.createdAt < hasViewedRecent?.timestamp;
|
||||
|
||||
return (
|
||||
<SingleThreadParent
|
||||
sx={{
|
||||
@ -771,13 +753,17 @@ export const GroupMail = ({
|
||||
>
|
||||
<ThreadSingleLastMessageP>
|
||||
<ThreadSingleLastMessageSpanP>
|
||||
last message:{' '}
|
||||
{t('group:last_message', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
:{' '}
|
||||
</ThreadSingleLastMessageSpanP>
|
||||
{formatDate(thread?.created)}
|
||||
</ThreadSingleLastMessageP>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
setTimeout(() => {
|
||||
@ -834,6 +820,7 @@ export const GroupMail = ({
|
||||
</Box>
|
||||
</ThreadContainer>
|
||||
</ThreadContainerFullWidth>
|
||||
|
||||
<LoadingSnackbar
|
||||
open={isLoading}
|
||||
info={{
|
||||
|
@ -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 ShortUniqueId from 'short-unique-id';
|
||||
import {
|
||||
@ -8,7 +8,6 @@ import {
|
||||
InstanceFooter,
|
||||
InstanceListContainer,
|
||||
InstanceListHeader,
|
||||
NewMessageCloseImg,
|
||||
NewMessageHeaderP,
|
||||
NewMessageInputRow,
|
||||
NewMessageSendButton,
|
||||
@ -143,13 +142,13 @@ export const NewThread = ({
|
||||
isPrivate,
|
||||
}: NewMessageProps) => {
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const { show } = React.useContext(MyContext);
|
||||
const { show } = useContext(MyContext);
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [value, setValue] = useState('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [threadTitle, setThreadTitle] = useState<string>('');
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const editorRef = useRef(null);
|
||||
const theme = useTheme();
|
||||
const setEditorRef = (editorInstance) => {
|
||||
@ -203,31 +202,37 @@ export const NewThread = ({
|
||||
// if (!description) missingFields.push('subject')
|
||||
if (missingFields.length > 0) {
|
||||
const missingFieldsString = missingFields.join(', ');
|
||||
const errMsg = `Missing: ${missingFieldsString}`;
|
||||
errorMsg = errMsg; // TODO translate
|
||||
const errMsg = t('group:message.error.missing_field', {
|
||||
field: missingFieldsString,
|
||||
postProcess: 'capitalize',
|
||||
});
|
||||
errorMsg = errMsg;
|
||||
}
|
||||
|
||||
if (errorMsg) {
|
||||
// dispatch(
|
||||
// setNotification({
|
||||
// msg: errorMsg,
|
||||
// alertType: "error",
|
||||
// })
|
||||
// );
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
const htmlContent = editorRef.current.getHTML();
|
||||
|
||||
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>')
|
||||
throw new Error('Please provide a first message to the thread');
|
||||
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') {
|
||||
const errMsg = t('group:message.generic.provide_message', {
|
||||
postProcess: 'capitalize',
|
||||
});
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
const fee = await getFee('ARBITRARY');
|
||||
let feeToShow = fee.fee;
|
||||
|
||||
if (!isMessage) {
|
||||
feeToShow = +feeToShow * 2;
|
||||
}
|
||||
await show({
|
||||
message: 'Would you like to perform a ARBITRARY transaction?',
|
||||
message: t('group:question.perform_transaction', {
|
||||
action: 'ARBITRARY',
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
publishFee: feeToShow + ' QORT',
|
||||
});
|
||||
|
||||
@ -238,6 +243,7 @@ export const NewThread = ({
|
||||
delete reply.reply;
|
||||
}
|
||||
}
|
||||
|
||||
const mailObject: any = {
|
||||
createdAt: Date.now(),
|
||||
version: 1,
|
||||
@ -250,7 +256,10 @@ export const NewThread = ({
|
||||
const secretKey =
|
||||
isPrivate === false ? null : await getSecretKey(false, true);
|
||||
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) {
|
||||
@ -273,17 +282,18 @@ export const NewThread = ({
|
||||
isPrivate === false
|
||||
? threadToBase64
|
||||
: await encryptSingleFunc(threadToBase64, secretKey);
|
||||
let identifierThread = `grp-${groupInfo.groupId}-thread-${idThread}`;
|
||||
const identifierThread = `grp-${groupInfo.groupId}-thread-${idThread}`;
|
||||
await publishGroupEncryptedResource({
|
||||
identifier: identifierThread,
|
||||
encryptedData: encryptSingleThread,
|
||||
});
|
||||
|
||||
let identifierPost = `thmsg-${identifierThread}-${idMsg}`;
|
||||
const identifierPost = `thmsg-${identifierThread}-${idMsg}`;
|
||||
await publishGroupEncryptedResource({
|
||||
identifier: identifierPost,
|
||||
encryptedData: encryptSingleFirstPost,
|
||||
});
|
||||
|
||||
const dataToSaveToStorage = {
|
||||
name: myName,
|
||||
identifier: identifierThread,
|
||||
@ -292,6 +302,7 @@ export const NewThread = ({
|
||||
created: Date.now(),
|
||||
groupId: groupInfo.groupId,
|
||||
};
|
||||
|
||||
const dataToSaveToStoragePost = {
|
||||
name: myName,
|
||||
identifier: identifierPost,
|
||||
@ -300,6 +311,7 @@ export const NewThread = ({
|
||||
created: Date.now(),
|
||||
threadId: identifierThread,
|
||||
};
|
||||
|
||||
await saveTempPublish({ data: dataToSaveToStorage, key: 'thread' });
|
||||
await saveTempPublish({
|
||||
data: dataToSaveToStoragePost,
|
||||
@ -307,36 +319,32 @@ export const NewThread = ({
|
||||
});
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully created thread. It may take some time for the publish to propagate',
|
||||
message: t('group:message.success.thread_creation', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
|
||||
// dispatch(
|
||||
// setNotification({
|
||||
// msg: "Message sent",
|
||||
// alertType: "success",
|
||||
// })
|
||||
// );
|
||||
if (publishCallback) {
|
||||
publishCallback();
|
||||
}
|
||||
closeModal();
|
||||
} 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 messageToBase64 = await objectToBase64(mailObject);
|
||||
const encryptSinglePost =
|
||||
isPrivate === false
|
||||
? messageToBase64
|
||||
: 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 = {
|
||||
threadId: idThread,
|
||||
name: myName,
|
||||
@ -349,32 +357,17 @@ export const NewThread = ({
|
||||
data: dataToSaveToStoragePost,
|
||||
key: 'thread-post',
|
||||
});
|
||||
// await qortalRequest(multiplePublishMsg);
|
||||
// dispatch(
|
||||
// setNotification({
|
||||
// msg: "Message sent",
|
||||
// alertType: "success",
|
||||
// })
|
||||
// );
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully created post. It may take some time for the publish to propagate',
|
||||
message: t('group:message.success.post_creation', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
if (publishCallback) {
|
||||
publishCallback();
|
||||
}
|
||||
// messageCallback({
|
||||
// identifier,
|
||||
// id: identifier,
|
||||
// name,
|
||||
// service: MAIL_SERVICE_TYPE,
|
||||
// created: Date.now(),
|
||||
// ...mailObject,
|
||||
// });
|
||||
}
|
||||
|
||||
closeModal();
|
||||
} catch (error: any) {
|
||||
if (error?.message) {
|
||||
@ -393,6 +386,7 @@ export const NewThread = ({
|
||||
const sendMail = () => {
|
||||
publishQDNResource();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -407,7 +401,15 @@ export const NewThread = ({
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<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>
|
||||
|
||||
<ReusableModal
|
||||
@ -433,8 +435,15 @@ export const NewThread = ({
|
||||
}}
|
||||
>
|
||||
<NewMessageHeaderP>
|
||||
{isMessage ? 'Post Message' : 'New Thread'}
|
||||
{isMessage
|
||||
? t('core:action.post_message', {
|
||||
postProcess: 'capitalize',
|
||||
})
|
||||
: t('core:action.new.thread', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</NewMessageHeaderP>
|
||||
|
||||
<CloseContainer
|
||||
sx={{
|
||||
height: '40px',
|
||||
@ -448,6 +457,7 @@ export const NewThread = ({
|
||||
/>
|
||||
</CloseContainer>
|
||||
</InstanceListHeader>
|
||||
|
||||
<InstanceListContainer
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
@ -459,6 +469,7 @@ export const NewThread = ({
|
||||
{!isMessage && (
|
||||
<>
|
||||
<Spacer height="10px" />
|
||||
|
||||
<NewMessageInputRow>
|
||||
<Input
|
||||
id="standard-adornment-name"
|
||||
@ -516,28 +527,27 @@ export const NewThread = ({
|
||||
overrideMobile
|
||||
customEditorHeight="240px"
|
||||
/>
|
||||
|
||||
</Box>
|
||||
</InstanceListContainer>
|
||||
|
||||
<InstanceFooter
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
padding: '20px 42px',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
height: '90px',
|
||||
padding: '20px 42px',
|
||||
}}
|
||||
>
|
||||
<NewMessageSendButton onClick={sendMail}>
|
||||
{isSending && (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<CircularProgress
|
||||
@ -550,7 +560,13 @@ export const NewThread = ({
|
||||
)}
|
||||
|
||||
<NewMessageSendP>
|
||||
{isMessage ? 'Post' : 'Create Thread'}
|
||||
{isMessage
|
||||
? t('core:action.post', {
|
||||
postProcess: 'capitalize',
|
||||
})
|
||||
: t('core:action.create_thread', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</NewMessageSendP>
|
||||
|
||||
{isMessage ? (
|
||||
|
@ -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 {
|
||||
withReact,
|
||||
@ -96,7 +96,7 @@ interface ReadOnlySlateProps {
|
||||
content: any;
|
||||
mode?: string;
|
||||
}
|
||||
const ReadOnlySlate: React.FC<ReadOnlySlateProps> = ({ content, mode }) => {
|
||||
const ReadOnlySlate: FC<ReadOnlySlateProps> = ({ content, mode }) => {
|
||||
const [load, setLoad] = useState(false);
|
||||
const editor = useMemo(() => withReact(createEditor()), []);
|
||||
const value = useMemo(() => content, [content]);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import { FC } from 'react';
|
||||
import { Box, Modal, useTheme } from '@mui/material';
|
||||
|
||||
interface MyModalProps {
|
||||
@ -9,7 +9,7 @@ interface MyModalProps {
|
||||
customStyles?: any;
|
||||
}
|
||||
|
||||
export const ReusableModal: React.FC<MyModalProps> = ({
|
||||
export const ReusableModal: FC<MyModalProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
onSubmit,
|
||||
|
@ -118,27 +118,6 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
|
||||
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 && (
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -1,10 +1,4 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
@ -18,7 +12,6 @@ import {
|
||||
ComposeP,
|
||||
GroupContainer,
|
||||
GroupNameP,
|
||||
MailIconImg,
|
||||
ShowMessageReturnButton,
|
||||
SingleThreadParent,
|
||||
ThreadContainer,
|
||||
@ -222,7 +215,7 @@ export const Thread = ({
|
||||
}
|
||||
};
|
||||
|
||||
const getMailMessages = React.useCallback(
|
||||
const getMailMessages = useCallback(
|
||||
async (groupInfo: any, before, after, isReverse, groupId) => {
|
||||
try {
|
||||
setTempPublishedList([]);
|
||||
@ -328,7 +321,7 @@ export const Thread = ({
|
||||
},
|
||||
[messages, secretKey]
|
||||
);
|
||||
const getMessages = React.useCallback(async () => {
|
||||
const getMessages = useCallback(async () => {
|
||||
if (
|
||||
!currentThread ||
|
||||
(!secretKey && isPrivate) ||
|
||||
@ -410,7 +403,7 @@ export const Thread = ({
|
||||
|
||||
const interval = useRef<any>(null);
|
||||
|
||||
const checkNewMessages = React.useCallback(
|
||||
const checkNewMessages = useCallback(
|
||||
async (groupInfo: any) => {
|
||||
try {
|
||||
let threadId = groupInfo.threadId;
|
||||
@ -494,7 +487,7 @@ export const Thread = ({
|
||||
firstMount.current = true;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
subscribeToEvent('threadFetchMode', threadFetchModeFunc);
|
||||
|
||||
return () => {
|
||||
@ -656,6 +649,7 @@ export const Thread = ({
|
||||
<div ref={threadBeginningRef} />
|
||||
<ThreadContainer>
|
||||
<Spacer height={'30px'} />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
@ -715,6 +709,7 @@ export const Thread = ({
|
||||
>
|
||||
{t('core:page.previous', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
textTransformation: 'capitalize',
|
||||
@ -733,6 +728,7 @@ export const Thread = ({
|
||||
>
|
||||
{t('core:page.next', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
textTransformation: 'capitalize',
|
||||
@ -1006,6 +1002,7 @@ export const Thread = ({
|
||||
>
|
||||
{t('core:page.first', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
textTransformation: 'capitalize',
|
||||
@ -1024,6 +1021,7 @@ export const Thread = ({
|
||||
>
|
||||
{t('core:page.previous', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
textTransformation: 'capitalize',
|
||||
@ -1042,6 +1040,7 @@ export const Thread = ({
|
||||
>
|
||||
{t('core:page.next', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
textTransformation: 'capitalize',
|
||||
@ -1061,12 +1060,14 @@ export const Thread = ({
|
||||
{t('core:page.last', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Spacer height="30px" />
|
||||
</Box>
|
||||
|
||||
<div ref={containerRef} />
|
||||
</ThreadContainer>
|
||||
</ThreadContainerFullWidth>
|
||||
|
||||
<LoadingSnackbar
|
||||
open={isLoading}
|
||||
info={{
|
||||
|
@ -9,20 +9,12 @@ import {
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ChatGroup } from '../Chat/ChatGroup';
|
||||
import { CreateCommonSecret } from '../Chat/CreateCommonSecret';
|
||||
import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
|
||||
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
|
||||
import CampaignIcon from '@mui/icons-material/Campaign';
|
||||
import { AddGroup } from './AddGroup';
|
||||
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
|
||||
import CreateIcon from '@mui/icons-material/Create';
|
||||
import {
|
||||
AuthenticatedContainerInnerRight,
|
||||
@ -52,7 +44,6 @@ import {
|
||||
import { RequestQueueWithPromise } from '../../utils/queue/queue';
|
||||
import { WebSocketActive } from './WebsocketActive';
|
||||
import { useMessageQueue } from '../../MessageQueueContext';
|
||||
import { ContextMenu } from '../ContextMenu';
|
||||
import { HomeDesktop } from './HomeDesktop';
|
||||
import { IconWrapper } from '../Desktop/DesktopFooter';
|
||||
import { DesktopHeader } from '../Desktop/DesktopHeader';
|
||||
@ -63,7 +54,6 @@ import { HubsIcon } from '../../assets/Icons/HubsIcon';
|
||||
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
|
||||
import { formatEmailDate } from './QMailMessages';
|
||||
import { AdminSpace } from '../Chat/AdminSpace';
|
||||
|
||||
import {
|
||||
addressInfoControllerAtom,
|
||||
groupAnnouncementsAtom,
|
||||
@ -77,9 +67,6 @@ import {
|
||||
timestampEnterDataAtom,
|
||||
} from '../../atoms/global';
|
||||
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 { WalletsAppWrapper } from './WalletsAppWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -92,6 +79,7 @@ export const getPublishesFromAdmins = async (admins: string[], groupId) => {
|
||||
groupId
|
||||
}&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('network error');
|
||||
}
|
||||
@ -100,9 +88,11 @@ export const getPublishesFromAdmins = async (admins: string[], groupId) => {
|
||||
const filterId = adminData.filter(
|
||||
(data: any) => data.identifier === `symmetric-qchat-group-${groupId}`
|
||||
);
|
||||
|
||||
if (filterId?.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sortedData = filterId.sort((a: any, b: any) => {
|
||||
// Get the most recent date for both a and b
|
||||
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];
|
||||
};
|
||||
|
||||
interface GroupProps {
|
||||
myAddress: string;
|
||||
isFocused: boolean;
|
||||
userInfo: any;
|
||||
balance: number;
|
||||
isFocused: boolean;
|
||||
myAddress: string;
|
||||
userInfo: any;
|
||||
}
|
||||
|
||||
export const timeDifferenceForNotificationChats = 900000;
|
||||
|
||||
export const requestQueueMemberNames = 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) => {
|
||||
// const validApi = await findUsableApi();
|
||||
|
||||
const response = await fetch(
|
||||
`${getBaseApiReact()}/groups/members/${groupNumber}?limit=0&onlyAdmins=true`
|
||||
);
|
||||
@ -422,27 +406,24 @@ export const Group = ({
|
||||
|
||||
const [chatMode, setChatMode] = useState('groups');
|
||||
const [newChat, setNewChat] = useState(false);
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const [isLoadingNotifyAdmin, setIsLoadingNotifyAdmin] = React.useState(false);
|
||||
const [isLoadingGroups, setIsLoadingGroups] = React.useState(true);
|
||||
const [isLoadingGroup, setIsLoadingGroup] = React.useState(false);
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const [isLoadingNotifyAdmin, setIsLoadingNotifyAdmin] = useState(false);
|
||||
const [isLoadingGroups, setIsLoadingGroups] = useState(true);
|
||||
const [isLoadingGroup, setIsLoadingGroup] = useState(false);
|
||||
const [firstSecretKeyInCreation, setFirstSecretKeyInCreation] =
|
||||
React.useState(false);
|
||||
const [groupSection, setGroupSection] = React.useState('home');
|
||||
useState(false);
|
||||
const [groupSection, setGroupSection] = useState('home');
|
||||
const [groupAnnouncements, setGroupAnnouncements] = useAtom(
|
||||
groupAnnouncementsAtom
|
||||
);
|
||||
|
||||
const [defaultThread, setDefaultThread] = React.useState(null);
|
||||
const [isOpenDrawer, setIsOpenDrawer] = React.useState(false);
|
||||
const [defaultThread, setDefaultThread] = useState(null);
|
||||
const [isOpenDrawer, setIsOpenDrawer] = useState(false);
|
||||
const setIsOpenBlockedUserModal = useSetAtom(isOpenBlockedModalAtom);
|
||||
|
||||
const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false);
|
||||
const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState('');
|
||||
const [drawerMode, setDrawerMode] = React.useState('groups');
|
||||
const [hideCommonKeyPopup, setHideCommonKeyPopup] = useState(false);
|
||||
const [isLoadingGroupMessage, setIsLoadingGroupMessage] = useState('');
|
||||
const [drawerMode, setDrawerMode] = useState('groups');
|
||||
const setMutedGroups = useSetAtom(mutedGroupsAtom);
|
||||
|
||||
const [mobileViewMode, setMobileViewMode] = useState('home');
|
||||
const [mobileViewModeKeepOpen, setMobileViewModeKeepOpen] = useState('');
|
||||
const isFocusedRef = useRef(true);
|
||||
@ -531,7 +512,10 @@ export const Group = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -557,7 +541,10 @@ export const Group = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -586,7 +573,10 @@ export const Group = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -1106,7 +1096,10 @@ export const Group = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
});
|
||||
setInfoSnack({
|
||||
|
@ -16,7 +16,8 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
try {
|
||||
const fee = await getFee('GROUP_INVITE');
|
||||
await show({
|
||||
message: t('group:question.group_invite', {
|
||||
message: t('group:question.perform_transaction', {
|
||||
action: 'GROUP_INVITE',
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
publishFee: fee.fee + ' QORT',
|
||||
@ -97,16 +98,16 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
label={t('group:invitation_expiry', { postProcess: 'capitalize' })}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={10800}>{t('core.time.hour', { count: 3 })}</MenuItem>
|
||||
<MenuItem value={21600}>{t('core.time.hour', { count: 6 })}</MenuItem>
|
||||
<MenuItem value={43200}>{t('core.time.hour', { count: 12 })}</MenuItem>
|
||||
<MenuItem value={86400}>{t('core.time.day', { count: 1 })}</MenuItem>
|
||||
<MenuItem value={259200}>{t('core.time.day', { count: 3 })}</MenuItem>
|
||||
<MenuItem value={432000}>{t('core.time.day', { count: 5 })}</MenuItem>
|
||||
<MenuItem value={604800}>{t('core.time.day', { count: 7 })}</MenuItem>
|
||||
<MenuItem value={864000}>{t('core.time.day', { count: 10 })}</MenuItem>
|
||||
<MenuItem value={1296000}>{t('core.time.day', { count: 15 })}</MenuItem>
|
||||
<MenuItem value={2592000}>{t('core.time.day', { count: 30 })}</MenuItem>
|
||||
<MenuItem value={10800}>{t('core:time.hour', { count: 3 })}</MenuItem>
|
||||
<MenuItem value={21600}>{t('core:time.hour', { count: 6 })}</MenuItem>
|
||||
<MenuItem value={43200}>{t('core:time.hour', { count: 12 })}</MenuItem>
|
||||
<MenuItem value={86400}>{t('core:time.day', { count: 1 })}</MenuItem>
|
||||
<MenuItem value={259200}>{t('core:time.day', { count: 3 })}</MenuItem>
|
||||
<MenuItem value={432000}>{t('core:time.day', { count: 5 })}</MenuItem>
|
||||
<MenuItem value={604800}>{t('core:time.day', { count: 7 })}</MenuItem>
|
||||
<MenuItem value={864000}>{t('core:time.day', { count: 10 })}</MenuItem>
|
||||
<MenuItem value={1296000}>{t('core:time.day', { count: 15 })}</MenuItem>
|
||||
<MenuItem value={2592000}>{t('core:time.day', { count: 30 })}</MenuItem>
|
||||
</Select>
|
||||
<Spacer height="20px" />
|
||||
<LoadingButton
|
||||
|
@ -54,7 +54,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
const [bans, setBans] = useState([]);
|
||||
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 listRef = useRef();
|
||||
const listRef = useRef(null);
|
||||
const [isLoadingUnban, setIsLoadingUnban] = useState(false);
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
|
||||
@ -88,7 +88,10 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
try {
|
||||
const fee = await getFee('CANCEL_GROUP_BAN');
|
||||
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',
|
||||
});
|
||||
setIsLoadingUnban(true);
|
||||
@ -165,13 +168,13 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '325px',
|
||||
height: '250px',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
height: '250px',
|
||||
padding: '10px',
|
||||
width: '325px',
|
||||
}}
|
||||
>
|
||||
<LoadingButton
|
||||
@ -214,12 +217,12 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
<p>{t('group:ban_list', { postProcess: 'capitalize' })}</p>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
height: '500px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 1,
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AutoSizer>
|
||||
|
@ -51,6 +51,11 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
import { getFee } from '../../background';
|
||||
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 function utf8ToBase64(inputString: string): string {
|
||||
@ -65,13 +70,11 @@ export function utf8ToBase64(inputString: string): string {
|
||||
return base64String;
|
||||
}
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
export function getGroupId(str) {
|
||||
const match = str.match(/group-(\d+)-/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
const THIRTY_MINUTES = 30 * 60 * 1000; // 30 minutes in milliseconds
|
||||
|
||||
export const ListOfGroupPromotions = () => {
|
||||
const [popoverAnchor, setPopoverAnchor] = useState(null);
|
||||
const [openPopoverIndex, setOpenPopoverIndex] = useState(null);
|
||||
@ -98,7 +101,8 @@ export const ListOfGroupPromotions = () => {
|
||||
const setTxList = useSetAtom(txListAtom);
|
||||
|
||||
const theme = useTheme();
|
||||
const listRef = useRef();
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const listRef = useRef(null);
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: promotions.length,
|
||||
getItemKey: React.useCallback(
|
||||
@ -120,6 +124,7 @@ export const ListOfGroupPromotions = () => {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getPromotions = useCallback(async () => {
|
||||
try {
|
||||
setPromotionTimeInterval(Date.now());
|
||||
@ -135,6 +140,7 @@ export const ListOfGroupPromotions = () => {
|
||||
let data: any[] = [];
|
||||
const uniqueGroupIds = new Set();
|
||||
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const getPromos = responseData?.map(async (promo: any) => {
|
||||
if (promo?.size < 200 && promo.created > oneWeekAgo) {
|
||||
const name = await requestQueuePromos.enqueue(async () => {
|
||||
@ -213,6 +219,7 @@ export const ListOfGroupPromotions = () => {
|
||||
setPopoverAnchor(null);
|
||||
setOpenPopoverIndex(null);
|
||||
};
|
||||
|
||||
const publishPromo = async () => {
|
||||
try {
|
||||
setIsLoadingPublish(true);
|
||||
@ -235,9 +242,12 @@ export const ListOfGroupPromotions = () => {
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
}); // TODO translate
|
||||
});
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
@ -264,7 +274,10 @@ export const ListOfGroupPromotions = () => {
|
||||
const groupId = group.groupId;
|
||||
const fee = await getFee('JOIN_GROUP');
|
||||
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',
|
||||
});
|
||||
setIsLoadingJoinGroup(true);
|
||||
@ -331,6 +344,7 @@ export const ListOfGroupPromotions = () => {
|
||||
});
|
||||
setIsLoadingJoinGroup(false);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingJoinGroup(false);
|
||||
}
|
||||
@ -339,30 +353,30 @@ export const ListOfGroupPromotions = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
marginTop: '20px',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
marginTop: '20px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
width: '100%',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
alignSelf: isExpanded && 'flex-start',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
padding: `0px ${isExpanded ? '24px' : '20px'}`,
|
||||
gap: '10px',
|
||||
justifyContent: 'flex-start',
|
||||
alignSelf: isExpanded && 'flex-start',
|
||||
padding: `0px ${isExpanded ? '24px' : '20px'}`,
|
||||
}}
|
||||
onClick={() => setIsExpanded((prev) => !prev)}
|
||||
>
|
||||
@ -374,6 +388,7 @@ export const ListOfGroupPromotions = () => {
|
||||
Group promotions{' '}
|
||||
{promotions.length > 0 && ` (${promotions.length})`}
|
||||
</Typography>
|
||||
|
||||
{isExpanded ? (
|
||||
<ExpandLessIcon
|
||||
sx={{
|
||||
@ -400,19 +415,19 @@ export const ListOfGroupPromotions = () => {
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: '750px',
|
||||
maxWidth: '90%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
maxWidth: '90%',
|
||||
padding: '0px 20px',
|
||||
width: '750px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
|
@ -18,6 +18,7 @@ import { getNameInfo } from './Group';
|
||||
import { getFee } from '../../background';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const getMemberInvites = async (groupNumber) => {
|
||||
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 [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
|
||||
const [isLoadingCancelInvite, setIsLoadingCancelInvite] = useState(false);
|
||||
|
||||
const listRef = useRef();
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const listRef = useRef(null);
|
||||
|
||||
const getInvites = async (groupId) => {
|
||||
try {
|
||||
@ -90,13 +91,18 @@ export const ListOfInvites = ({
|
||||
|
||||
const handleCancelInvitation = async (address) => {
|
||||
try {
|
||||
// TODO translate
|
||||
const fee = await getFee('CANCEL_GROUP_INVITE');
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
setIsLoadingCancelInvite(true);
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage('cancelInvitationToGroup', {
|
||||
@ -107,8 +113,9 @@ export const ListOfInvites = ({
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully canceled invitation. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.invitation_cancellation', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
handlePopoverClose();
|
||||
@ -126,13 +133,18 @@ export const ListOfInvites = ({
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: 'error',
|
||||
message: error.message || 'An error occurred',
|
||||
message:
|
||||
error.message ||
|
||||
t('core:message.error.generic', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingCancelInvite(false);
|
||||
}
|
||||
@ -168,13 +180,13 @@ export const ListOfInvites = ({
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '325px',
|
||||
height: '250px',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
height: '250px',
|
||||
padding: '10px',
|
||||
width: '325px',
|
||||
}}
|
||||
>
|
||||
<LoadingButton
|
||||
@ -183,10 +195,13 @@ export const ListOfInvites = ({
|
||||
variant="contained"
|
||||
onClick={() => handleCancelInvitation(member?.invitee)}
|
||||
>
|
||||
Cancel Invitation
|
||||
{t('core:action.cancel_invitation', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
</Popover>
|
||||
|
||||
<ListItemButton
|
||||
onClick={(event) => handlePopoverOpen(event, index)}
|
||||
>
|
||||
@ -200,6 +215,7 @@ export const ListOfInvites = ({
|
||||
}
|
||||
/>
|
||||
</ListItemAvatar>
|
||||
|
||||
<ListItemText primary={member?.name || member?.invitee} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
@ -211,15 +227,19 @@ export const ListOfInvites = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Invitees list</p>
|
||||
<p>
|
||||
{t('group:invitees_list', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
height: '500px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 1,
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AutoSizer>
|
||||
|
@ -15,11 +15,12 @@ import {
|
||||
List,
|
||||
} from 'react-virtualized';
|
||||
import { getNameInfo } from './Group';
|
||||
import { getBaseApi, getFee } from '../../background';
|
||||
import { getFee } from '../../background';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import { txListAtom } from '../../atoms/global';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const getMemberInvites = async (groupNumber) => {
|
||||
const response = await fetch(
|
||||
@ -59,11 +60,11 @@ export const ListOfJoinRequests = ({
|
||||
}) => {
|
||||
const [invites, setInvites] = useState([]);
|
||||
const [txList, setTxList] = useAtom(txListAtom);
|
||||
|
||||
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 listRef = useRef();
|
||||
const listRef = useRef(null);
|
||||
const [isLoadingAccept, setIsLoadingAccept] = useState(false);
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
|
||||
const getInvites = async (groupId) => {
|
||||
try {
|
||||
@ -93,12 +94,18 @@ export const ListOfJoinRequests = ({
|
||||
|
||||
const handleAcceptJoinRequest = async (address) => {
|
||||
try {
|
||||
const fee = await getFee('GROUP_INVITE'); // TODO translate
|
||||
const fee = await getFee('GROUP_INVITE');
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
setIsLoadingAccept(true);
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage('inviteToGroup', {
|
||||
@ -111,19 +118,23 @@ export const ListOfJoinRequests = ({
|
||||
setIsLoadingAccept(false);
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully accepted join request. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success,group_join', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
handlePopoverClose();
|
||||
res(response);
|
||||
|
||||
setTxList((prev) => [
|
||||
{
|
||||
...response,
|
||||
type: 'join-request-accept',
|
||||
label: `Accepted join request: awaiting confirmation`,
|
||||
labelDone: `User successfully joined!`,
|
||||
label: t('group:message.success,invitation_request', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
labelDone: t('group:message.success,user_joined', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
done: false,
|
||||
groupId,
|
||||
qortalAddress: address,
|
||||
@ -144,13 +155,16 @@ export const ListOfJoinRequests = ({
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: 'error',
|
||||
message: error?.message || 'An error occurred',
|
||||
message:
|
||||
error?.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' }),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingAccept(false);
|
||||
}
|
||||
@ -158,13 +172,15 @@ export const ListOfJoinRequests = ({
|
||||
|
||||
const rowRenderer = ({ index, key, parent, style }) => {
|
||||
const member = invites[index];
|
||||
const findJoinRequsetInTxList = txList?.find(
|
||||
const findJoinRequestInTxList = txList?.find(
|
||||
(tx) =>
|
||||
tx?.groupId === groupId &&
|
||||
tx?.qortalAddress === member?.joiner &&
|
||||
tx?.type === 'join-request-accept'
|
||||
);
|
||||
if (findJoinRequsetInTxList) return null;
|
||||
|
||||
if (findJoinRequestInTxList) return null;
|
||||
|
||||
return (
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
@ -207,10 +223,11 @@ export const ListOfJoinRequests = ({
|
||||
variant="contained"
|
||||
onClick={() => handleAcceptJoinRequest(member?.joiner)}
|
||||
>
|
||||
Accept
|
||||
{t('core:action.accept', { postProcess: 'capitalize' })}
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
</Popover>
|
||||
|
||||
<ListItemButton
|
||||
onClick={(event) => handlePopoverOpen(event, index)}
|
||||
>
|
||||
@ -235,7 +252,7 @@ export const ListOfJoinRequests = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Join request list</p>
|
||||
<p>{t('core:list.join_request', { postProcess: 'capitalize' })}</p>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { getFee } from '../../background';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
@ -41,7 +42,8 @@ const ListOfMembers = ({
|
||||
const [isLoadingMakeAdmin, setIsLoadingMakeAdmin] = useState(false);
|
||||
const [isLoadingRemoveAdmin, setIsLoadingRemoveAdmin] = useState(false);
|
||||
const theme = useTheme();
|
||||
const listRef = useRef();
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const listRef = useRef(null);
|
||||
|
||||
const handlePopoverOpen = (event, index) => {
|
||||
setPopoverAnchor(event.currentTarget);
|
||||
@ -57,7 +59,10 @@ const ListOfMembers = ({
|
||||
try {
|
||||
const fee = await getFee('GROUP_KICK');
|
||||
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',
|
||||
});
|
||||
|
||||
@ -72,8 +77,9 @@ const ListOfMembers = ({
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully kicked member from group. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.group_kick', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
handlePopoverClose();
|
||||
@ -90,7 +96,9 @@ const ListOfMembers = ({
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: 'error',
|
||||
message: error.message || 'An error occurred',
|
||||
message:
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' }),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
@ -104,12 +112,18 @@ const ListOfMembers = ({
|
||||
};
|
||||
const handleBan = async (address) => {
|
||||
try {
|
||||
const fee = await getFee('GROUP_BAN'); // TODO translate
|
||||
const fee = await getFee('GROUP_BAN');
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
setIsLoadingBan(true);
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage('banFromGroup', {
|
||||
@ -121,8 +135,9 @@ const ListOfMembers = ({
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully banned member from group. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.group_ban', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
handlePopoverClose();
|
||||
@ -139,13 +154,16 @@ const ListOfMembers = ({
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: 'error',
|
||||
message: error.message || 'An error occurred',
|
||||
message:
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' }),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingBan(false);
|
||||
}
|
||||
@ -155,7 +173,10 @@ const ListOfMembers = ({
|
||||
try {
|
||||
const fee = await getFee('ADD_GROUP_ADMIN');
|
||||
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',
|
||||
});
|
||||
setIsLoadingMakeAdmin(true);
|
||||
@ -169,8 +190,9 @@ const ListOfMembers = ({
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully made member an admin. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.group_member_admin', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
handlePopoverClose();
|
||||
@ -187,13 +209,16 @@ const ListOfMembers = ({
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: 'error',
|
||||
message: error.message || 'An error occurred',
|
||||
message:
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' }),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingMakeAdmin(false);
|
||||
}
|
||||
@ -203,7 +228,10 @@ const ListOfMembers = ({
|
||||
try {
|
||||
const fee = await getFee('REMOVE_GROUP_ADMIN');
|
||||
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',
|
||||
});
|
||||
setIsLoadingRemoveAdmin(true);
|
||||
@ -217,8 +245,9 @@ const ListOfMembers = ({
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully removed member as an admin. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.group_remove_member', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
handlePopoverClose();
|
||||
@ -235,13 +264,16 @@ const ListOfMembers = ({
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: 'error',
|
||||
message: error.message || 'An error occurred',
|
||||
message:
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' }),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingRemoveAdmin(false);
|
||||
}
|
||||
@ -276,13 +308,13 @@ const ListOfMembers = ({
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '325px',
|
||||
height: '250px',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
height: '250px',
|
||||
padding: '10px',
|
||||
width: '325px',
|
||||
}}
|
||||
>
|
||||
{isOwner && (
|
||||
@ -293,48 +325,49 @@ const ListOfMembers = ({
|
||||
variant="contained"
|
||||
onClick={() => handleKick(member?.member)}
|
||||
>
|
||||
Kick member from group
|
||||
{t('group:action.kick_member', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</LoadingButton>
|
||||
|
||||
<LoadingButton
|
||||
loading={isLoadingBan}
|
||||
loadingPosition="start"
|
||||
variant="contained"
|
||||
onClick={() => handleBan(member?.member)}
|
||||
>
|
||||
Ban member from group
|
||||
{t('group:action.ban', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</LoadingButton>
|
||||
|
||||
<LoadingButton
|
||||
loading={isLoadingMakeAdmin}
|
||||
loadingPosition="start"
|
||||
variant="contained"
|
||||
onClick={() => makeAdmin(member?.member)}
|
||||
>
|
||||
Make an admin
|
||||
{t('group:action.make_admin', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</LoadingButton>
|
||||
|
||||
<LoadingButton
|
||||
loading={isLoadingRemoveAdmin}
|
||||
loadingPosition="start"
|
||||
variant="contained"
|
||||
onClick={() => removeAdmin(member?.member)}
|
||||
>
|
||||
Remove as admin
|
||||
{t('group:action.remove_admin', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</LoadingButton>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Popover>
|
||||
<ListItem
|
||||
key={member?.member}
|
||||
// secondaryAction={
|
||||
// <Checkbox
|
||||
// edge="end"
|
||||
// onChange={handleToggle(value)}
|
||||
// checked={checked.indexOf(value) !== -1}
|
||||
// inputProps={{ 'aria-labelledby': labelId }}
|
||||
// />
|
||||
// }
|
||||
disablePadding
|
||||
>
|
||||
|
||||
<ListItem key={member?.member} disablePadding>
|
||||
<ListItemButton
|
||||
onClick={(event) => handlePopoverOpen(event, index)}
|
||||
>
|
||||
@ -348,6 +381,7 @@ const ListOfMembers = ({
|
||||
}
|
||||
/>
|
||||
</ListItemAvatar>
|
||||
|
||||
<ListItemText
|
||||
id={''}
|
||||
primary={member?.name || member?.member}
|
||||
@ -359,7 +393,9 @@ const ListOfMembers = ({
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
>
|
||||
Admin
|
||||
{t('core:admin', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
</ListItemButton>
|
||||
@ -372,28 +408,31 @@ const ListOfMembers = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Member list</p>
|
||||
<p>
|
||||
{t('core:list.member', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
height: '500px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 1,
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
ref={listRef}
|
||||
width={width}
|
||||
deferredMeasurementCache={cache}
|
||||
height={height}
|
||||
ref={listRef}
|
||||
rowCount={members.length}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
// onScroll={handleScroll}
|
||||
deferredMeasurementCache={cache}
|
||||
width={width}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
@ -9,10 +9,12 @@ import { Box, Typography } from '@mui/material';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { CustomLoader } from '../../common/CustomLoader';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const ListOfThreadPostsWatched = () => {
|
||||
const [posts, setPosts] = React.useState([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
|
||||
const getPosts = async () => {
|
||||
try {
|
||||
@ -42,34 +44,38 @@ export const ListOfThreadPostsWatched = () => {
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred'); // TODO translate
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
getPosts();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '322px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '0px 20px',
|
||||
width: '322px',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -78,8 +84,12 @@ export const ListOfThreadPostsWatched = () => {
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
New Thread Posts:
|
||||
{t('group:thread_posts', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
:
|
||||
</Typography>
|
||||
|
||||
<Spacer height="10px" />
|
||||
</Box>
|
||||
|
||||
@ -97,9 +107,9 @@ export const ListOfThreadPostsWatched = () => {
|
||||
{loading && posts.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<CustomLoader />
|
||||
@ -108,11 +118,11 @@ export const ListOfThreadPostsWatched = () => {
|
||||
{!loading && posts.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -122,7 +132,9 @@ export const ListOfThreadPostsWatched = () => {
|
||||
color: 'rgba(255, 255, 255, 0.2)',
|
||||
}}
|
||||
>
|
||||
Nothing to display
|
||||
{t('group:message.generic.no_display', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
@ -130,11 +142,11 @@ export const ListOfThreadPostsWatched = () => {
|
||||
<List
|
||||
className="scrollable-container"
|
||||
sx={{
|
||||
width: '100%',
|
||||
maxWidth: 360,
|
||||
bgcolor: 'background.paper',
|
||||
maxHeight: '300px',
|
||||
maxWidth: 360,
|
||||
overflow: 'auto',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{posts?.map((post) => {
|
||||
|
@ -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 Dialog from '@mui/material/Dialog';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
@ -25,6 +35,7 @@ import { Spacer } from '../../common/Spacer';
|
||||
import InsertLinkIcon from '@mui/icons-material/InsertLink';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { txListAtom } from '../../atoms/global';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function a11yProps(index: number) {
|
||||
return {
|
||||
@ -33,37 +44,35 @@ function a11yProps(index: number) {
|
||||
};
|
||||
}
|
||||
|
||||
const Transition = React.forwardRef(function Transition(
|
||||
const Transition = forwardRef(function Transition(
|
||||
props: TransitionProps & {
|
||||
children: React.ReactElement;
|
||||
children: ReactElement;
|
||||
},
|
||||
ref: React.Ref<unknown>
|
||||
ref: Ref<unknown>
|
||||
) {
|
||||
return <Slide direction="up" ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
export const ManageMembers = ({
|
||||
address,
|
||||
open,
|
||||
setOpen,
|
||||
selectedGroup,
|
||||
|
||||
isAdmin,
|
||||
isOwner,
|
||||
}) => {
|
||||
const [membersWithNames, setMembersWithNames] = React.useState([]);
|
||||
const [tab, setTab] = React.useState('create');
|
||||
const [value, setValue] = React.useState(0);
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const [isLoadingMembers, setIsLoadingMembers] = React.useState(false);
|
||||
const [isLoadingLeave, setIsLoadingLeave] = React.useState(false);
|
||||
const [groupInfo, setGroupInfo] = React.useState(null);
|
||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
const [membersWithNames, setMembersWithNames] = useState([]);
|
||||
const [value, setValue] = useState(0);
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const [isLoadingMembers, setIsLoadingMembers] = useState(false);
|
||||
const [isLoadingLeave, setIsLoadingLeave] = useState(false);
|
||||
const [groupInfo, setGroupInfo] = useState(null);
|
||||
const handleChange = (event: SyntheticEvent, newValue: number) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
const theme = useTheme();
|
||||
const { show } = React.useContext(MyContext);
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const { show } = useContext(MyContext);
|
||||
const setTxList = useSetAtom(txListAtom);
|
||||
|
||||
const handleClose = () => {
|
||||
@ -75,7 +84,10 @@ export const ManageMembers = ({
|
||||
setIsLoadingLeave(true);
|
||||
const fee = await getFee('LEAVE_GROUP');
|
||||
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',
|
||||
});
|
||||
|
||||
@ -90,8 +102,14 @@ export const ManageMembers = ({
|
||||
{
|
||||
...response,
|
||||
type: 'leave-group',
|
||||
label: `Left Group ${selectedGroup?.groupName}: awaiting confirmation`,
|
||||
labelDone: `Left Group ${selectedGroup?.groupName}: success!`,
|
||||
label: t('group:message.success.group_leave_name', {
|
||||
group_name: selectedGroup?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
labelDone: t('group:message.success.group_leave_label', {
|
||||
group_name: selectedGroup?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
done: false,
|
||||
groupId: selectedGroup?.groupId,
|
||||
},
|
||||
@ -100,8 +118,9 @@ export const ManageMembers = ({
|
||||
res(response);
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully requested to leave group. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.group_leave', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
return;
|
||||
@ -109,7 +128,10 @@ export const ManageMembers = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred'); // TODO translate
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -119,7 +141,7 @@ export const ManageMembers = ({
|
||||
}
|
||||
};
|
||||
|
||||
const getMembersWithNames = React.useCallback(async (groupId) => {
|
||||
const getMembersWithNames = useCallback(async (groupId) => {
|
||||
try {
|
||||
setIsLoadingMembers(true);
|
||||
const res = await getGroupMembers(groupId);
|
||||
@ -139,6 +161,7 @@ export const ManageMembers = ({
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getGroupInfo = async (groupId) => {
|
||||
try {
|
||||
const response = await fetch(`${getBaseApiReact()}/groups/${groupId}`);
|
||||
@ -149,7 +172,7 @@ export const ManageMembers = ({
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (selectedGroup?.groupId) {
|
||||
getMembers(selectedGroup?.groupId);
|
||||
getGroupInfo(selectedGroup?.groupId);
|
||||
@ -160,7 +183,7 @@ export const ManageMembers = ({
|
||||
setValue(4);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
subscribeToEvent('openGroupJoinRequest', openGroupJoinRequestFunc);
|
||||
|
||||
return () => {
|
||||
@ -169,7 +192,7 @@ export const ManageMembers = ({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Fragment>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={open}
|
||||
@ -184,18 +207,20 @@ export const ManageMembers = ({
|
||||
>
|
||||
<Toolbar>
|
||||
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
|
||||
Manage Members
|
||||
{t('group:action.manage_members', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
onClick={handleClose}
|
||||
aria-label="close"
|
||||
color="inherit"
|
||||
edge="start"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: theme.palette.background.default,
|
||||
@ -284,12 +309,19 @@ export const ManageMembers = ({
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography>GroupId: {groupInfo?.groupId}</Typography>
|
||||
|
||||
<Typography>GroupName: {groupInfo?.groupName}</Typography>
|
||||
<Typography>
|
||||
{t('group:group.id', { postProcess: 'capitalize' })}:{' '}
|
||||
{groupInfo?.groupId}
|
||||
</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>
|
||||
|
||||
<ButtonBase
|
||||
@ -301,7 +333,11 @@ export const ManageMembers = ({
|
||||
await navigator.clipboard.writeText(link);
|
||||
}}
|
||||
>
|
||||
<InsertLinkIcon /> <Typography>Join Group Link</Typography>
|
||||
<InsertLinkIcon />
|
||||
|
||||
<Typography>
|
||||
{t('group:join_link', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
|
||||
@ -315,10 +351,11 @@ export const ManageMembers = ({
|
||||
variant="contained"
|
||||
onClick={handleLeaveGroup}
|
||||
>
|
||||
Leave Group
|
||||
{t('group:action.leave_group', { postProcess: 'capitalize' })}
|
||||
</LoadingButton>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{value === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
@ -331,7 +368,7 @@ export const ManageMembers = ({
|
||||
variant="contained"
|
||||
onClick={() => getMembersWithNames(selectedGroup?.groupId)}
|
||||
>
|
||||
Load members with names
|
||||
{t('group:action.load_members', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
|
||||
<Spacer height="10px" />
|
||||
@ -347,6 +384,7 @@ export const ManageMembers = ({
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{value === 1 && (
|
||||
<Box
|
||||
sx={{
|
||||
@ -426,10 +464,12 @@ export const ManageMembers = ({
|
||||
<LoadingSnackbar
|
||||
open={isLoadingMembers}
|
||||
info={{
|
||||
message: 'Loading member list with names... please wait.',
|
||||
message: t('group:message.generic.loading_members', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
@ -11,12 +11,12 @@ import MailIcon from '@mui/icons-material/Mail';
|
||||
import MailOutlineIcon from '@mui/icons-material/MailOutline';
|
||||
import { executeEvent } from '../../utils/events';
|
||||
import { CustomLoader } from '../../common/CustomLoader';
|
||||
|
||||
import { mailsAtom, qMailLastEnteredTimestampAtom } from '../../atoms/global';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
import MarkEmailUnreadIcon from '@mui/icons-material/MarkEmailUnread';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const isLessThanOneWeekOld = (timestamp) => {
|
||||
// Current time in milliseconds
|
||||
@ -54,6 +54,7 @@ export const QMailMessages = ({ userName, userAddress }) => {
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
|
||||
const getMails = useCallback(async () => {
|
||||
try {
|
||||
@ -89,7 +90,10 @@ export const QMailMessages = ({ userName, userAddress }) => {
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred'); // TODO translate
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -129,20 +133,20 @@ export const QMailMessages = ({ userName, userAddress }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
width: '322px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: '10px',
|
||||
padding: '0px 20px',
|
||||
justifyContent: 'flex-start',
|
||||
padding: '0px 20px',
|
||||
width: '322px',
|
||||
}}
|
||||
onClick={() => setIsExpanded((prev) => !prev)}
|
||||
>
|
||||
@ -151,8 +155,9 @@ export const QMailMessages = ({ userName, userAddress }) => {
|
||||
fontSize: '1rem',
|
||||
}}
|
||||
>
|
||||
Latest Q-Mails
|
||||
{t('group:latest_mails', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
|
||||
<MarkEmailUnreadIcon
|
||||
sx={{
|
||||
color: anyUnread
|
||||
@ -195,9 +200,9 @@ export const QMailMessages = ({ userName, userAddress }) => {
|
||||
{loading && mails.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<CustomLoader />
|
||||
@ -206,11 +211,11 @@ export const QMailMessages = ({ userName, userAddress }) => {
|
||||
{!loading && mails.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -220,7 +225,9 @@ export const QMailMessages = ({ userName, userAddress }) => {
|
||||
color: theme.palette.primary,
|
||||
}}
|
||||
>
|
||||
Nothing to display
|
||||
{t('group:message.generic.no_display', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
Fragment,
|
||||
ReactElement,
|
||||
Ref,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
@ -15,11 +16,30 @@ import Typography from '@mui/material/Typography';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import Slide from '@mui/material/Slide';
|
||||
import { TransitionProps } from '@mui/material/transitions';
|
||||
import { Box, FormControlLabel, Switch, styled, useTheme } from '@mui/material';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
TextField,
|
||||
styled,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { enabledDevModeAtom } from '../../atoms/global';
|
||||
|
||||
import ThemeManager from '../Theme/ThemeManager';
|
||||
import { useAtom } from 'jotai';
|
||||
import { decryptStoredWallet } from '../../utils/decryptWallet';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import PhraseWallet from '../../utils/generateWallet/phrase-wallet';
|
||||
import { walletVersion } from '../../background';
|
||||
import Base58 from '../../deps/Base58';
|
||||
import { MyContext } from '../../App';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
|
||||
padding: 8,
|
||||
@ -63,11 +83,11 @@ const Transition = forwardRef(function Transition(
|
||||
return <Slide direction="up" ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
export const Settings = ({ address, open, setOpen }) => {
|
||||
export const Settings = ({ open, setOpen, rawWallet }) => {
|
||||
const [checked, setChecked] = useState(false);
|
||||
const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom);
|
||||
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setChecked(event.target.checked);
|
||||
@ -82,7 +102,7 @@ export const Settings = ({ address, open, setOpen }) => {
|
||||
if (response?.error) {
|
||||
console.error('Error adding user settings:', response.error);
|
||||
} else {
|
||||
console.log('User settings added successfully'); // TODO translate
|
||||
console.log('User settings added successfully');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -113,7 +133,10 @@ export const Settings = ({ address, open, setOpen }) => {
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -136,7 +159,9 @@ export const Settings = ({ address, open, setOpen }) => {
|
||||
<AppBar sx={{ position: 'relative' }}>
|
||||
<Toolbar>
|
||||
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
|
||||
General Settings
|
||||
{t('core:general_settings', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
|
||||
<IconButton
|
||||
@ -152,13 +177,13 @@ export const Settings = ({ address, open, setOpen }) => {
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
color: theme.palette.text.primary,
|
||||
padding: '20px',
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
gap: '20px',
|
||||
overflowY: 'auto',
|
||||
padding: '20px',
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
@ -168,7 +193,9 @@ export const Settings = ({ address, open, setOpen }) => {
|
||||
control={
|
||||
<LocalNodeSwitch checked={checked} onChange={handleChange} />
|
||||
}
|
||||
label="Disable all push notifications"
|
||||
label={t('group:action.disable_push_notifications', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
/>
|
||||
{window?.electronAPI && (
|
||||
<FormControlLabel
|
||||
@ -184,12 +211,157 @@ export const Settings = ({ address, open, setOpen }) => {
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Enable dev mode"
|
||||
label={t('group:action.enable_dev_mode', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{isEnabledDevMode && <ExportPrivateKey rawWallet={rawWallet} />}
|
||||
<ThemeManager />
|
||||
</Box>
|
||||
</Dialog>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const ExportPrivateKey = ({ rawWallet }) => {
|
||||
const [password, setPassword] = useState('');
|
||||
const [privateKey, setPrivateKey] = useState('');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { setOpenSnackGlobal, setInfoSnackCustom } = useContext(MyContext);
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
|
||||
const exportPrivateKeyFunc = async () => {
|
||||
try {
|
||||
setInfoSnackCustom({
|
||||
type: 'info',
|
||||
message: t('group:message.generic.descrypt_wallet', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
|
||||
setOpenSnackGlobal(true);
|
||||
const wallet = structuredClone(rawWallet);
|
||||
|
||||
const res = await decryptStoredWallet(password, wallet);
|
||||
const wallet2 = new PhraseWallet(res, wallet?.version || walletVersion);
|
||||
|
||||
const keyPair = Base58.encode(wallet2._addresses[0].keyPair.privateKey);
|
||||
setPrivateKey(keyPair);
|
||||
setInfoSnackCustom({
|
||||
type: '',
|
||||
message: '',
|
||||
});
|
||||
|
||||
setOpenSnackGlobal(false);
|
||||
} catch (error) {
|
||||
setInfoSnackCustom({
|
||||
type: 'error',
|
||||
message: error?.message
|
||||
? t('group:message.error.decrypt_wallet', {
|
||||
errorMessage: error?.message,
|
||||
postProcess: 'capitalize',
|
||||
})
|
||||
: t('group:message.error.descrypt_wallet', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
|
||||
setOpenSnackGlobal(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
width: '200px',
|
||||
}}
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
{t('group:action.export_private_key', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{t('group:action.export_password', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{t('group:message.generic.secure_place', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</DialogContentText>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<TextField
|
||||
autoFocus
|
||||
type="password"
|
||||
value={password}
|
||||
autoComplete="off"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
{privateKey && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(privateKey);
|
||||
setInfoSnackCustom({
|
||||
type: 'success',
|
||||
message: t('group:message.generic.private_key_copied', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
|
||||
setOpenSnackGlobal(true);
|
||||
}}
|
||||
>
|
||||
{t('group:action.copy_private_key', {
|
||||
postProcess: 'capitalize',
|
||||
})}{' '}
|
||||
<ContentCopyIcon color="primary" />
|
||||
</Button>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
setPassword('');
|
||||
setPrivateKey('');
|
||||
}}
|
||||
>
|
||||
{t('group:action.cancel', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
|
||||
<Button variant="contained" onClick={exportPrivateKeyFunc}>
|
||||
{t('group:action.decrypt', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
@ -17,27 +17,27 @@ export const ThingsToDoInitial = ({
|
||||
balance,
|
||||
userInfo,
|
||||
}) => {
|
||||
const [checked1, setChecked1] = React.useState(false);
|
||||
const [checked2, setChecked2] = React.useState(false);
|
||||
const [checked1, setChecked1] = useState(false);
|
||||
const [checked2, setChecked2] = useState(false);
|
||||
const { t } = useTranslation(['core', 'tutorial']);
|
||||
const theme = useTheme();
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (balance && +balance >= 6) {
|
||||
setChecked1(true);
|
||||
}
|
||||
}, [balance]);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (name) setChecked2(true);
|
||||
}, [name]);
|
||||
|
||||
const isLoaded = React.useMemo(() => {
|
||||
const isLoaded = useMemo(() => {
|
||||
if (userInfo !== null) return true;
|
||||
return false;
|
||||
}, [userInfo]);
|
||||
|
||||
const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(() => {
|
||||
const hasDoneNameAndBalanceAndIsLoaded = useMemo(() => {
|
||||
if (isLoaded && checked1 && checked2) return true;
|
||||
return false;
|
||||
}, [checked1, isLoaded, checked2]);
|
||||
@ -55,18 +55,18 @@ export const ThingsToDoInitial = ({
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '322px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '0px 20px',
|
||||
width: '322px',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -125,6 +125,7 @@ export const ThingsToDoInitial = ({
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
/>
|
||||
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
justifyContent: 'flex-end',
|
||||
@ -144,6 +145,7 @@ export const ThingsToDoInitial = ({
|
||||
</ListItemIcon>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
||||
<ListItem
|
||||
sx={{
|
||||
marginBottom: '20px',
|
||||
|
@ -22,6 +22,7 @@ import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmail
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { txListAtom } from '../../atoms/global';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
@ -60,9 +61,10 @@ export const UserListOfInvites = ({
|
||||
const [invites, setInvites] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
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 listRef = useRef();
|
||||
const listRef = useRef(null);
|
||||
|
||||
const getRequests = async () => {
|
||||
try {
|
||||
@ -94,9 +96,13 @@ export const UserListOfInvites = ({
|
||||
|
||||
const handleJoinGroup = async (groupId, groupName) => {
|
||||
try {
|
||||
const fee = await getFee('JOIN_GROUP'); // TODO translate
|
||||
const fee = await getFee('JOIN_GROUP');
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
@ -123,8 +129,9 @@ export const UserListOfInvites = ({
|
||||
res(response);
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.group_join', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
handlePopoverClose();
|
||||
@ -140,13 +147,16 @@ export const UserListOfInvites = ({
|
||||
.catch((error) => {
|
||||
setInfoSnack({
|
||||
type: 'error',
|
||||
message: error.message || 'An error occurred',
|
||||
message:
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' }),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
rej(error);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@ -182,16 +192,22 @@ export const UserListOfInvites = ({
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '325px',
|
||||
height: '250px',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
height: '250px',
|
||||
padding: '10px',
|
||||
width: '325px',
|
||||
}}
|
||||
>
|
||||
<Typography>Join {invite?.groupName}</Typography>
|
||||
<Typography>
|
||||
{t('core:action.join', {
|
||||
postProcess: 'capitalize',
|
||||
})}{' '}
|
||||
{invite?.groupName}
|
||||
</Typography>
|
||||
|
||||
<LoadingButton
|
||||
loading={isLoading}
|
||||
loadingPosition="start"
|
||||
@ -200,10 +216,13 @@ export const UserListOfInvites = ({
|
||||
handleJoinGroup(invite?.groupId, invite?.groupName)
|
||||
}
|
||||
>
|
||||
Join group
|
||||
{t('group:action.join_group', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
</Popover>
|
||||
|
||||
<ListItemButton
|
||||
onClick={(event) => handlePopoverOpen(event, index)}
|
||||
>
|
||||
@ -221,7 +240,9 @@ export const UserListOfInvites = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Spacer width="15px" />
|
||||
|
||||
<ListItemText
|
||||
primary={invite?.groupName}
|
||||
secondary={invite?.description}
|
||||
@ -242,14 +263,19 @@ export const UserListOfInvites = ({
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<p>Invite list</p>
|
||||
<p>
|
||||
{t('core:list.invite', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</p>
|
||||
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AutoSizer>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Box, ButtonBase, Divider, Typography, useTheme } from '@mui/material';
|
||||
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 AppViewerContainer from '../Apps/AppViewerContainer';
|
||||
import {
|
||||
@ -19,7 +19,6 @@ export const WalletsAppWrapper = () => {
|
||||
const [navigationController, setNavigationController] = useAtom(
|
||||
navigationControllerAtom
|
||||
);
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState({
|
||||
tabId: '5558589',
|
||||
name: 'Q-Wallets',
|
||||
@ -60,17 +59,17 @@ export const WalletsAppWrapper = () => {
|
||||
{isOpen && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
backgroundColor: theme.palette.background.paper, // TODO: set color theme
|
||||
zIndex: 100,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
overflow: 'hidden',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderTopLeftRadius: '10px',
|
||||
borderTopRightRadius: '10px',
|
||||
bottom: 0,
|
||||
boxShadow: 4,
|
||||
height: '100vh',
|
||||
overflow: 'hidden',
|
||||
position: 'fixed',
|
||||
right: 0,
|
||||
width: '100vw',
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -85,11 +84,11 @@ export const WalletsAppWrapper = () => {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '5px',
|
||||
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Typography>Q-Wallets</Typography>
|
||||
|
||||
<ButtonBase onClick={handleClose}>
|
||||
<CloseIcon
|
||||
sx={{
|
||||
@ -108,6 +107,7 @@ export const WalletsAppWrapper = () => {
|
||||
ref={iframeRef}
|
||||
skipAuth={true}
|
||||
/>
|
||||
|
||||
<AppsNavBarParent>
|
||||
<AppsNavBarLeft
|
||||
sx={{
|
||||
@ -126,6 +126,7 @@ export const WalletsAppWrapper = () => {
|
||||
>
|
||||
<NavBack />
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
if (selectedTab?.refreshFunc) {
|
||||
|
@ -164,6 +164,7 @@ export const useBlockedAddresses = () => {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (address) {
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
|
@ -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 { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App';
|
||||
import {
|
||||
|
@ -36,7 +36,7 @@ export const NewUsersCTA = ({ balance }) => {
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{t('core:new_user', { postProcess: 'capitalize' })}
|
||||
{t('core:question.new_user', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
@ -168,6 +168,7 @@ export const QortPrice = () => {
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@ -198,6 +199,7 @@ export const QortPrice = () => {
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Tooltip
|
||||
title={
|
||||
<span style={{ fontSize: '14px', fontWeight: 700 }}>
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { supportedLanguages } from '../../../i18n';
|
||||
import { Tooltip, useTheme } from '@mui/material';
|
||||
import { supportedLanguages } from '../../i18n/i18n';
|
||||
import {
|
||||
FormControl,
|
||||
MenuItem,
|
||||
Select,
|
||||
Tooltip,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
|
||||
const LanguageSelector = () => {
|
||||
const { i18n, t } = useTranslation(['core']);
|
||||
@ -19,20 +25,6 @@ const LanguageSelector = () => {
|
||||
const { name, flag } =
|
||||
supportedLanguages[currentLang] || supportedLanguages['en'];
|
||||
|
||||
// Detect clicks outside the component
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (selectorRef.current && !selectorRef.current.contains(event.target)) {
|
||||
setShowSelect(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={selectorRef}
|
||||
@ -44,33 +36,13 @@ const LanguageSelector = () => {
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
title={t('core:action.change_language', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
>
|
||||
{showSelect ? (
|
||||
<select
|
||||
style={{
|
||||
fontSize: '1rem',
|
||||
border: '2px',
|
||||
background: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
bottom: '7px',
|
||||
}}
|
||||
value={currentLang}
|
||||
onChange={handleChange}
|
||||
autoFocus
|
||||
>
|
||||
{Object.entries(supportedLanguages).map(([code, { name }]) => (
|
||||
<option key={code} value={code}>
|
||||
{code.toUpperCase()} - {name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
{!showSelect && (
|
||||
<Tooltip
|
||||
key={currentLang}
|
||||
title={t('core:action.change_language', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
>
|
||||
<button
|
||||
onClick={() => setShowSelect(true)}
|
||||
style={{
|
||||
@ -81,10 +53,36 @@ const LanguageSelector = () => {
|
||||
}}
|
||||
aria-label={`Current language: ${name}`}
|
||||
>
|
||||
{showSelect ? undefined : flag}
|
||||
{flag}
|
||||
</button>
|
||||
)}
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{showSelect && (
|
||||
<FormControl
|
||||
size="small"
|
||||
sx={{
|
||||
minWidth: 120,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
open
|
||||
labelId="language-select-label"
|
||||
id="language-select"
|
||||
value={currentLang}
|
||||
onChange={handleChange}
|
||||
autoFocus
|
||||
onClose={() => setShowSelect(false)}
|
||||
>
|
||||
{Object.entries(supportedLanguages).map(([code, { name }]) => (
|
||||
<MenuItem key={code} value={code}>
|
||||
{code.toUpperCase()} – {name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Box, CircularProgress } from "@mui/material";
|
||||
import { Box, CircularProgress } from '@mui/material';
|
||||
|
||||
export const Loader = () => {
|
||||
return (
|
||||
<Box sx={{
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
@ -11,13 +11,14 @@ export const Loader = () => {
|
||||
height: '100%',
|
||||
position: 'fixed',
|
||||
top: '0px',
|
||||
left:'0px',
|
||||
left: '0px',
|
||||
right: '0px',
|
||||
bottom: '0px',
|
||||
zIndex: 10,
|
||||
background: 'rgba(0, 0, 0, 0.4)'
|
||||
}}>
|
||||
<CircularProgress color="success" size={25} />
|
||||
background: 'rgba(0, 0, 0, 0.4)',
|
||||
}}
|
||||
>
|
||||
<CircularProgress color="success" size={25} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -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 { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App';
|
||||
import {
|
||||
|
@ -158,6 +158,12 @@ export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => {
|
||||
value={paymentPassword}
|
||||
onChange={(e) => setPaymentPassword(e.target.value)}
|
||||
autoComplete="off"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (isLoadingSendCoin) return;
|
||||
sendCoinFunc();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
@ -27,15 +27,25 @@ export const ReactionPicker = ({ onReaction }) => {
|
||||
if (showPicker) {
|
||||
setShowPicker(false);
|
||||
} else {
|
||||
// Get the button's position
|
||||
const buttonRect = buttonRef.current.getBoundingClientRect();
|
||||
const pickerWidth = 350;
|
||||
const pickerHeight = 400; // Match Picker height prop
|
||||
|
||||
// Calculate position to align the right edge of the picker with the button's right edge
|
||||
setPickerPosition({
|
||||
top: buttonRect.bottom + window.scrollY, // Position below the button
|
||||
left: buttonRect.right + window.scrollX - pickerWidth, // Align right edges
|
||||
});
|
||||
// Initial position (below the button)
|
||||
let top = buttonRect.bottom + window.scrollY;
|
||||
let left = buttonRect.right + window.scrollX - pickerWidth;
|
||||
|
||||
// If picker would overflow bottom, show it above the button
|
||||
const overflowBottom =
|
||||
top + pickerHeight > window.innerHeight + window.scrollY;
|
||||
if (overflowBottom) {
|
||||
top = buttonRect.top + window.scrollY - pickerHeight;
|
||||
}
|
||||
|
||||
// Optional: prevent overflow on the left too
|
||||
if (left < 0) left = 0;
|
||||
|
||||
setPickerPosition({ top, left });
|
||||
setShowPicker(true);
|
||||
}
|
||||
};
|
||||
@ -92,12 +102,13 @@ export const ReactionPicker = ({ onReaction }) => {
|
||||
allowExpandReactions={true}
|
||||
autoFocusSearch={false}
|
||||
emojiStyle={EmojiStyle.NATIVE}
|
||||
height="450"
|
||||
height={400}
|
||||
onEmojiClick={handlePicker}
|
||||
onReactionClick={handleReaction}
|
||||
reactionsDefaultOpen={true}
|
||||
// reactionsDefaultOpen={true}
|
||||
// open={true}
|
||||
theme={Theme.DARK}
|
||||
width="350"
|
||||
width={350}
|
||||
/>
|
||||
</div>,
|
||||
document.body
|
||||
|
@ -1,33 +1,22 @@
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
Collapse,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Input,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
List,
|
||||
MenuItem,
|
||||
Popover,
|
||||
Select,
|
||||
TextField,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { Label } from './Group/AddGroup';
|
||||
import { Spacer } from '../common/Spacer';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { getBaseApiReact, MyContext } from '../App';
|
||||
import { getBaseApiReact } from '../App';
|
||||
import { getFee } from '../background';
|
||||
import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked';
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from '../utils/events';
|
||||
@ -43,6 +32,7 @@ enum Availability {
|
||||
AVAILABLE = 'available',
|
||||
NOT_AVAILABLE = 'not-available',
|
||||
}
|
||||
|
||||
export const RegisterName = ({
|
||||
setOpenSnack,
|
||||
setInfoSnack,
|
||||
@ -77,7 +67,6 @@ export const RegisterName = ({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
// Debounce logic
|
||||
@ -195,21 +184,22 @@ export const RegisterName = ({
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">{'Register name'}</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<Box
|
||||
sx={{
|
||||
width: '400px',
|
||||
maxWidth: '90vw',
|
||||
height: '500px',
|
||||
maxHeight: '90vh',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
height: '500px',
|
||||
maxHeight: '90vh',
|
||||
maxWidth: '90vw',
|
||||
padding: '10px',
|
||||
width: '400px',
|
||||
}}
|
||||
>
|
||||
<Label>Choose a name</Label>
|
||||
<Label>Choose a name</Label> // TODO: translate
|
||||
<TextField
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
@ -237,6 +227,7 @@ export const RegisterName = ({
|
||||
requires a {nameFee} QORT fee
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Spacer height="10px" />
|
||||
</>
|
||||
)}
|
||||
@ -307,6 +298,7 @@ export const RegisterName = ({
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Publish data to Qortal: anything from apps to videos. Fully decentralized!" />
|
||||
</ListItem>
|
||||
|
||||
<ListItem disablePadding>
|
||||
<ListItemIcon>
|
||||
<RadioButtonCheckedIcon
|
||||
@ -320,6 +312,7 @@ export const RegisterName = ({
|
||||
</List>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button
|
||||
disabled={isLoadingRegisterName}
|
||||
@ -331,6 +324,7 @@ export const RegisterName = ({
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
disabled={
|
||||
!registerNameValue.trim() ||
|
||||
|
@ -25,7 +25,7 @@ const ThemeContext = createContext({
|
||||
toggleTheme: () => {},
|
||||
userThemes: [defaultTheme],
|
||||
addUserTheme: (themes) => {},
|
||||
setUserTheme: (theme) => {},
|
||||
setUserTheme: (theme, themes) => {},
|
||||
currentThemeId: 'default',
|
||||
});
|
||||
|
||||
@ -83,13 +83,13 @@ export const ThemeProvider = ({ children }) => {
|
||||
saveSettings(themes);
|
||||
};
|
||||
|
||||
const setUserTheme = (theme) => {
|
||||
const setUserTheme = (theme, themes) => {
|
||||
if (theme.id === 'default') {
|
||||
setCurrentThemeId('default');
|
||||
saveSettings(userThemes, themeMode, 'default');
|
||||
saveSettings(themes || userThemes, themeMode, 'default');
|
||||
} else {
|
||||
setCurrentThemeId(theme.id);
|
||||
saveSettings(userThemes, themeMode, theme.id);
|
||||
saveSettings(themes || userThemes, themeMode, theme.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -119,7 +119,7 @@ export default function ThemeManager() {
|
||||
const newTheme = { ...themeDraft, id: uid.rnd() };
|
||||
const updatedThemes = [...userThemes, newTheme];
|
||||
addUserTheme(updatedThemes);
|
||||
setUserTheme(newTheme);
|
||||
setUserTheme(newTheme, updatedThemes);
|
||||
}
|
||||
setOpenEditor(false);
|
||||
};
|
||||
@ -135,19 +135,22 @@ export default function ThemeManager() {
|
||||
);
|
||||
|
||||
if (defaultTheme) {
|
||||
setUserTheme(defaultTheme);
|
||||
setUserTheme(defaultTheme, updatedThemes);
|
||||
} else {
|
||||
// Emergency fallback
|
||||
setUserTheme({
|
||||
light: lightThemeOptions,
|
||||
dark: darkThemeOptions,
|
||||
});
|
||||
setUserTheme(
|
||||
{
|
||||
light: lightThemeOptions,
|
||||
dark: darkThemeOptions,
|
||||
},
|
||||
updatedThemes
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleApplyTheme = (theme) => {
|
||||
setUserTheme(theme);
|
||||
setUserTheme(theme, null);
|
||||
};
|
||||
|
||||
const handleColorChange = (mode, fieldPath, color) => {
|
||||
@ -210,7 +213,8 @@ export default function ThemeManager() {
|
||||
const newTheme = { ...importedTheme, id: uid.rnd() };
|
||||
const updatedThemes = [...userThemes, newTheme];
|
||||
addUserTheme(updatedThemes);
|
||||
setUserTheme(newTheme);
|
||||
|
||||
setUserTheme(newTheme, updatedThemes);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
54
src/i18n/i18n.ts
Normal file
54
src/i18n/i18n.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
const capitalize = {
|
||||
type: 'postProcessor',
|
||||
name: 'capitalize',
|
||||
process: (value: string) => value.charAt(0).toUpperCase() + value.slice(1),
|
||||
};
|
||||
|
||||
export const supportedLanguages = {
|
||||
de: { name: 'Deutsch', flag: '🇩🇪' },
|
||||
en: { name: 'English', flag: '🇺🇸' },
|
||||
es: { name: 'Español', flag: '🇪🇸' },
|
||||
fr: { name: 'Français', flag: '🇫🇷' },
|
||||
it: { name: 'Italiano', flag: '🇮🇹' },
|
||||
ru: { name: 'Русский', flag: '🇷🇺' },
|
||||
};
|
||||
|
||||
// Load all JSON files under locales/**/*
|
||||
const modules = import.meta.glob('./locales/**/*.json', {
|
||||
eager: true,
|
||||
}) as Record<string, any>;
|
||||
|
||||
// Construct i18n resources object
|
||||
const resources: Record<string, Record<string, any>> = {};
|
||||
|
||||
for (const path in modules) {
|
||||
// Path format: './locales/en/core.json'
|
||||
const match = path.match(/\.\/locales\/([^/]+)\/([^/]+)\.json$/);
|
||||
if (!match) continue;
|
||||
|
||||
const [, lang, ns] = match;
|
||||
resources[lang] = resources[lang] || {};
|
||||
resources[lang][ns] = modules[path].default;
|
||||
}
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
.use(capitalize as any)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: 'en',
|
||||
lng: navigator.language,
|
||||
supportedLngs: Object.keys(supportedLanguages),
|
||||
ns: ['core', 'auth', 'group', 'tutorial'],
|
||||
defaultNS: 'core',
|
||||
interpolation: { escapeValue: false },
|
||||
react: { useSuspense: false },
|
||||
debug: import.meta.env.MODE === 'development',
|
||||
});
|
||||
|
||||
export default i18n;
|
133
src/i18n/locales/de/core.json
Normal file
133
src/i18n/locales/de/core.json
Normal file
@ -0,0 +1,133 @@
|
||||
{
|
||||
"action": {
|
||||
"add": "hinzufügen",
|
||||
"accept": "akzeptieren",
|
||||
"backup_account": "Konto sichern",
|
||||
"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": {
|
||||
"block_height": "Blockhöhe",
|
||||
"information": "Kerninformationen",
|
||||
"peers": "verbundene Peers",
|
||||
"version": "Core-Version"
|
||||
},
|
||||
"ui": {
|
||||
"version": "UI-Version"
|
||||
},
|
||||
"count": {
|
||||
"none": "keine",
|
||||
"one": "eins"
|
||||
},
|
||||
"description": "Beschreibung",
|
||||
"downloading_qdn": "Herunterladen von QDN",
|
||||
"fee": {
|
||||
"payment": "Zahlungsgebühr",
|
||||
"publish": "Veröffentlichungsgebühr"
|
||||
},
|
||||
"general_settings": "allgemeine Einstellungen",
|
||||
"last_height": "letzte Höhe",
|
||||
"list": {
|
||||
"invite": "Einladungsliste",
|
||||
"join_request": "Beitrittsanfragenliste",
|
||||
"member": "Mitgliederliste"
|
||||
},
|
||||
"loading": "Lädt...",
|
||||
"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": {
|
||||
"generic": "Ein Fehler ist aufgetreten",
|
||||
"incorrect_password": "falsches Passwort",
|
||||
"missing_field": "fehlt: {{ field }}",
|
||||
"save_qdn": "Speichern in QDN nicht möglich"
|
||||
},
|
||||
"status": {
|
||||
"minting": "(minting)",
|
||||
"not_minting": "(kein minting)",
|
||||
"synchronized": "synchronisiert",
|
||||
"synchronizing": "synchronisiere"
|
||||
},
|
||||
"success": {
|
||||
"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": {
|
||||
"no_pinned_changes": "Du hast derzeit keine Änderungen an deinen angehefteten Apps",
|
||||
"overwrite_changes": "Die App konnte deine gespeicherten angehefteten QDN-Apps nicht laden. Möchtest du diese Änderungen überschreiben?",
|
||||
"overwrite_qdn": "in QDN überschreiben",
|
||||
"publish_qdn": "Möchtest du deine Einstellungen verschlüsselt in QDN veröffentlichen?",
|
||||
"qdn": "QDN-Speicherung verwenden",
|
||||
"register_name": "Du brauchst einen registrierten Qortal-Namen, um deine angehefteten Apps in QDN zu speichern.",
|
||||
"reset_pinned": "Gefällt dir deine lokale Änderung nicht? Möchtest du auf die Standard-Apps zurücksetzen?",
|
||||
"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_qdn": "auf QDN zurücksetzen",
|
||||
"save_qdn": "in QDN speichern",
|
||||
"save": "speichern",
|
||||
"settings": "Du nutzt die Export-/Import-Methode zum Speichern der Einstellungen.",
|
||||
"unsaved_changes": "Du hast nicht gespeicherte Änderungen an deinen angehefteten Apps. Speichere sie in QDN."
|
||||
},
|
||||
"settings": "Einstellungen",
|
||||
"supply": "Versorgung",
|
||||
"theme": {
|
||||
"dark": "dunkler Modus",
|
||||
"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",
|
||||
"tutorial": "Tutorial",
|
||||
"user_lookup": "Benutzersuche",
|
||||
"wallet": {
|
||||
"wallet": "Wallet",
|
||||
"wallet_other": "Wallets"
|
||||
},
|
||||
"welcome": "Willkommen"
|
||||
}
|
105
src/i18n/locales/de/group.json
Normal file
105
src/i18n/locales/de/group.json
Normal 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"
|
||||
}
|
@ -5,56 +5,65 @@
|
||||
"backup_account": "backup account",
|
||||
"backup_wallet": "backup wallet",
|
||||
"cancel": "cancel",
|
||||
"cancel_invitation": "cancel invitation",
|
||||
"change": "change",
|
||||
"change_language": "change language",
|
||||
"choose": "choose",
|
||||
"close": "close",
|
||||
"continue": "continue",
|
||||
"continue_logout": "continue to logout",
|
||||
"create_thread": "create thread",
|
||||
"decline": "decline",
|
||||
"decrypt": "decrypt",
|
||||
"edit": "edit",
|
||||
"export": "export",
|
||||
"import": "import",
|
||||
"invite": "invite",
|
||||
"join": "join",
|
||||
"logout": "logout",
|
||||
"notify": "notify"
|
||||
"new": {
|
||||
"post": "new post",
|
||||
"thread": "new thread"
|
||||
},
|
||||
"notify": "notify",
|
||||
"post": "post",
|
||||
"post_message": "post message"
|
||||
},
|
||||
"admin": "admin",
|
||||
"core": {
|
||||
"block_height": "block height",
|
||||
"information": "core information",
|
||||
"peers": "connected peers",
|
||||
"version": "core version"
|
||||
},
|
||||
"ui": {
|
||||
"version": "UI version"
|
||||
},
|
||||
"count": {
|
||||
"none": "none",
|
||||
"one": "one"
|
||||
},
|
||||
"description": "description",
|
||||
"downloading_qdn": "downloading from QDN",
|
||||
"fee": {
|
||||
"payment": "payment fee",
|
||||
"publish": "publish fee"
|
||||
},
|
||||
"page": {
|
||||
"last": "last",
|
||||
"first": "first",
|
||||
"next": "next",
|
||||
"previous": "previous"
|
||||
},
|
||||
"downloading_qdn": "downloading from QDN",
|
||||
"general_settings": "general settings",
|
||||
"last_height": "last height",
|
||||
"list": {
|
||||
"invite": "invite list",
|
||||
"join_request": "join request list",
|
||||
"member": "member list"
|
||||
},
|
||||
"loading": "loading...",
|
||||
"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",
|
||||
"minting_status": "minting status",
|
||||
"new_user": "are you a new user?",
|
||||
"payment_notification": "payment notification",
|
||||
"price": "price",
|
||||
"q_mail": "q-mail",
|
||||
"message": {
|
||||
"error": {
|
||||
"generic": "an error occurred",
|
||||
"incorrect_password": "incorrect password",
|
||||
"missing_field": "missing: {{ field }}",
|
||||
"save_qdn": "unable to save to QDN"
|
||||
},
|
||||
"status": {
|
||||
@ -70,6 +79,19 @@
|
||||
"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": {
|
||||
"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?",
|
105
src/i18n/locales/en/group.json
Normal file
105
src/i18n/locales/en/group.json
Normal file
@ -0,0 +1,105 @@
|
||||
{
|
||||
"action": {
|
||||
"ban": "ban member from group",
|
||||
"cancel_ban": "cancel ban",
|
||||
"copy_private_key": "copy private key",
|
||||
"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",
|
||||
"join_group": "join group",
|
||||
"kick_member": "kick member from group",
|
||||
"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",
|
||||
"remove_admin": "remove as admin",
|
||||
"return_to_thread": "return to threads"
|
||||
},
|
||||
"advanced_options": "advanced options",
|
||||
"approval_threshold": "group Approval Threshold (number / percentage of Admins that must approve a transaction)",
|
||||
"ban_list": "ban list",
|
||||
"block_delay": {
|
||||
"minimum": "minimum Block delay for Group Transaction Approvals",
|
||||
"maximum": "maximum Block delay for Group Transaction Approvals"
|
||||
},
|
||||
"group": {
|
||||
"closed": "closed (private) - users need permission to join",
|
||||
"description": "description of group",
|
||||
"id": "group id",
|
||||
"invites": "group invites",
|
||||
"management": "group management",
|
||||
"member_number": "number of members",
|
||||
"name": "group name",
|
||||
"open": "open (public)",
|
||||
"type": "group type"
|
||||
},
|
||||
"invitation_expiry": "invitation Expiry Time",
|
||||
"invitees_list": "invitees list",
|
||||
"join_link": "join group link",
|
||||
"join_requests": "join requests",
|
||||
"last_message": "last message",
|
||||
"latest_mails": "latest Q-Mails",
|
||||
"message": {
|
||||
"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...",
|
||||
"group_invited_you": "{{group}} has invited you",
|
||||
"loading_members": "loading member list with names... please wait.",
|
||||
"no_display": "nothing to display",
|
||||
"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.",
|
||||
"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."
|
||||
},
|
||||
"error": {
|
||||
"access_name": "cannot send a message without a access to your name",
|
||||
"descrypt_wallet": "error decrypting wallet {{ :errorMessage }}",
|
||||
"description_required": "please provide a description",
|
||||
"group_info": "cannot access group information",
|
||||
"group_secret_key": "cannot get group secret key",
|
||||
"name_required": "please provide a name",
|
||||
"notify_admins": "try notifying an admin from the list of admins below:",
|
||||
"thread_id": "unable to locate thread Id"
|
||||
},
|
||||
"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_name": "created group {{group_name}}: awaiting confirmation",
|
||||
"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_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_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.",
|
||||
"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"
|
||||
}
|
133
src/i18n/locales/es/core.json
Normal file
133
src/i18n/locales/es/core.json
Normal file
@ -0,0 +1,133 @@
|
||||
{
|
||||
"action": {
|
||||
"add": "añadir",
|
||||
"accept": "aceptar",
|
||||
"backup_account": "respaldar cuenta",
|
||||
"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": {
|
||||
"block_height": "altura del bloque",
|
||||
"information": "información del núcleo",
|
||||
"peers": "pares conectados",
|
||||
"version": "versión del núcleo"
|
||||
},
|
||||
"ui": {
|
||||
"version": "versión de la interfaz"
|
||||
},
|
||||
"count": {
|
||||
"none": "ninguno",
|
||||
"one": "uno"
|
||||
},
|
||||
"description": "descripción",
|
||||
"downloading_qdn": "descargando desde QDN",
|
||||
"fee": {
|
||||
"payment": "tarifa de pago",
|
||||
"publish": "tarifa de publicación"
|
||||
},
|
||||
"general_settings": "configuración general",
|
||||
"last_height": "última altura",
|
||||
"list": {
|
||||
"invite": "lista de invitaciones",
|
||||
"join_request": "lista de solicitudes de unión",
|
||||
"member": "lista de miembros"
|
||||
},
|
||||
"loading": "cargando...",
|
||||
"loading_posts": "cargando publicaciones... por favor espera.",
|
||||
"message_us": "por favor contáctanos en Telegram o Discord si necesitas 4 QORT para comenzar a chatear sin limitaciones",
|
||||
"message": {
|
||||
"error": {
|
||||
"generic": "ocurrió un error",
|
||||
"incorrect_password": "contraseña incorrecta",
|
||||
"missing_field": "falta: {{ field }}",
|
||||
"save_qdn": "no se pudo guardar en QDN"
|
||||
},
|
||||
"status": {
|
||||
"minting": "(generando)",
|
||||
"not_minting": "(no generando)",
|
||||
"synchronized": "sincronizado",
|
||||
"synchronizing": "sincronizando"
|
||||
},
|
||||
"success": {
|
||||
"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": {
|
||||
"no_pinned_changes": "actualmente no tienes cambios en tus aplicaciones fijadas",
|
||||
"overwrite_changes": "la aplicación no pudo descargar tus aplicaciones fijadas guardadas en QDN. ¿Deseas sobrescribir esos cambios?",
|
||||
"overwrite_qdn": "sobrescribir en QDN",
|
||||
"publish_qdn": "¿quieres publicar tu configuración en QDN (cifrada)?",
|
||||
"qdn": "usar guardado 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? ¿Quieres restablecer las aplicaciones fijadas por defecto?",
|
||||
"reset_qdn": "¿no te gustan tus cambios locales actuales? ¿Quieres restablecer tus aplicaciones fijadas guardadas en QDN?",
|
||||
"revert_default": "restablecer a valores predeterminados",
|
||||
"revert_qdn": "restablecer desde QDN",
|
||||
"save_qdn": "guardar en QDN",
|
||||
"save": "guardar",
|
||||
"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."
|
||||
},
|
||||
"settings": "configuración",
|
||||
"supply": "oferta",
|
||||
"theme": {
|
||||
"dark": "modo oscuro",
|
||||
"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",
|
||||
"tutorial": "tutorial",
|
||||
"user_lookup": "búsqueda de usuario",
|
||||
"wallet": {
|
||||
"wallet": "monedero",
|
||||
"wallet_other": "monederos"
|
||||
},
|
||||
"welcome": "bienvenido"
|
||||
}
|
105
src/i18n/locales/es/group.json
Normal file
105
src/i18n/locales/es/group.json
Normal 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"
|
||||
}
|
133
src/i18n/locales/fr/core.json
Normal file
133
src/i18n/locales/fr/core.json
Normal file
@ -0,0 +1,133 @@
|
||||
{
|
||||
"action": {
|
||||
"add": "ajouter",
|
||||
"accept": "accepter",
|
||||
"backup_account": "sauvegarder le compte",
|
||||
"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": {
|
||||
"block_height": "hauteur de bloc",
|
||||
"information": "informations du noyau",
|
||||
"peers": "pairs connectés",
|
||||
"version": "version du noyau"
|
||||
},
|
||||
"ui": {
|
||||
"version": "version de l'interface"
|
||||
},
|
||||
"count": {
|
||||
"none": "aucun",
|
||||
"one": "un"
|
||||
},
|
||||
"description": "description",
|
||||
"downloading_qdn": "téléchargement depuis QDN",
|
||||
"fee": {
|
||||
"payment": "frais de paiement",
|
||||
"publish": "frais de publication"
|
||||
},
|
||||
"general_settings": "paramètres généraux",
|
||||
"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_posts": "chargement des messages... veuillez patienter.",
|
||||
"message_us": "veuillez nous contacter sur Telegram ou Discord si vous avez besoin de 4 QORT pour commencer à discuter sans limites",
|
||||
"message": {
|
||||
"error": {
|
||||
"generic": "une erreur est survenue",
|
||||
"incorrect_password": "mot de passe incorrect",
|
||||
"missing_field": "champ manquant : {{ field }}",
|
||||
"save_qdn": "impossible d'enregistrer dans QDN"
|
||||
},
|
||||
"status": {
|
||||
"minting": "(en cours de minage)",
|
||||
"not_minting": "(non miné)",
|
||||
"synchronized": "synchronisé",
|
||||
"synchronizing": "synchronisation"
|
||||
},
|
||||
"success": {
|
||||
"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": {
|
||||
"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 enregistrées sur QDN. Voulez-vous écraser ces modifications ?",
|
||||
"overwrite_qdn": "écraser sur QDN",
|
||||
"publish_qdn": "voulez-vous publier vos paramètres sur QDN (chiffrés) ?",
|
||||
"qdn": "utiliser l'enregistrement 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 changements locaux actuels ? Voulez-vous réinitialiser les applications par défaut ?",
|
||||
"reset_qdn": "vous n'aimez pas vos changements locaux actuels ? Voulez-vous restaurer les applications enregistrées sur QDN ?",
|
||||
"revert_default": "réinitialiser aux valeurs par défaut",
|
||||
"revert_qdn": "restaurer depuis QDN",
|
||||
"save_qdn": "enregistrer sur QDN",
|
||||
"save": "enregistrer",
|
||||
"settings": "vous utilisez la méthode d'exportation/importation pour sauvegarder les paramètres.",
|
||||
"unsaved_changes": "vous avez des modifications non enregistrées dans vos applications épinglées. Enregistrez-les sur QDN."
|
||||
},
|
||||
"settings": "paramètres",
|
||||
"supply": "approvisionnement",
|
||||
"theme": {
|
||||
"dark": "mode sombre",
|
||||
"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",
|
||||
"tutorial": "tutoriel",
|
||||
"user_lookup": "recherche d'utilisateur",
|
||||
"wallet": {
|
||||
"wallet": "portefeuille",
|
||||
"wallet_other": "portefeuilles"
|
||||
},
|
||||
"welcome": "bienvenue"
|
||||
}
|
105
src/i18n/locales/fr/group.json
Normal file
105
src/i18n/locales/fr/group.json
Normal 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 d’adhé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 qu’un 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 qu’un 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 d’envoyer 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 d’obtenir 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 l’identifiant 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 d’adhé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 d’adhé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"
|
||||
}
|
@ -2,92 +2,114 @@
|
||||
"action": {
|
||||
"add": "aggiungi",
|
||||
"accept": "accetta",
|
||||
"backup_account": "backup account",
|
||||
"backup_wallet": "backup wallet",
|
||||
"backup_account": "esegui backup account",
|
||||
"backup_wallet": "esegui backup portafoglio",
|
||||
"cancel": "annulla",
|
||||
"cancel_invitation": "annulla invito",
|
||||
"change": "cambia",
|
||||
"change_language": "cambia lingua",
|
||||
"choose": "scegli",
|
||||
"close": "chiudi",
|
||||
"continue": "continua",
|
||||
"continue_logout": "continua con il logout",
|
||||
"create_thread": "crea discussione",
|
||||
"decline": "rifiuta",
|
||||
"decrypt": "decifra",
|
||||
"edit": "modifica",
|
||||
"export": "esporta",
|
||||
"import": "importa",
|
||||
"invite": "invita",
|
||||
"join": "unisciti",
|
||||
"logout": "esci",
|
||||
"notify": "notifica"
|
||||
"new": {
|
||||
"post": "nuovo post",
|
||||
"thread": "nuova discussione"
|
||||
},
|
||||
"notify": "notifica",
|
||||
"post": "pubblica",
|
||||
"post_message": "invia messaggio"
|
||||
},
|
||||
"admin": "amministratore",
|
||||
"core": {
|
||||
"block_height": "altezza blocco",
|
||||
"information": "informazioni core",
|
||||
"peers": "peer connessi",
|
||||
"version": "versione core"
|
||||
},
|
||||
"ui": {
|
||||
"version": "versione UI"
|
||||
},
|
||||
"count": {
|
||||
"none": "nessuno",
|
||||
"one": "uno"
|
||||
},
|
||||
"description": "descrizione",
|
||||
"downloading_qdn": "scaricamento da QDN",
|
||||
"fee": {
|
||||
"payment": "commissione di pagamento",
|
||||
"publish": "commissione di pubblicazione"
|
||||
},
|
||||
"page": {
|
||||
"last": "ultimo",
|
||||
"first": "primo",
|
||||
"next": "successivo",
|
||||
"previous": "precedente"
|
||||
},
|
||||
"downloading_qdn": "scaricamento da QDN",
|
||||
"general_settings": "impostazioni generali",
|
||||
"last_height": "ultima altezza",
|
||||
"list": {
|
||||
"invite": "lista inviti",
|
||||
"join_request": "lista richieste di adesione",
|
||||
"member": "lista membri"
|
||||
},
|
||||
"loading": "caricamento...",
|
||||
"loading_posts": "caricamento post... attendere prego.",
|
||||
"message_us": "per favore scrivici 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",
|
||||
"loading_posts": "caricamento post... attendere.",
|
||||
"message_us": "contattaci su Telegram o Discord se hai bisogno di 4 QORT per iniziare a chattare senza limitazioni",
|
||||
"message": {
|
||||
"error": {
|
||||
"generic": "si è verificato un errore",
|
||||
"incorrect_password": "password errata",
|
||||
"missing_field": "manca: {{ field }}",
|
||||
"save_qdn": "impossibile salvare su QDN"
|
||||
},
|
||||
"status": {
|
||||
"minting": "(minting)",
|
||||
"not_minting": "(non minting)",
|
||||
"minting": "(in conio)",
|
||||
"not_minting": "(non in conio)",
|
||||
"synchronized": "sincronizzato",
|
||||
"synchronizing": "sincronizzazione in corso"
|
||||
"synchronizing": "sincronizzazione"
|
||||
},
|
||||
"success": {
|
||||
"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",
|
||||
"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": {
|
||||
"no_pinned_changes": "attualmente non hai modifiche alle tue app appuntate",
|
||||
"overwrite_changes": "l'app non è riuscita a scaricare le tue app appuntate salvate su QDN. Vuoi sovrascrivere le modifiche?",
|
||||
"no_pinned_changes": "attualmente non hai modifiche nelle app fissate",
|
||||
"overwrite_changes": "l'app non è riuscita a scaricare le tue app fissate salvate su QDN. Vuoi sovrascrivere le modifiche?",
|
||||
"overwrite_qdn": "sovrascrivi su QDN",
|
||||
"publish_qdn": "vuoi pubblicare le tue impostazioni su QDN (crittografato)?",
|
||||
"qdn": "usa il salvataggio su QDN",
|
||||
"register_name": "devi avere un nome Qortal registrato per salvare le tue app appuntate su QDN.",
|
||||
"reset_pinned": "non ti piacciono le modifiche locali attuali? Vuoi ripristinare le app appuntate predefinite?",
|
||||
"reset_qdn": "non ti piacciono le modifiche locali attuali? Vuoi ripristinare le app appuntate salvate su QDN?",
|
||||
"revert_default": "ripristina predefinito",
|
||||
"publish_qdn": "vuoi pubblicare le tue impostazioni su QDN (crittografate)?",
|
||||
"qdn": "usa salvataggio 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? Vuoi ripristinare le app fissate predefinite?",
|
||||
"reset_qdn": "non ti piacciono le modifiche locali? Vuoi ripristinare le app fissate salvate su QDN?",
|
||||
"revert_default": "ripristina ai valori predefiniti",
|
||||
"revert_qdn": "ripristina da QDN",
|
||||
"save_qdn": "salva su QDN",
|
||||
"save": "salva",
|
||||
"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",
|
||||
"supply": "disponibilità",
|
||||
"supply": "offerta",
|
||||
"theme": {
|
||||
"dark": "modalità scura",
|
||||
"light": "modalità chiara"
|
||||
@ -104,8 +126,8 @@
|
||||
"tutorial": "tutorial",
|
||||
"user_lookup": "ricerca utente",
|
||||
"wallet": {
|
||||
"wallet": "wallet",
|
||||
"wallet_other": "wallet"
|
||||
"wallet": "portafoglio",
|
||||
"wallet_other": "portafogli"
|
||||
},
|
||||
"welcome": "benvenuto"
|
||||
}
|
105
src/i18n/locales/it/group.json
Normal file
105
src/i18n/locales/it/group.json
Normal file
@ -0,0 +1,105 @@
|
||||
{
|
||||
"action": {
|
||||
"ban": "banna membro dal gruppo",
|
||||
"cancel_ban": "annulla ban",
|
||||
"copy_private_key": "copia chiave privata",
|
||||
"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",
|
||||
"join_group": "unisciti al gruppo",
|
||||
"kick_member": "espelli membro dal gruppo",
|
||||
"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",
|
||||
"remove_admin": "rimuovi come amministratore",
|
||||
"return_to_thread": "torna alle discussioni"
|
||||
},
|
||||
"advanced_options": "opzioni avanzate",
|
||||
"approval_threshold": "soglia di approvazione del gruppo (numero / percentuale di amministratori che devono approvare una transazione)",
|
||||
"ban_list": "lista bannati",
|
||||
"block_delay": {
|
||||
"minimum": "ritardo minimo dei blocchi per le approvazioni delle transazioni di gruppo",
|
||||
"maximum": "ritardo massimo dei blocchi per le approvazioni delle transazioni di gruppo"
|
||||
},
|
||||
"group": {
|
||||
"closed": "chiuso (privato) – gli utenti hanno bisogno del permesso per entrare",
|
||||
"description": "descrizione del gruppo",
|
||||
"id": "ID gruppo",
|
||||
"invites": "inviti del gruppo",
|
||||
"management": "gestione del gruppo",
|
||||
"member_number": "numero di membri",
|
||||
"name": "nome del gruppo",
|
||||
"open": "aperto (pubblico)",
|
||||
"type": "tipo di gruppo"
|
||||
},
|
||||
"invitation_expiry": "tempo di scadenza dell'invito",
|
||||
"invitees_list": "lista degli invitati",
|
||||
"join_link": "link per unirsi al gruppo",
|
||||
"join_requests": "richieste di adesione",
|
||||
"last_message": "ultimo messaggio",
|
||||
"latest_mails": "ultimi Q-Mails",
|
||||
"message": {
|
||||
"generic": {
|
||||
"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",
|
||||
"loading_members": "caricamento elenco membri con nomi... attendere.",
|
||||
"no_display": "niente da mostrare",
|
||||
"no_selection": "nessun gruppo selezionato",
|
||||
"not_part_group": "non fai parte del gruppo cifrato. Attendi che un amministratore re-critti le chiavi.",
|
||||
"only_encrypted": "verranno mostrati solo i messaggi non cifrati.",
|
||||
"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": {
|
||||
"access_name": "impossibile inviare un messaggio senza accesso al tuo nome",
|
||||
"descrypt_wallet": "errore nella decrittazione del portafoglio {{ :errorMessage }}",
|
||||
"description_required": "inserisci una descrizione",
|
||||
"group_info": "impossibile accedere alle informazioni del gruppo",
|
||||
"group_secret_key": "impossibile ottenere la chiave segreta del gruppo",
|
||||
"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": {
|
||||
"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_name": "gruppo {{group_name}} creato: in attesa di conferma",
|
||||
"group_creation_label": "gruppo {{name}} creato: successo!",
|
||||
"group_invite": "{{value}} invitato 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_label": "entrato nel gruppo {{name}}: successo!",
|
||||
"group_join_request": "richiesta di accesso al gruppo {{group_name}}: in attesa di conferma",
|
||||
"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"
|
||||
}
|
133
src/i18n/locales/ru/core.json
Normal file
133
src/i18n/locales/ru/core.json
Normal file
@ -0,0 +1,133 @@
|
||||
{
|
||||
"action": {
|
||||
"add": "добавить",
|
||||
"accept": "принять",
|
||||
"backup_account": "резервное копирование аккаунта",
|
||||
"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": {
|
||||
"block_height": "высота блока",
|
||||
"information": "информация ядра",
|
||||
"peers": "подключённые узлы",
|
||||
"version": "версия ядра"
|
||||
},
|
||||
"ui": {
|
||||
"version": "версия интерфейса"
|
||||
},
|
||||
"count": {
|
||||
"none": "нет",
|
||||
"one": "один"
|
||||
},
|
||||
"description": "описание",
|
||||
"downloading_qdn": "загрузка из QDN",
|
||||
"fee": {
|
||||
"payment": "комиссия за платёж",
|
||||
"publish": "комиссия за публикацию"
|
||||
},
|
||||
"general_settings": "общие настройки",
|
||||
"last_height": "последняя высота",
|
||||
"list": {
|
||||
"invite": "список приглашений",
|
||||
"join_request": "список запросов на вступление",
|
||||
"member": "список участников"
|
||||
},
|
||||
"loading": "загрузка...",
|
||||
"loading_posts": "загрузка сообщений... пожалуйста, подождите.",
|
||||
"message_us": "напишите нам в Telegram или Discord, если вам нужно 4 QORT, чтобы начать чат без ограничений",
|
||||
"message": {
|
||||
"error": {
|
||||
"generic": "произошла ошибка",
|
||||
"incorrect_password": "неверный пароль",
|
||||
"missing_field": "отсутствует: {{ field }}",
|
||||
"save_qdn": "не удалось сохранить в QDN"
|
||||
},
|
||||
"status": {
|
||||
"minting": "(выпуск монет)",
|
||||
"not_minting": "(не выпускается)",
|
||||
"synchronized": "синхронизировано",
|
||||
"synchronizing": "синхронизация"
|
||||
},
|
||||
"success": {
|
||||
"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": {
|
||||
"no_pinned_changes": "у вас нет изменений в закреплённых приложениях",
|
||||
"overwrite_changes": "не удалось загрузить ваши закреплённые приложения из QDN. Перезаписать изменения?",
|
||||
"overwrite_qdn": "перезаписать в QDN",
|
||||
"publish_qdn": "опубликовать настройки в QDN (в зашифрованном виде)?",
|
||||
"qdn": "использовать сохранение в QDN",
|
||||
"register_name": "вам нужно зарегистрированное имя Qortal, чтобы сохранять закреплённые приложения в QDN.",
|
||||
"reset_pinned": "не нравятся текущие локальные изменения? Сбросить до стандартных приложений?",
|
||||
"reset_qdn": "не нравятся текущие локальные изменения? Сбросить до сохранённых в QDN приложений?",
|
||||
"revert_default": "сбросить по умолчанию",
|
||||
"revert_qdn": "восстановить из QDN",
|
||||
"save_qdn": "сохранить в QDN",
|
||||
"save": "сохранить",
|
||||
"settings": "вы используете метод экспорта/импорта для сохранения настроек.",
|
||||
"unsaved_changes": "у вас есть несохранённые изменения в закреплённых приложениях. Сохраните их в QDN."
|
||||
},
|
||||
"settings": "настройки",
|
||||
"supply": "предложение",
|
||||
"theme": {
|
||||
"dark": "тёмная тема",
|
||||
"light": "светлая тема"
|
||||
},
|
||||
"time": {
|
||||
"day_one": "{{count}} день",
|
||||
"day_other": "{{count}} дней",
|
||||
"hour_one": "{{count}} час",
|
||||
"hour_other": "{{count}} часов",
|
||||
"minute_one": "{{count}} минута",
|
||||
"minute_other": "{{count}} минут"
|
||||
},
|
||||
"title": "заголовок",
|
||||
"tutorial": "учебник",
|
||||
"user_lookup": "поиск пользователя",
|
||||
"wallet": {
|
||||
"wallet": "кошелёк",
|
||||
"wallet_other": "кошельки"
|
||||
},
|
||||
"welcome": "добро пожаловать"
|
||||
}
|
105
src/i18n/locales/ru/group.json
Normal file
105
src/i18n/locales/ru/group.json
Normal 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": "новые сообщения в теме"
|
||||
}
|
@ -5,7 +5,7 @@ import './messaging/messagesToBackground';
|
||||
import { MessageQueueProvider } from './MessageQueueContext.tsx';
|
||||
import { ThemeProvider } from './components/Theme/ThemeContext.tsx';
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import '../i18n';
|
||||
import './i18n/i18n.js';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<>
|
||||
|
@ -61,6 +61,9 @@ import {
|
||||
buyNameRequest,
|
||||
sellNameRequest,
|
||||
cancelSellNameRequest,
|
||||
signForeignFees,
|
||||
multiPaymentWithPrivateData,
|
||||
transferAssetRequest,
|
||||
} from './qortalRequests/get';
|
||||
import { getData, storeData } from './utils/chromeStorage';
|
||||
import { executeEvent } from './utils/events';
|
||||
@ -752,7 +755,7 @@ function setupMessageListenerQortalRequest() {
|
||||
|
||||
case 'UPDATE_FOREIGN_FEE': {
|
||||
try {
|
||||
const res = await updateForeignFee(request.payload);
|
||||
const res = await updateForeignFee(request.payload, isFromExtension);
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
@ -804,7 +807,10 @@ function setupMessageListenerQortalRequest() {
|
||||
|
||||
case 'SET_CURRENT_FOREIGN_SERVER': {
|
||||
try {
|
||||
const res = await setCurrentForeignServer(request.payload);
|
||||
const res = await setCurrentForeignServer(
|
||||
request.payload,
|
||||
isFromExtension
|
||||
);
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
@ -830,7 +836,7 @@ function setupMessageListenerQortalRequest() {
|
||||
|
||||
case 'ADD_FOREIGN_SERVER': {
|
||||
try {
|
||||
const res = await addForeignServer(request.payload);
|
||||
const res = await addForeignServer(request.payload, isFromExtension);
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
@ -856,7 +862,10 @@ function setupMessageListenerQortalRequest() {
|
||||
|
||||
case 'REMOVE_FOREIGN_SERVER': {
|
||||
try {
|
||||
const res = await removeForeignServer(request.payload);
|
||||
const res = await removeForeignServer(
|
||||
request.payload,
|
||||
isFromExtension
|
||||
);
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
@ -1755,6 +1764,63 @@ function setupMessageListenerQortalRequest() {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA': {
|
||||
try {
|
||||
const res = await multiPaymentWithPrivateData(
|
||||
request.payload,
|
||||
isFromExtension
|
||||
);
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
action: request.action,
|
||||
payload: res,
|
||||
type: 'backgroundMessageResponse',
|
||||
},
|
||||
event.origin
|
||||
);
|
||||
} catch (error) {
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
action: request.action,
|
||||
error: error?.message,
|
||||
type: 'backgroundMessageResponse',
|
||||
},
|
||||
event.origin
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'TRANSFER_ASSET': {
|
||||
try {
|
||||
const res = await transferAssetRequest(
|
||||
request.payload,
|
||||
isFromExtension
|
||||
);
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
action: request.action,
|
||||
payload: res,
|
||||
type: 'backgroundMessageResponse',
|
||||
},
|
||||
event.origin
|
||||
);
|
||||
} catch (error) {
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
action: request.action,
|
||||
error: error?.message,
|
||||
type: 'backgroundMessageResponse',
|
||||
},
|
||||
event.origin
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'BUY_NAME': {
|
||||
try {
|
||||
const res = await buyNameRequest(request.payload, isFromExtension);
|
||||
@ -1833,6 +1899,32 @@ function setupMessageListenerQortalRequest() {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'SIGN_FOREIGN_FEES': {
|
||||
try {
|
||||
const res = await signForeignFees(request.payload, isFromExtension);
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
action: request.action,
|
||||
payload: res,
|
||||
type: 'backgroundMessageResponse',
|
||||
},
|
||||
event.origin
|
||||
);
|
||||
} catch (error) {
|
||||
event.source.postMessage(
|
||||
{
|
||||
requestId: request.requestId,
|
||||
action: request.action,
|
||||
error: error.message,
|
||||
type: 'backgroundMessageResponse',
|
||||
},
|
||||
event.origin
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -32,6 +32,11 @@ import {
|
||||
cancelSellName,
|
||||
buyName,
|
||||
getBaseApi,
|
||||
getAssetBalanceInfo,
|
||||
getNameOrAddress,
|
||||
getAssetInfo,
|
||||
getPublicKey,
|
||||
transferAsset,
|
||||
} from '../background';
|
||||
import {
|
||||
getNameInfo,
|
||||
@ -50,6 +55,7 @@ import { QORT_DECIMALS } from '../constants/constants';
|
||||
import Base58 from '../deps/Base58';
|
||||
import ed2curve from '../deps/ed2curve';
|
||||
import nacl from '../deps/nacl-fast';
|
||||
|
||||
import {
|
||||
base64ToUint8Array,
|
||||
createSymmetricKeyAndNonce,
|
||||
@ -78,6 +84,10 @@ import { fileToBase64 } from '../utils/fileReading';
|
||||
import { mimeToExtensionMap } from '../utils/memeTypes';
|
||||
import { RequestQueueWithPromise } from '../utils/queue/queue';
|
||||
import utils from '../utils/utils';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { isValidBase64WithDecode } from '../utils/decode';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 6 });
|
||||
|
||||
export const requestQueueGetAtAddresses = new RequestQueueWithPromise(10);
|
||||
|
||||
@ -1293,10 +1303,7 @@ export const publishMultipleQDNResources = async (
|
||||
html: `
|
||||
<div style="max-height: 30vh; overflow-y: auto;">
|
||||
<style>
|
||||
body {
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
|
||||
.resource-container {
|
||||
display: flex;
|
||||
@ -1305,7 +1312,7 @@ export const publishMultipleQDNResources = async (
|
||||
padding: 16px;
|
||||
margin: 8px 0;
|
||||
border-radius: 8px;
|
||||
background-color: #1e1e1e;
|
||||
background-color: var(--background-default);
|
||||
}
|
||||
|
||||
.resource-detail {
|
||||
@ -1314,7 +1321,7 @@ export const publishMultipleQDNResources = async (
|
||||
|
||||
.resource-detail span {
|
||||
font-weight: bold;
|
||||
color: #bb86fc;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
@ -2649,7 +2656,12 @@ export const getForeignFee = async (data) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateForeignFee = async (data) => {
|
||||
function calculateRateFromFee(totalFee, sizeInBytes) {
|
||||
const fee = (totalFee / sizeInBytes) * 1000;
|
||||
return fee.toFixed(0);
|
||||
}
|
||||
|
||||
export const updateForeignFee = async (data, isFromExtension) => {
|
||||
const isGateway = await isRunningGateway();
|
||||
if (isGateway) {
|
||||
throw new Error('This action cannot be done through a public node');
|
||||
@ -2670,33 +2682,52 @@ export const updateForeignFee = async (data) => {
|
||||
}
|
||||
|
||||
const { coin, type, value } = data;
|
||||
const url = `/crosschain/${coin.toLowerCase()}/update${type}`;
|
||||
|
||||
try {
|
||||
const endpoint = await createEndpoint(url);
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ value }),
|
||||
});
|
||||
const text3 =
|
||||
type === 'feerequired' ? `${value} sats` : `${value} sats per kb`;
|
||||
const text4 =
|
||||
type === 'feerequired'
|
||||
? `*The ${value} sats fee is derived from ${calculateRateFromFee(value, 300)} sats per kb, for a transaction that is approximately 300 bytes in size.`
|
||||
: '';
|
||||
const resPermission = await getUserPermission(
|
||||
{
|
||||
text1: `Do you give this application permission to update foreign fees on your node?`,
|
||||
text2: `type: ${type === 'feerequired' ? 'unlocking' : 'locking'}`,
|
||||
text3: `value: ${text3}`,
|
||||
text4,
|
||||
highlightedText: `Coin: ${coin}`,
|
||||
},
|
||||
isFromExtension
|
||||
);
|
||||
|
||||
if (!response.ok) throw new Error('Failed to update foreign fee');
|
||||
let res;
|
||||
try {
|
||||
res = await response.clone().json();
|
||||
} catch (e) {
|
||||
res = await response.text();
|
||||
}
|
||||
if (res?.error && res?.message) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
return res; // Return full response here
|
||||
} catch (error) {
|
||||
throw new Error(error?.message || 'Error in update foreign fee');
|
||||
const { accepted } = resPermission;
|
||||
if (!accepted) {
|
||||
throw new Error('User declined request');
|
||||
}
|
||||
const url = `/crosschain/${coin.toLowerCase()}/update${type}`;
|
||||
const valueStringified = JSON.stringify(+value);
|
||||
|
||||
const endpoint = await createEndpoint(url);
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: valueStringified,
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to update foreign fee');
|
||||
let res;
|
||||
try {
|
||||
res = await response.clone().json();
|
||||
} catch (e) {
|
||||
res = await response.text();
|
||||
}
|
||||
if (res?.error && res?.message) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
return res; // Return full response here
|
||||
};
|
||||
|
||||
export const getServerConnectionHistory = async (data) => {
|
||||
@ -2749,7 +2780,7 @@ export const getServerConnectionHistory = async (data) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const setCurrentForeignServer = async (data) => {
|
||||
export const setCurrentForeignServer = async (data, isFromExtension) => {
|
||||
const isGateway = await isRunningGateway();
|
||||
if (isGateway) {
|
||||
throw new Error('This action cannot be done through a public node');
|
||||
@ -2771,6 +2802,21 @@ export const setCurrentForeignServer = async (data) => {
|
||||
}
|
||||
|
||||
const { coin, host, port, type } = data;
|
||||
|
||||
const resPermission = await getUserPermission(
|
||||
{
|
||||
text1: `Do you give this application permission to set the current server?`,
|
||||
text2: `type: ${type}`,
|
||||
text3: `host: ${host}`,
|
||||
highlightedText: `Coin: ${coin}`,
|
||||
},
|
||||
isFromExtension
|
||||
);
|
||||
|
||||
const { accepted } = resPermission;
|
||||
if (!accepted) {
|
||||
throw new Error('User declined request');
|
||||
}
|
||||
const body = {
|
||||
hostName: host,
|
||||
port: port,
|
||||
@ -2779,37 +2825,33 @@ export const setCurrentForeignServer = async (data) => {
|
||||
|
||||
const url = `/crosschain/${coin.toLowerCase()}/setcurrentserver`;
|
||||
|
||||
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to set current server');
|
||||
|
||||
let res;
|
||||
try {
|
||||
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to set current server');
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await response.clone().json();
|
||||
} catch (e) {
|
||||
res = await response.text();
|
||||
}
|
||||
|
||||
if (res?.error && res?.message) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
|
||||
return res; // Return the full response
|
||||
} catch (error) {
|
||||
throw new Error(error?.message || 'Error in set current server');
|
||||
res = await response.clone().json();
|
||||
} catch (e) {
|
||||
res = await response.text();
|
||||
}
|
||||
|
||||
if (res?.error && res?.message) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
|
||||
return res; // Return the full response
|
||||
};
|
||||
|
||||
export const addForeignServer = async (data) => {
|
||||
export const addForeignServer = async (data, isFromExtension) => {
|
||||
const isGateway = await isRunningGateway();
|
||||
if (isGateway) {
|
||||
throw new Error('This action cannot be done through a public node');
|
||||
@ -2831,6 +2873,21 @@ export const addForeignServer = async (data) => {
|
||||
}
|
||||
|
||||
const { coin, host, port, type } = data;
|
||||
|
||||
const resPermission = await getUserPermission(
|
||||
{
|
||||
text1: `Do you give this application permission to add a server?`,
|
||||
text2: `type: ${type}`,
|
||||
text3: `host: ${host}`,
|
||||
highlightedText: `Coin: ${coin}`,
|
||||
},
|
||||
isFromExtension
|
||||
);
|
||||
|
||||
const { accepted } = resPermission;
|
||||
if (!accepted) {
|
||||
throw new Error('User declined request');
|
||||
}
|
||||
const body = {
|
||||
hostName: host,
|
||||
port: port,
|
||||
@ -2839,37 +2896,33 @@ export const addForeignServer = async (data) => {
|
||||
|
||||
const url = `/crosschain/${coin.toLowerCase()}/addserver`;
|
||||
|
||||
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to add server');
|
||||
|
||||
let res;
|
||||
try {
|
||||
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to add server');
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await response.clone().json();
|
||||
} catch (e) {
|
||||
res = await response.text();
|
||||
}
|
||||
|
||||
if (res?.error && res?.message) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
|
||||
return res; // Return the full response
|
||||
} catch (error) {
|
||||
throw new Error(error.message || 'Error in adding server');
|
||||
res = await response.clone().json();
|
||||
} catch (e) {
|
||||
res = await response.text();
|
||||
}
|
||||
|
||||
if (res?.error && res?.message) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
|
||||
return res; // Return the full response
|
||||
};
|
||||
|
||||
export const removeForeignServer = async (data) => {
|
||||
export const removeForeignServer = async (data, isFromExtension) => {
|
||||
const isGateway = await isRunningGateway();
|
||||
if (isGateway) {
|
||||
throw new Error('This action cannot be done through a public node');
|
||||
@ -2891,6 +2944,21 @@ export const removeForeignServer = async (data) => {
|
||||
}
|
||||
|
||||
const { coin, host, port, type } = data;
|
||||
|
||||
const resPermission = await getUserPermission(
|
||||
{
|
||||
text1: `Do you give this application permission to remove a server?`,
|
||||
text2: `type: ${type}`,
|
||||
text3: `host: ${host}`,
|
||||
highlightedText: `Coin: ${coin}`,
|
||||
},
|
||||
isFromExtension
|
||||
);
|
||||
|
||||
const { accepted } = resPermission;
|
||||
if (!accepted) {
|
||||
throw new Error('User declined request');
|
||||
}
|
||||
const body = {
|
||||
hostName: host,
|
||||
port: port,
|
||||
@ -2899,34 +2967,30 @@ export const removeForeignServer = async (data) => {
|
||||
|
||||
const url = `/crosschain/${coin.toLowerCase()}/removeserver`;
|
||||
|
||||
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to remove server');
|
||||
|
||||
let res;
|
||||
try {
|
||||
const endpoint = await createEndpoint(url); // Assuming createEndpoint is available
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to remove server');
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await response.clone().json();
|
||||
} catch (e) {
|
||||
res = await response.text();
|
||||
}
|
||||
|
||||
if (res?.error && res?.message) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
|
||||
return res; // Return the full response
|
||||
} catch (error) {
|
||||
throw new Error(error?.message || 'Error in removing server');
|
||||
res = await response.clone().json();
|
||||
} catch (e) {
|
||||
res = await response.text();
|
||||
}
|
||||
|
||||
if (res?.error && res?.message) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
|
||||
return res; // Return the full response
|
||||
};
|
||||
|
||||
export const getDaySummary = async () => {
|
||||
@ -3484,6 +3548,35 @@ export const sendCoin = async (data, isFromExtension) => {
|
||||
}
|
||||
};
|
||||
|
||||
function calculateFeeFromRate(feePerKb, sizeInBytes) {
|
||||
return (feePerKb / 1000) * sizeInBytes;
|
||||
}
|
||||
|
||||
const getBuyingFees = async (foreignBlockchain) => {
|
||||
const ticker = sellerForeignFee[foreignBlockchain].ticker;
|
||||
if (!ticker) throw new Error('invalid foreign blockchain');
|
||||
const unlockFee = await getForeignFee({
|
||||
coin: ticker,
|
||||
type: 'feerequired',
|
||||
});
|
||||
const lockFee = await getForeignFee({
|
||||
coin: ticker,
|
||||
type: 'feekb',
|
||||
});
|
||||
return {
|
||||
ticker: ticker,
|
||||
lock: {
|
||||
sats: lockFee,
|
||||
fee: lockFee / QORT_DECIMALS,
|
||||
},
|
||||
unlock: {
|
||||
sats: unlockFee,
|
||||
fee: unlockFee / QORT_DECIMALS,
|
||||
feePerKb: +calculateRateFromFee(+unlockFee, 300) / QORT_DECIMALS,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const createBuyOrder = async (data, isFromExtension) => {
|
||||
const requiredFields = ['crosschainAtInfo', 'foreignBlockchain'];
|
||||
const missingFields: string[] = [];
|
||||
@ -3519,6 +3612,7 @@ export const createBuyOrder = async (data, isFromExtension) => {
|
||||
|
||||
const crosschainAtInfo = await Promise.all(atPromises);
|
||||
try {
|
||||
const buyingFees = await getBuyingFees(foreignBlockchain);
|
||||
const resPermission = await getUserPermission(
|
||||
{
|
||||
text1:
|
||||
@ -3532,10 +3626,45 @@ export const createBuyOrder = async (data, isFromExtension) => {
|
||||
return latest + +cur?.expectedForeignAmount;
|
||||
}, 0)
|
||||
)}
|
||||
${` ${crosschainAtInfo?.[0]?.foreignBlockchain}`}`,
|
||||
${` ${buyingFees.ticker}`}`,
|
||||
highlightedText: `Is using public node: ${isGateway}`,
|
||||
fee: '',
|
||||
foreignFee: `${sellerForeignFee[foreignBlockchain].value} ${sellerForeignFee[foreignBlockchain].ticker}`,
|
||||
html: `
|
||||
<div style="max-height: 30vh; overflow-y: auto; font-family: sans-serif;">
|
||||
<style>
|
||||
.fee-container {
|
||||
background-color: var(--background-default);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid #444;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.fee-label {
|
||||
font-weight: bold;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.fee-description {
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="fee-container">
|
||||
<div class="fee-label">Total Unlocking Fee:</div>
|
||||
<div>${(+buyingFees?.unlock?.fee * atAddresses?.length)?.toFixed(8)} ${buyingFees.ticker}</div>
|
||||
<div class="fee-description">
|
||||
This fee is an estimate based on ${atAddresses?.length} ${atAddresses?.length > 1 ? 'orders' : 'order'}, assuming a 300-byte size at a rate of ${buyingFees?.unlock?.feePerKb?.toFixed(8)} ${buyingFees.ticker} per KB.
|
||||
</div>
|
||||
|
||||
<div class="fee-label">Total Locking Fee:</div>
|
||||
<div>${+buyingFees?.lock.fee.toFixed(8)} ${buyingFees.ticker} per kb</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
isFromExtension
|
||||
);
|
||||
@ -4924,3 +5053,459 @@ export const buyNameRequest = async (data, isFromExtension) => {
|
||||
throw new Error('User declined request');
|
||||
}
|
||||
};
|
||||
|
||||
export const signForeignFees = async (data, isFromExtension) => {
|
||||
const resPermission = await getUserPermission(
|
||||
{
|
||||
text1: `Do you give this application permission to sign the required fees for all your trade offers?`,
|
||||
},
|
||||
isFromExtension
|
||||
);
|
||||
const { accepted } = resPermission;
|
||||
if (accepted) {
|
||||
const wallet = await getSaveWallet();
|
||||
const address = wallet.address0;
|
||||
const resKeyPair = await getKeyPair();
|
||||
const parsedData = resKeyPair;
|
||||
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
||||
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
||||
const keyPair = {
|
||||
privateKey: uint8PrivateKey,
|
||||
publicKey: uint8PublicKey,
|
||||
};
|
||||
|
||||
const unsignedFeesUrl = await createEndpoint(
|
||||
`/crosschain/unsignedfees/${address}`
|
||||
);
|
||||
|
||||
const unsignedFeesResponse = await fetch(unsignedFeesUrl);
|
||||
|
||||
const unsignedFees = await unsignedFeesResponse.json();
|
||||
|
||||
const signedFees = [];
|
||||
|
||||
unsignedFees.forEach((unsignedFee) => {
|
||||
const unsignedDataDecoded = Base58.decode(unsignedFee.data);
|
||||
|
||||
const signature = nacl.sign.detached(
|
||||
unsignedDataDecoded,
|
||||
keyPair.privateKey
|
||||
);
|
||||
|
||||
const signedFee = {
|
||||
timestamp: unsignedFee.timestamp,
|
||||
data: `${Base58.encode(signature)}`,
|
||||
atAddress: unsignedFee.atAddress,
|
||||
fee: unsignedFee.fee,
|
||||
};
|
||||
|
||||
signedFees.push(signedFee);
|
||||
});
|
||||
|
||||
const signedFeesUrl = await createEndpoint(`/crosschain/signedfees`);
|
||||
|
||||
await fetch(signedFeesUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: `${JSON.stringify(signedFees)}`,
|
||||
});
|
||||
|
||||
return true;
|
||||
} else {
|
||||
throw new Error('User declined request');
|
||||
}
|
||||
};
|
||||
export const multiPaymentWithPrivateData = async (data, isFromExtension) => {
|
||||
const requiredFields = ['payments', 'assetId'];
|
||||
requiredFields.forEach((field) => {
|
||||
if (data[field] === undefined || data[field] === null) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
});
|
||||
const resKeyPair = await getKeyPair();
|
||||
const parsedData = resKeyPair;
|
||||
const privateKey = parsedData.privateKey;
|
||||
const userPublicKey = parsedData.publicKey;
|
||||
const { fee: paymentFee } = await getFee('TRANSFER_ASSET');
|
||||
const { fee: arbitraryFee } = await getFee('ARBITRARY');
|
||||
|
||||
let name = null;
|
||||
const payments = data.payments;
|
||||
const assetId = data.assetId;
|
||||
const pendingTransactions = [];
|
||||
const pendingAdditionalArbitraryTxs = [];
|
||||
const additionalArbitraryTxsWithoutPayment =
|
||||
data?.additionalArbitraryTxsWithoutPayment || [];
|
||||
let totalAmount = 0;
|
||||
let fee = 0;
|
||||
for (const payment of payments) {
|
||||
const paymentRefId = uid.rnd();
|
||||
const requiredFieldsPayment = ['recipient', 'amount'];
|
||||
|
||||
for (const field of requiredFieldsPayment) {
|
||||
if (!payment[field]) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
const confirmReceiver = await getNameOrAddress(payment.recipient);
|
||||
if (confirmReceiver.error) {
|
||||
throw new Error('Invalid receiver address or name');
|
||||
}
|
||||
const receiverPublicKey = await getPublicKey(confirmReceiver);
|
||||
|
||||
const amount = +payment.amount.toFixed(8);
|
||||
|
||||
pendingTransactions.push({
|
||||
type: 'PAYMENT',
|
||||
recipientAddress: confirmReceiver,
|
||||
amount: amount,
|
||||
paymentRefId,
|
||||
});
|
||||
|
||||
fee = fee + +paymentFee;
|
||||
totalAmount = totalAmount + amount;
|
||||
|
||||
if (payment.arbitraryTxs && payment.arbitraryTxs.length > 0) {
|
||||
for (const arbitraryTx of payment.arbitraryTxs) {
|
||||
const requiredFieldsArbitraryTx = ['service', 'identifier', 'base64'];
|
||||
|
||||
for (const field of requiredFieldsArbitraryTx) {
|
||||
if (!arbitraryTx[field]) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
const getName = await getNameInfo();
|
||||
if (!getName) throw new Error('Name needed to publish');
|
||||
name = getName;
|
||||
}
|
||||
|
||||
const isValid = isValidBase64WithDecode(arbitraryTx.base64);
|
||||
if (!isValid) throw new Error('Invalid base64 data');
|
||||
if (!arbitraryTx?.service?.includes('_PRIVATE'))
|
||||
throw new Error('Please use a PRIVATE service');
|
||||
const additionalPublicKeys = arbitraryTx?.additionalPublicKeys || [];
|
||||
pendingTransactions.push({
|
||||
type: 'ARBITRARY',
|
||||
identifier: arbitraryTx.identifier,
|
||||
service: arbitraryTx.service,
|
||||
base64: arbitraryTx.base64,
|
||||
description: arbitraryTx?.description || '',
|
||||
paymentRefId,
|
||||
publicKeys: [receiverPublicKey, ...additionalPublicKeys],
|
||||
});
|
||||
|
||||
fee = fee + +arbitraryFee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
additionalArbitraryTxsWithoutPayment &&
|
||||
additionalArbitraryTxsWithoutPayment.length > 0
|
||||
) {
|
||||
for (const arbitraryTx of additionalArbitraryTxsWithoutPayment) {
|
||||
const requiredFieldsArbitraryTx = ['service', 'identifier', 'base64'];
|
||||
|
||||
for (const field of requiredFieldsArbitraryTx) {
|
||||
if (!arbitraryTx[field]) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
const getName = await getNameInfo();
|
||||
if (!getName) throw new Error('Name needed to publish');
|
||||
name = getName;
|
||||
}
|
||||
|
||||
const isValid = isValidBase64WithDecode(arbitraryTx.base64);
|
||||
if (!isValid) throw new Error('Invalid base64 data');
|
||||
if (!arbitraryTx?.service?.includes('_PRIVATE'))
|
||||
throw new Error('Please use a PRIVATE service');
|
||||
const additionalPublicKeys = arbitraryTx?.additionalPublicKeys || [];
|
||||
pendingAdditionalArbitraryTxs.push({
|
||||
type: 'ARBITRARY',
|
||||
identifier: arbitraryTx.identifier,
|
||||
service: arbitraryTx.service,
|
||||
base64: arbitraryTx.base64,
|
||||
description: arbitraryTx?.description || '',
|
||||
publicKeys: additionalPublicKeys,
|
||||
});
|
||||
|
||||
fee = fee + +arbitraryFee;
|
||||
}
|
||||
}
|
||||
|
||||
if (!name) throw new Error('A name is needed to publish');
|
||||
const balance = await getBalanceInfo();
|
||||
|
||||
if (+balance < fee) throw new Error('Your QORT balance is insufficient');
|
||||
const assetBalance = await getAssetBalanceInfo(assetId);
|
||||
const assetInfo = await getAssetInfo(assetId);
|
||||
if (assetBalance < totalAmount)
|
||||
throw new Error('Your asset balance is insufficient');
|
||||
|
||||
const resPermission = await getUserPermission(
|
||||
{
|
||||
text1:
|
||||
'Do you give this application permission to make the following payments and publishes?',
|
||||
text2: `Asset used in payments: ${assetInfo.name}`,
|
||||
html: `
|
||||
<div style="max-height: 30vh; overflow-y: auto;">
|
||||
<style>
|
||||
|
||||
.resource-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid;
|
||||
padding: 16px;
|
||||
margin: 8px 0;
|
||||
border-radius: 8px;
|
||||
background-color: var(--background-default);
|
||||
}
|
||||
|
||||
.resource-detail {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.resource-detail span {
|
||||
font-weight: bold;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.resource-container {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.resource-detail {
|
||||
flex: 1 1 45%;
|
||||
margin-bottom: 0;
|
||||
padding: 4px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
${pendingTransactions
|
||||
.filter((item) => item.type === 'PAYMENT')
|
||||
.map(
|
||||
(payment) => `
|
||||
<div class="resource-container">
|
||||
<div class="resource-detail"><span>Recipient:</span> ${
|
||||
payment.recipientAddress
|
||||
}</div>
|
||||
<div class="resource-detail"><span>Amount:</span> ${payment.amount}</div>
|
||||
</div>`
|
||||
)
|
||||
.join('')}
|
||||
${[...pendingTransactions, ...pendingAdditionalArbitraryTxs]
|
||||
.filter((item) => item.type === 'ARBITRARY')
|
||||
.map(
|
||||
(arbitraryTx) => `
|
||||
<div class="resource-container">
|
||||
<div class="resource-detail"><span>Service:</span> ${
|
||||
arbitraryTx.service
|
||||
}</div>
|
||||
<div class="resource-detail"><span>Name:</span> ${name}</div>
|
||||
<div class="resource-detail"><span>Identifier:</span> ${
|
||||
arbitraryTx.identifier
|
||||
}</div>
|
||||
</div>`
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
|
||||
`,
|
||||
highlightedText: `Total Amount: ${totalAmount}`,
|
||||
fee: fee,
|
||||
},
|
||||
isFromExtension
|
||||
);
|
||||
const { accepted, checkbox1 = false } = resPermission;
|
||||
if (!accepted) {
|
||||
throw new Error('User declined request');
|
||||
}
|
||||
|
||||
// const failedTxs = []
|
||||
const paymentsDone = {};
|
||||
|
||||
const transactionsDone = [];
|
||||
|
||||
for (const transaction of pendingTransactions) {
|
||||
const type = transaction.type;
|
||||
|
||||
if (type === 'PAYMENT') {
|
||||
const makePayment = await retryTransaction(
|
||||
transferAsset,
|
||||
[
|
||||
{
|
||||
amount: transaction.amount,
|
||||
assetId,
|
||||
recipient: transaction.recipientAddress,
|
||||
},
|
||||
],
|
||||
true
|
||||
);
|
||||
if (makePayment) {
|
||||
transactionsDone.push(makePayment?.signature);
|
||||
if (transaction.paymentRefId) {
|
||||
paymentsDone[transaction.paymentRefId] = makePayment;
|
||||
}
|
||||
}
|
||||
} else if (type === 'ARBITRARY' && paymentsDone[transaction.paymentRefId]) {
|
||||
const objectToEncrypt = {
|
||||
data: transaction.base64,
|
||||
payment: paymentsDone[transaction.paymentRefId],
|
||||
};
|
||||
|
||||
const toBase64 = await retryTransaction(
|
||||
objectToBase64,
|
||||
[objectToEncrypt],
|
||||
true
|
||||
);
|
||||
|
||||
if (!toBase64) continue; // Skip if encryption fails
|
||||
|
||||
const encryptDataResponse = await retryTransaction(
|
||||
encryptDataGroup,
|
||||
[
|
||||
{
|
||||
data64: toBase64,
|
||||
publicKeys: transaction.publicKeys,
|
||||
privateKey,
|
||||
userPublicKey,
|
||||
},
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
if (!encryptDataResponse) continue; // Skip if encryption fails
|
||||
|
||||
const resPublish = await retryTransaction(
|
||||
publishData,
|
||||
[
|
||||
{
|
||||
registeredName: encodeURIComponent(name),
|
||||
file: encryptDataResponse,
|
||||
service: transaction.service,
|
||||
identifier: encodeURIComponent(transaction.identifier),
|
||||
uploadType: 'file',
|
||||
description: transaction?.description,
|
||||
isBase64: true,
|
||||
apiVersion: 2,
|
||||
withFee: true,
|
||||
},
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
if (resPublish?.signature) {
|
||||
transactionsDone.push(resPublish?.signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const transaction of pendingAdditionalArbitraryTxs) {
|
||||
const objectToEncrypt = {
|
||||
data: transaction.base64,
|
||||
};
|
||||
|
||||
const toBase64 = await retryTransaction(
|
||||
objectToBase64,
|
||||
[objectToEncrypt],
|
||||
true
|
||||
);
|
||||
|
||||
if (!toBase64) continue; // Skip if encryption fails
|
||||
|
||||
const encryptDataResponse = await retryTransaction(
|
||||
encryptDataGroup,
|
||||
[
|
||||
{
|
||||
data64: toBase64,
|
||||
publicKeys: transaction.publicKeys,
|
||||
privateKey,
|
||||
userPublicKey,
|
||||
},
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
if (!encryptDataResponse) continue; // Skip if encryption fails
|
||||
|
||||
const resPublish = await retryTransaction(
|
||||
publishData,
|
||||
[
|
||||
{
|
||||
registeredName: encodeURIComponent(name),
|
||||
file: encryptDataResponse,
|
||||
service: transaction.service,
|
||||
identifier: encodeURIComponent(transaction.identifier),
|
||||
uploadType: 'file',
|
||||
description: transaction?.description,
|
||||
isBase64: true,
|
||||
apiVersion: 2,
|
||||
withFee: true,
|
||||
},
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
if (resPublish?.signature) {
|
||||
transactionsDone.push(resPublish?.signature);
|
||||
}
|
||||
}
|
||||
|
||||
return transactionsDone;
|
||||
};
|
||||
|
||||
export const transferAssetRequest = async (data, isFromExtension) => {
|
||||
const requiredFields = ['amount', 'assetId', 'recipient'];
|
||||
requiredFields.forEach((field) => {
|
||||
if (data[field] === undefined || data[field] === null) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
});
|
||||
const amount = data.amount;
|
||||
const assetId = data.assetId;
|
||||
const recipient = data.recipient;
|
||||
|
||||
const { fee } = await getFee('TRANSFER_ASSET');
|
||||
const balance = await getBalanceInfo();
|
||||
|
||||
if (+balance < +fee) throw new Error('Your QORT balance is insufficient');
|
||||
const assetBalance = await getAssetBalanceInfo(assetId);
|
||||
if (assetBalance < amount)
|
||||
throw new Error('Your asset balance is insufficient');
|
||||
const confirmReceiver = await getNameOrAddress(recipient);
|
||||
if (confirmReceiver.error) {
|
||||
throw new Error('Invalid receiver address or name');
|
||||
}
|
||||
const assetInfo = await getAssetInfo(assetId);
|
||||
const resPermission = await getUserPermission(
|
||||
{
|
||||
text1: `Do you give this application permission to transfer the following asset?`,
|
||||
text2: `Asset: ${assetInfo?.name}`,
|
||||
highlightedText: `Amount: ${amount}`,
|
||||
fee: fee,
|
||||
},
|
||||
isFromExtension
|
||||
);
|
||||
|
||||
const { accepted } = resPermission;
|
||||
if (!accepted) {
|
||||
throw new Error('User declined request');
|
||||
}
|
||||
const res = await transferAsset({
|
||||
amount,
|
||||
recipient: confirmReceiver,
|
||||
assetId,
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
@ -8,15 +8,14 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tooltip .bottom {
|
||||
.tooltip .core-panel {
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--black);
|
||||
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
|
||||
box-sizing: border-box;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
max-width: 250px;
|
||||
min-width: 225px;
|
||||
width: max-content;
|
||||
opacity: 0;
|
||||
padding: 10px 10px;
|
||||
position: absolute;
|
||||
@ -27,23 +26,23 @@
|
||||
z-index: 99999999;
|
||||
}
|
||||
|
||||
.tooltip[data-theme='light'] .bottom {
|
||||
.tooltip[data-theme='light'] .core-panel {
|
||||
background-color: #f1f1f1;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.tooltip[data-theme='dark'] .bottom {
|
||||
.tooltip[data-theme='dark'] .core-panel {
|
||||
background-color: var(--bg-2);
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.tooltip:hover .bottom {
|
||||
.tooltip:hover .core-panel {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.tooltip .bottom i {
|
||||
.tooltip .core-panel i {
|
||||
bottom: 100%;
|
||||
height: 12px;
|
||||
left: 50%;
|
||||
|
@ -56,6 +56,7 @@ const commonThemeOptions = {
|
||||
xl: 1536,
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
@ -72,6 +73,7 @@ const commonThemeOptions = {
|
||||
disableRipple: true,
|
||||
},
|
||||
},
|
||||
|
||||
MuiModal: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
|
@ -48,6 +48,7 @@ export const darkThemeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: (theme) => ({
|
||||
':root': {
|
||||
@ -55,14 +56,22 @@ export const darkThemeOptions: ThemeOptions = {
|
||||
'--bg-primary': 'rgba(31, 32, 35, 1)',
|
||||
'--bg-2': 'rgb(39, 40, 44)',
|
||||
'--primary-main': theme.palette.primary.main,
|
||||
'--text-primary': theme.palette.text.primary,
|
||||
'--text-secondary': theme.palette.text.secondary,
|
||||
'--background-default': theme.palette.background.default,
|
||||
'--background-paper': theme.palette.background.paper,
|
||||
'--background-surface': theme.palette.background.surface,
|
||||
},
|
||||
|
||||
'*, *::before, *::after': {
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
|
||||
html: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
},
|
||||
|
||||
body: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
@ -95,6 +104,7 @@ export const darkThemeOptions: ThemeOptions = {
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
MuiIcon: {
|
||||
defaultProps: {
|
||||
style: {
|
||||
@ -103,6 +113,7 @@ export const darkThemeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
MuiDialog: {
|
||||
styleOverrides: {
|
||||
paper: {
|
||||
@ -110,6 +121,7 @@ export const darkThemeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
MuiPopover: {
|
||||
styleOverrides: {
|
||||
paper: {
|
||||
|
@ -32,6 +32,7 @@ export const lightThemeOptions: ThemeOptions = {
|
||||
unread: 'rgb(66, 151, 226)',
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
MuiCard: {
|
||||
styleOverrides: {
|
||||
@ -48,6 +49,7 @@ export const lightThemeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: (theme) => ({
|
||||
':root': {
|
||||
@ -55,6 +57,11 @@ export const lightThemeOptions: ThemeOptions = {
|
||||
'--bg-primary': 'rgba(31, 32, 35, 1)',
|
||||
'--bg-2': 'rgba(39, 40, 44, 1)',
|
||||
'--primary-main': theme.palette.primary.main,
|
||||
'--text-primary': theme.palette.text.primary,
|
||||
'--text-secondary': theme.palette.text.secondary,
|
||||
'--background-default': theme.palette.background.default,
|
||||
'--background-paper': theme.palette.background.paper,
|
||||
'--background-surface': theme.palette.background.surface,
|
||||
},
|
||||
|
||||
'*, *::before, *::after': {
|
||||
@ -108,6 +115,7 @@ export const lightThemeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
MuiDialog: {
|
||||
styleOverrides: {
|
||||
paper: {
|
||||
@ -115,6 +123,7 @@ export const lightThemeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
MuiPopover: {
|
||||
styleOverrides: {
|
||||
paper: {
|
||||
|
35
src/transactions/TransferAssetTransaction.ts
Normal file
35
src/transactions/TransferAssetTransaction.ts
Normal file
@ -0,0 +1,35 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { QORT_DECIMALS } from '../constants/constants'
|
||||
import TransactionBase from './TransactionBase'
|
||||
|
||||
export default class TransferAssetTransaction extends TransactionBase {
|
||||
constructor() {
|
||||
super()
|
||||
this.type = 12
|
||||
}
|
||||
|
||||
set recipient(recipient) {
|
||||
this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient)
|
||||
}
|
||||
|
||||
set amount(amount) {
|
||||
this._amount = Math.round(amount * QORT_DECIMALS)
|
||||
this._amountBytes = this.constructor.utils.int64ToBytes(this._amount)
|
||||
}
|
||||
|
||||
set assetId(assetId) {
|
||||
this._assetId = this.constructor.utils.int64ToBytes(assetId)
|
||||
}
|
||||
|
||||
get params() {
|
||||
const params = super.params
|
||||
params.push(
|
||||
this._recipient,
|
||||
this._assetId,
|
||||
this._amountBytes,
|
||||
this._feeBytes
|
||||
)
|
||||
return params
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import UpdateGroupTransaction from './UpdateGroupTransaction.js'
|
||||
import SellNameTransacion from './SellNameTransacion.js'
|
||||
import CancelSellNameTransacion from './CancelSellNameTransacion.js'
|
||||
import BuyNameTransacion from './BuyNameTransacion.js'
|
||||
import TransferAssetTransaction from './TransferAssetTransaction.js'
|
||||
|
||||
|
||||
export const transactionTypes = {
|
||||
@ -35,6 +36,7 @@ export const transactionTypes = {
|
||||
7: BuyNameTransacion,
|
||||
8: CreatePollTransaction,
|
||||
9: VoteOnPollTransaction,
|
||||
12: TransferAssetTransaction,
|
||||
16: DeployAtTransaction,
|
||||
18: ChatTransaction,
|
||||
181: GroupChatTransaction,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user