Merge pull request #25 from nbenaglia/feature/i18n-groups

I18N: add group namespace
This commit is contained in:
nico.benaz 2025-04-26 17:14:20 +02:00 committed by GitHub
commit 4fa9aa3c91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 995 additions and 507 deletions

13
i18n.js
View File

@ -19,6 +19,15 @@ const capitalize = {
}, },
}; };
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 i18n
.use(HttpApi) .use(HttpApi)
.use(LanguageDetector) .use(LanguageDetector)
@ -42,8 +51,8 @@ i18n
escapeValue: false, escapeValue: false,
}, },
lng: navigator.language, lng: navigator.language,
ns: ['auth', 'core', 'tutorial'], ns: ['auth', 'core', 'group', 'tutorial'],
supportedLngs: ['en', 'it', 'es', 'fr', 'de', 'ru'], supportedLngs: Object.keys(supportedLanguages),
}); });
export default i18n; export default i18n;

View File

@ -29,6 +29,11 @@
"password": "password", "password": "password",
"password_confirmation": "confirm password", "password_confirmation": "confirm password",
"return_to_list": "return to list", "return_to_list": "return to list",
"tips": {
"digital_id": "your wallet is like your digital ID on Qortal, and is how you will login to the Qortal User Interface. It holds your public address and the Qortal name you will eventually choose. Every transaction you make is linked to your ID, and this is where you manage all your QORT and other tradeable cryptocurrencies on Qortal.",
"new_account": "creating an account means creating a new wallet and digital ID to start using Qortal. Once you have made your account, you can start doing things like obtaining some QORT, buying a name and avatar, publishing videos and blogs, and much more.",
"new_users": "new users start here!"
},
"wallet": { "wallet": {
"password_confirmation": "confirm wallet password", "password_confirmation": "confirm wallet password",
"password": "wallet password", "password": "wallet password",

View File

@ -1,27 +1,57 @@
{ {
"add": "add", "action": {
"cancel": "cancel", "add": "add",
"choose": "choose", "accept": "accept",
"close": "close", "backup_account": "backup account",
"continue": "continue", "backup_wallet": "backup wallet",
"cancel": "cancel",
"change": "change",
"change_language": "change language",
"choose": "choose",
"close": "close",
"continue": "continue",
"continue_logout": "continue to logout",
"decline": "decline",
"edit": "edit",
"export": "export",
"import": "import",
"invite": "invite",
"join": "join",
"logout": "logout",
"notify": "notify"
},
"core": { "core": {
"block_height": "block height", "block_height": "block height",
"information": "core information", "information": "core information",
"peers": "connected peers", "peers": "connected peers",
"version": "core version" "version": "core version"
}, },
"count": {
"none": "none",
"one": "one"
},
"description": "description", "description": "description",
"edit": "edit", "fee": {
"export": "export", "payment": "payment fee",
"import": "import", "publish": "publish fee"
},
"page": {
"last": "last",
"first": "first",
"next": "next",
"previous": "previous"
},
"downloading_qdn": "downloading from QDN",
"last_height": "last height", "last_height": "last height",
"loading": "loading...", "loading": "loading...",
"logout": "logout", "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", "minting_status": "minting status",
"new_user": "are you a new user?",
"payment_notification": "payment notification", "payment_notification": "payment notification",
"price": "price", "price": "price",
"q_mail": "q-mail", "q_mail": "q-mail",
"result": { "message": {
"error": { "error": {
"generic": "an error occurred", "generic": "an error occurred",
"incorrect_password": "incorrect password", "incorrect_password": "incorrect password",
@ -34,7 +64,10 @@
"synchronizing": "synchronizing" "synchronizing": "synchronizing"
}, },
"success": { "success": {
"publish_qdn": "successfully published to QDN" "order_submitted": "your buy order was submitted",
"publish_qdn": "successfully published to QDN",
"request_read": "I have read this request",
"transfer": "the transfer was succesful!"
} }
}, },
"save_options": { "save_options": {
@ -59,11 +92,18 @@
"dark": "dark mode", "dark": "dark mode",
"light": "light mode" "light": "light mode"
}, },
"time": {
"day_one": "{{count}} day",
"day_other": "{{count}} days",
"hour_one": "{{count}} hour",
"hour_other": "{{count}} hours",
"minute_one": "{{count}} minute",
"minute_other": "{{count}} minutes"
},
"title": "title", "title": "title",
"tutorial": "tutorial", "tutorial": "tutorial",
"user_lookup": "user lookup", "user_lookup": "user lookup",
"wallet": { "wallet": {
"backup_wallet": "backup wallet",
"wallet": "wallet", "wallet": "wallet",
"wallet_other": "wallets" "wallet_other": "wallets"
}, },

View File

@ -0,0 +1,65 @@
{
"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"
}
}
}

View File

@ -6,38 +6,43 @@
}, },
"advanced_users": "per utenti avanzati", "advanced_users": "per utenti avanzati",
"apikey": { "apikey": {
"alternative": "alternativa: seleziona un file", "alternative": "alternativa: selezione file",
"change": "cambia la chiave API", "change": "cambia APIkey",
"enter": "inserisci la chiave API", "enter": "inserisci APIkey",
"import": "importa chiave API", "import": "importa APIkey",
"key": "chiave API", "key": "chiave API",
"select_valid": "selezione una chiave API valida" "select_valid": "seleziona una APIkey valida"
}, },
"authenticate": "autenticazione", "authenticate": "autentica",
"build_version": "versione build", "build_version": "versione build",
"create_account": "crea un account", "create_account": "crea account",
"download_account": "scarica account", "download_account": "scarica account",
"keep_secure": "metti al sicuro il file del tuo account", "keep_secure": "mantieni sicuro il file del tuo account",
"node": { "node": {
"choose": "scegli un nodo custom", "choose": "scegli nodo personalizzato",
"custom_many": "nodi custom", "custom_many": "nodi personalizzati",
"use_custom": "use nodo custom", "use_custom": "usa nodo personalizzato",
"use_local": "usa nodo locale", "use_local": "usa nodo locale",
"using": "nodo in uso", "using": "utilizzo nodo",
"using_public": "utilizzo nodo pubblico" "using_public": "utilizzo nodo pubblico"
}, },
"password_confirmation": "confirma la password",
"password": "password", "password": "password",
"password_confirmation": "conferma password",
"return_to_list": "torna alla lista",
"tips": {
"digital_id": "il tuo wallet è come la tua identità digitale su Qortal ed è il modo in cui accederai all'interfaccia utente di Qortal. Contiene il tuo indirizzo pubblico e il nome Qortal che sceglierai. Ogni transazione che esegui è collegata alla tua identità ed è qui che gestisci tutti i tuoi QORT e altre criptovalute scambiabili su Qortal.",
"new_account": "creare un account significa creare un nuovo wallet e un'identità digitale per iniziare a usare Qortal. Una volta creato l'account, potrai iniziare a ottenere QORT, acquistare un nome e un avatar, pubblicare video e blog, e molto altro.",
"new_users": "i nuovi utenti iniziano qui!"
},
"wallet": { "wallet": {
"password_confirmation": "conferma la password del wallet", "password_confirmation": "conferma password del wallet",
"password": "password del wallet", "password": "password del wallet",
"keep_password": "mantieni la password attuale", "keep_password": "mantieni password corrente",
"new_password": "nuova password", "new_password": "nuova password",
"error": { "error": {
"missing_new_password": "per favore inserisci una nuova password", "missing_new_password": "per favore inserisci una nuova password",
"missing_password": "per favore inserisci la tua password" "missing_password": "per favore inserisci la tua password"
} }
}, },
"return_to_list": "ritorna alla lista",
"welcome": "benvenuto in" "welcome": "benvenuto in"
} }

View File

@ -1,71 +1,111 @@
{ {
"add": "aggiungi", "action": {
"cancel": "annulla", "add": "aggiungi",
"choose": "scegli", "accept": "accetta",
"close": "chiudi", "backup_account": "backup account",
"continue": "continua", "backup_wallet": "backup wallet",
"cancel": "annulla",
"change": "cambia",
"change_language": "cambia lingua",
"choose": "scegli",
"close": "chiudi",
"continue": "continua",
"continue_logout": "continua con il logout",
"decline": "rifiuta",
"edit": "modifica",
"export": "esporta",
"import": "importa",
"invite": "invita",
"join": "unisciti",
"logout": "esci",
"notify": "notifica"
},
"core": { "core": {
"block_height": "altezza del blocco", "block_height": "altezza blocco",
"information": "informazioni core", "information": "informazioni core",
"peers": "peer connessi", "peers": "peer connessi",
"version": "versione core" "version": "versione core"
}, },
"count": {
"none": "nessuno",
"one": "uno"
},
"description": "descrizione", "description": "descrizione",
"edit": "modifica", "fee": {
"export": "esporta", "payment": "commissione di pagamento",
"import": "importa", "publish": "commissione di pubblicazione"
},
"page": {
"last": "ultimo",
"first": "primo",
"next": "successivo",
"previous": "precedente"
},
"downloading_qdn": "scaricamento da QDN",
"last_height": "ultima altezza", "last_height": "ultima altezza",
"loading": "caricamento...", "loading": "caricamento...",
"logout": "disconnetti", "loading_posts": "caricamento post... attendere prego.",
"minting_status": "stato del conio", "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", "payment_notification": "notifica di pagamento",
"price": "prezzo", "price": "prezzo",
"q_mail": "q-mail", "q_mail": "q-mail",
"result": { "message": {
"error": { "error": {
"generic": "si è verificato un errore", "generic": "si è verificato un errore",
"incorrect_password": "password errata", "incorrect_password": "password errata",
"save_qdn": "impossibile salvare su QDN" "save_qdn": "impossibile salvare su QDN"
}, },
"status": { "status": {
"minting": "(conio in corso)", "minting": "(minting)",
"not_minting": "(conio non attivo)", "not_minting": "(non minting)",
"synchronized": "sincronizzato", "synchronized": "sincronizzato",
"synchronizing": "sincronizzazione in corso" "synchronizing": "sincronizzazione in corso"
}, },
"success": { "success": {
"publish_qdn": "pubblicato con successo su QDN" "order_submitted": "il tuo ordine di acquisto è stato inviato",
"publish_qdn": "pubblicato su QDN con successo",
"request_read": "ho letto questa richiesta",
"transfer": "il trasferimento è stato effettuato con successo!"
} }
}, },
"save_options": { "save_options": {
"no_pinned_changes": "attualmente non hai modifiche alle tue app appuntate", "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 queste modifiche?", "overwrite_changes": "l'app non è riuscita a scaricare le tue app appuntate salvate su QDN. Vuoi sovrascrivere le modifiche?",
"overwrite_qdn": "sovrascrivi su QDN", "overwrite_qdn": "sovrascrivi su QDN",
"publish_qdn": "vuoi pubblicare le tue impostazioni su QDN (crittografate)?", "publish_qdn": "vuoi pubblicare le tue impostazioni su QDN (crittografato)?",
"qdn": "usa il salvataggio QDN", "qdn": "usa il salvataggio su QDN",
"register_name": "hai bisogno di un nome Qortal registrato per salvare le tue app appuntate 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_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 tue app appuntate salvate su QDN?", "reset_qdn": "non ti piacciono le modifiche locali attuali? Vuoi ripristinare le app appuntate salvate su QDN?",
"revert_default": "ripristina predefinite", "revert_default": "ripristina predefinito",
"revert_qdn": "ripristina da QDN", "revert_qdn": "ripristina da QDN",
"save_qdn": "salva su QDN", "save_qdn": "salva su QDN",
"save": "salva", "save": "salva",
"settings": "stai utilizzando il metodo esporta/importa per salvare le impostazioni.", "settings": "stai usando il metodo di esportazione/importazione per salvare le impostazioni.",
"unsaved_changes": "hai modifiche non salvate alle tue app appuntate. Salvale su QDN." "unsaved_changes": "hai modifiche non salvate alle tue app appuntate. Salvale su QDN."
}, },
"settings": "impostazioni", "settings": "impostazioni",
"supply": "offerta", "supply": "disponibilità",
"theme": { "theme": {
"dark": "modalità scura", "dark": "modalità scura",
"light": "modalità chiara" "light": "modalità chiara"
}, },
"time": {
"day_one": "{{count}} giorno",
"day_other": "{{count}} giorni",
"hour_one": "{{count}} ora",
"hour_other": "{{count}} ore",
"minute_one": "{{count}} minuto",
"minute_other": "{{count}} minuti"
},
"title": "titolo", "title": "titolo",
"tutorial": "tutorial", "tutorial": "tutorial",
"user_lookup": "ricerca utente", "user_lookup": "ricerca utente",
"wallet": { "wallet": {
"backup_wallet": "backup portafoglio", "wallet": "wallet",
"wallet": "portafoglio", "wallet_other": "wallet"
"wallet_other": "portafogli"
}, },
"welcome": "benvenuto" "welcome": "benvenuto"
} }

View File

@ -0,0 +1,65 @@
{
"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"
}
}
}

View File

@ -135,6 +135,7 @@ import { GeneralNotifications } from './components/GeneralNotifications';
import { PdfViewer } from './common/PdfViewer'; import { PdfViewer } from './common/PdfViewer';
import ThemeSelector from './components/Theme/ThemeSelector.tsx'; import ThemeSelector from './components/Theme/ThemeSelector.tsx';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import LanguageSelector from './components/Language/LanguageSelector.tsx';
import { DownloadWallet } from './components/Auth/DownloadWallet.tsx'; import { DownloadWallet } from './components/Auth/DownloadWallet.tsx';
type extStates = type extStates =
@ -255,14 +256,7 @@ export const getBaseApiReact = (customApi?: string) => {
return groupApi; return groupApi;
} }
}; };
// export const getArbitraryEndpointReact = () => {
// if (globalApiKey) {
// return `/arbitrary/resources/search`;
// } else {
// return `/arbitrary/resources/searchsimple`;
// }
// };
export const getArbitraryEndpointReact = () => { export const getArbitraryEndpointReact = () => {
if (globalApiKey) { if (globalApiKey) {
return `/arbitrary/resources/searchsimple`; return `/arbitrary/resources/searchsimple`;
@ -571,26 +565,6 @@ function App() {
isFocusedRef.current = isFocused; isFocusedRef.current = isFocused;
}, [isFocused]); }, [isFocused]);
// const checkIfUserHasLocalNode = useCallback(async () => {
// try {
// const url = `http://127.0.0.1:12391/admin/status`;
// const response = await fetch(url, {
// method: "GET",
// headers: {
// "Content-Type": "application/json",
// },
// });
// const data = await response.json();
// if (data?.isSynchronizing === false && data?.syncPercent === 100) {
// setHasLocalNode(true);
// }
// } catch (error) {}
// }, []);
// useEffect(() => {
// checkIfUserHasLocalNode();
// }, [extState]);
const address = useMemo(() => { const address = useMemo(() => {
if (!rawWallet?.address0) return ''; if (!rawWallet?.address0) return '';
return rawWallet.address0; return rawWallet.address0;
@ -1007,7 +981,7 @@ function App() {
await showUnsavedChanges({ await showUnsavedChanges({
message: 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.', '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') { } else if (extState === 'authenticated') {
await showUnsavedChanges({ await showUnsavedChanges({
message: 'Are you sure you would like to logout?', message: 'Are you sure you would like to logout?',
@ -1311,19 +1285,24 @@ function App() {
</Tooltip> </Tooltip>
)} )}
</Box> </Box>
<Spacer height="48px" /> <Spacer height="48px" />
{authenticatedMode === 'ltc' ? ( {authenticatedMode === 'ltc' ? (
<> <>
<img src={ltcLogo} /> <img src={ltcLogo} />
<Spacer height="32px" /> <Spacer height="32px" />
<CopyToClipboard text={rawWallet?.ltcAddress}> <CopyToClipboard text={rawWallet?.ltcAddress}>
<AddressBox> <AddressBox>
{rawWallet?.ltcAddress?.slice(0, 6)}... {rawWallet?.ltcAddress?.slice(0, 6)}...
{rawWallet?.ltcAddress?.slice(-4)} <img src={Copy} /> {rawWallet?.ltcAddress?.slice(-4)} <img src={Copy} />
</AddressBox> </AddressBox>
</CopyToClipboard> </CopyToClipboard>
<Spacer height="10px" /> <Spacer height="10px" />
{ltcBalanceLoading && ( {ltcBalanceLoading && (
<CircularProgress color="success" size={16} /> <CircularProgress color="success" size={16} />
)} )}
@ -1345,6 +1324,7 @@ function App() {
> >
{ltcBalance} LTC {ltcBalance} LTC
</TextP> </TextP>
<RefreshIcon <RefreshIcon
onClick={getLtcBalanceFunc} onClick={getLtcBalanceFunc}
sx={{ sx={{
@ -1364,7 +1344,9 @@ function App() {
myName={userInfo?.name} myName={userInfo?.name}
balance={balance} balance={balance}
/> />
<Spacer height="32px" /> <Spacer height="32px" />
<TextP <TextP
sx={{ sx={{
textAlign: 'center', textAlign: 'center',
@ -1374,7 +1356,9 @@ function App() {
> >
{userInfo?.name} {userInfo?.name}
</TextP> </TextP>
<Spacer height="10px" /> <Spacer height="10px" />
<CopyToClipboard text={rawWallet?.address0}> <CopyToClipboard text={rawWallet?.address0}>
<AddressBox> <AddressBox>
{rawWallet?.address0?.slice(0, 6)}... {rawWallet?.address0?.slice(0, 6)}...
@ -1514,7 +1498,7 @@ function App() {
textTransform: 'uppercase', textTransform: 'uppercase',
}} }}
> >
{t('core:logout')} {t('core:action.logout')}
</span> </span>
} }
placement="left" placement="left"
@ -1869,7 +1853,7 @@ function App() {
textTransform: 'uppercase', textTransform: 'uppercase',
}} }}
> >
{t('core:backup_wallet')} {t('core:action.backup_wallet')}
</span> </span>
} }
placement="left" placement="left"
@ -1903,10 +1887,6 @@ function App() {
<AppContainer <AppContainer
sx={{ sx={{
height: '100vh', height: '100vh',
// backgroundImage: desktopViewMode === "apps" && 'url("appsBg.svg")',
// backgroundSize: desktopViewMode === "apps" && "cover",
// backgroundPosition: desktopViewMode === "apps" && "center",
// backgroundRepeat: desktopViewMode === "apps" && "no-repeat",
}} }}
> >
<PdfViewer /> <PdfViewer />
@ -2036,7 +2016,6 @@ function App() {
/> />
</Box> </Box>
)} )}
{isShowQortalRequest && !isMainWindow && ( {isShowQortalRequest && !isMainWindow && (
<> <>
<Spacer height="120px" /> <Spacer height="120px" />
@ -2319,7 +2298,6 @@ function App() {
<ErrorText>{sendPaymentError}</ErrorText> <ErrorText>{sendPaymentError}</ErrorText>
</> </>
)} )}
{extState === 'web-app-request-payment' && !isMainWindow && ( {extState === 'web-app-request-payment' && !isMainWindow && (
<> <>
<Spacer height="100px" /> <Spacer height="100px" />
@ -2953,7 +2931,9 @@ function App() {
}); });
}} }}
> >
Backup Account {t('core:action.backup_account', {
postProcess: 'capitalize',
})}
</CustomButton> </CustomButton>
</> </>
)} )}
@ -2981,7 +2961,9 @@ function App() {
lineHeight: '15px', lineHeight: '15px',
}} }}
> >
The transfer was succesful! {t('core:message.success.transfer', {
postProcess: 'capitalize',
})}
</TextP> </TextP>
<Spacer height="100px" /> <Spacer height="100px" />
<CustomButton <CustomButton
@ -2989,7 +2971,7 @@ function App() {
returnToMain(); returnToMain();
}} }}
> >
Continue {t('core:action.continue', { postProcess: 'capitalize' })}
</CustomButton> </CustomButton>
</Box> </Box>
)} )}
@ -3004,7 +2986,9 @@ function App() {
lineHeight: '15px', lineHeight: '15px',
}} }}
> >
The transfer was succesful! {t('core:message.success.transfer', {
postProcess: 'capitalize',
})}
</TextP> </TextP>
<Spacer height="100px" /> <Spacer height="100px" />
<CustomButton <CustomButton
@ -3012,7 +2996,7 @@ function App() {
window.close(); window.close();
}} }}
> >
Continue {t('core:action.continue', { postProcess: 'capitalize' })}
</CustomButton> </CustomButton>
</> </>
)} )}
@ -3027,7 +3011,9 @@ function App() {
lineHeight: '15px', lineHeight: '15px',
}} }}
> >
Your buy order was submitted {t('core:message.success.order_submitted', {
postProcess: 'capitalize',
})}
</TextP> </TextP>
<Spacer height="100px" /> <Spacer height="100px" />
<CustomButton <CustomButton
@ -3035,10 +3021,11 @@ function App() {
window.close(); window.close();
}} }}
> >
Close {t('core:action.close', { postProcess: 'capitalize' })}
</CustomButton> </CustomButton>
</> </>
)} )}
{countdown && ( {countdown && (
<Box <Box
style={{ style={{
@ -3082,12 +3069,18 @@ function App() {
</DialogContentText> </DialogContentText>
{message?.paymentFee && ( {message?.paymentFee && (
<DialogContentText id="alert-dialog-description2"> <DialogContentText id="alert-dialog-description2">
payment fee: {message.paymentFee} {t('core:fee.payment', {
postProcess: 'capitalize',
})}
: {message.paymentFee}
</DialogContentText> </DialogContentText>
)} )}
{message?.publishFee && ( {message?.publishFee && (
<DialogContentText id="alert-dialog-description2"> <DialogContentText id="alert-dialog-description2">
publish fee: {message.publishFee} {t('core:fee.publish', {
postProcess: 'capitalize',
})}
: {message.publishFee}
</DialogContentText> </DialogContentText>
)} )}
</DialogContent> </DialogContent>
@ -3108,7 +3101,9 @@ function App() {
onClick={onOk} onClick={onOk}
autoFocus autoFocus
> >
accept {t('core:action.accept', {
postProcess: 'capitalize',
})}
</Button> </Button>
<Button <Button
sx={{ sx={{
@ -3125,7 +3120,9 @@ function App() {
variant="contained" variant="contained"
onClick={onCancel} onClick={onCancel}
> >
decline {t('core:action.decline', {
postProcess: 'capitalize',
})}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@ -3146,7 +3143,9 @@ function App() {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button variant="contained" onClick={onOkInfo} autoFocus> <Button variant="contained" onClick={onOkInfo} autoFocus>
Close {t('core:action.close', {
postProcess: 'capitalize',
})}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@ -3165,14 +3164,18 @@ function App() {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button variant="contained" onClick={onCancelUnsavedChanges}> <Button variant="contained" onClick={onCancelUnsavedChanges}>
Cancel {t('core:action.cancel', {
postProcess: 'capitalize',
})}
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
onClick={onOkUnsavedChanges} onClick={onOkUnsavedChanges}
autoFocus autoFocus
> >
Continue to Logout {t('core:action.decline', {
postProcess: 'capitalize',
})}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@ -3443,7 +3446,9 @@ function App() {
label={ label={
<Box sx={{ display: 'flex', alignItems: 'center' }}> <Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography sx={{ fontSize: '14px' }}> <Typography sx={{ fontSize: '14px' }}>
I have read this request {t('core:message.success.request_read', {
postProcess: 'capitalize',
})}
</Typography> </Typography>
<PriorityHighIcon color="warning" /> <PriorityHighIcon color="warning" />
</Box> </Box>
@ -3454,8 +3459,8 @@ function App() {
<Spacer height="29px" /> <Spacer height="29px" />
<Box <Box
sx={{ sx={{
display: 'flex',
alignItems: 'center', alignItems: 'center',
display: 'flex',
gap: '14px', gap: '14px',
}} }}
> >
@ -3491,7 +3496,9 @@ function App() {
onOkQortalRequestExtension('accepted'); onOkQortalRequestExtension('accepted');
}} }}
> >
accept {t('core:action.accept', {
postProcess: 'capitalize',
})}
</CustomButtonAccept> </CustomButtonAccept>
<CustomButtonAccept <CustomButtonAccept
color="black" color="black"
@ -3501,7 +3508,9 @@ function App() {
}} }}
onClick={() => onCancelQortalRequestExtension()} onClick={() => onCancelQortalRequestExtension()}
> >
decline {t('core:action.decline', {
postProcess: 'capitalize',
})}
</CustomButtonAccept> </CustomButtonAccept>
</Box> </Box>
<ErrorText>{sendPaymentError}</ErrorText> <ErrorText>{sendPaymentError}</ErrorText>
@ -3566,6 +3575,7 @@ function App() {
/> />
)} )}
<LanguageSelector />
<ThemeSelector /> <ThemeSelector />
</AppContainer> </AppContainer>
); );

View File

@ -31,6 +31,7 @@ import { GlobalContext } from '../App';
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
import ThemeSelector from '../components/Theme/ThemeSelector'; import ThemeSelector from '../components/Theme/ThemeSelector';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import LanguageSelector from '../components/Language/LanguageSelector';
const manifestData = { const manifestData = {
version: '0.5.3', version: '0.5.3',
@ -510,14 +511,8 @@ export const NotAuthenticated = ({
fontSize: '16px', fontSize: '16px',
}} }}
> >
Your wallet is like your digital ID on Qortal, and is how you {t('auth:tips.digital_id', { postProcess: 'capitalize' })}
will login to the Qortal User Interface. It holds your public </Typography>
address and the Qortal name you will eventually choose. Every
transaction you make is linked to your ID, and this is where you
manage all your QORT and other tradeable cryptocurrencies on
Qortal.
</Typography>{' '}
// TODO translate
</React.Fragment> </React.Fragment>
} }
> >
@ -547,9 +542,8 @@ export const NotAuthenticated = ({
fontSize: '18px', fontSize: '18px',
}} }}
> >
New users start here! {t('auth:tips.new_users', { postProcess: 'capitalize' })}
</Typography>{' '} </Typography>
// TODO translate
<Spacer height="10px" /> <Spacer height="10px" />
<Typography <Typography
color="inherit" color="inherit"
@ -557,12 +551,8 @@ export const NotAuthenticated = ({
fontSize: '16px', fontSize: '16px',
}} }}
> >
Creating an account means creating a new wallet and digital ID {t('auth:tips.new_account', { postProcess: 'capitalize' })}
to start using Qortal. Once you have made your account, you can </Typography>
start doing things like obtaining some QORT, buying a name and
avatar, publishing videos and blogs, and much more.
</Typography>{' '}
// TODO translate
</React.Fragment> </React.Fragment>
} }
> >
@ -816,7 +806,7 @@ export const NotAuthenticated = ({
}} }}
variant="contained" variant="contained"
> >
{t('core:choose', { postProcess: 'capitalize' })} {t('core:action.choose', { postProcess: 'capitalize' })}
</Button> </Button>
</Box> </Box>
</Box> </Box>
@ -875,7 +865,9 @@ export const NotAuthenticated = ({
}} }}
variant="contained" variant="contained"
> >
{t('core:choose', { postProcess: 'capitalize' })} {t('core:action.choose', {
postProcess: 'capitalize',
})}
</Button> </Button>
<Button <Button
@ -888,7 +880,9 @@ export const NotAuthenticated = ({
}} }}
variant="contained" variant="contained"
> >
{t('core:edit', { postProcess: 'capitalize' })} {t('core:action.edit', {
postProcess: 'capitalize',
})}
</Button> </Button>
<Button <Button
@ -940,7 +934,7 @@ export const NotAuthenticated = ({
<DialogActions> <DialogActions>
{mode === 'list' && ( {mode === 'list' && (
<Button variant="contained" onClick={addCustomNode}> <Button variant="contained" onClick={addCustomNode}>
{t('core:add', { postProcess: 'capitalize' })} {t('core:action.add', { postProcess: 'capitalize' })}
</Button> </Button>
)} )}
@ -953,7 +947,7 @@ export const NotAuthenticated = ({
}} }}
autoFocus autoFocus
> >
{t('core:close', { postProcess: 'capitalize' })} {t('core:action.close', { postProcess: 'capitalize' })}
</Button> </Button>
</> </>
)} )}
@ -1075,7 +1069,7 @@ export const NotAuthenticated = ({
setShowSelectApiKey(false); setShowSelectApiKey(false);
}} }}
> >
{t('core:close', { postProcess: 'capitalize' })} {t('core:action.close', { postProcess: 'capitalize' })}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@ -1097,6 +1091,7 @@ export const NotAuthenticated = ({
/> />
</ButtonBase> </ButtonBase>
<LanguageSelector />
<ThemeSelector /> <ThemeSelector />
</> </>
); );

View File

@ -15,6 +15,7 @@ import { extractComponents } from '../Chat/MessageDisplay';
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward'; import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
import { AppsPrivate } from './AppsPrivate'; import { AppsPrivate } from './AppsPrivate';
import ThemeSelector from '../Theme/ThemeSelector'; import ThemeSelector from '../Theme/ThemeSelector';
import LanguageSelector from '../Language/LanguageSelector';
export const AppsHomeDesktop = ({ export const AppsHomeDesktop = ({
setMode, setMode,
@ -157,6 +158,7 @@ export const AppsHomeDesktop = ({
/> />
</AppsContainer> </AppsContainer>
<LanguageSelector />
<ThemeSelector /> <ThemeSelector />
</> </>
); );

View File

@ -79,21 +79,21 @@ export const CoreSyncStatus = () => {
if (isMintingPossible && !isUsingGateway) { if (isMintingPossible && !isUsingGateway) {
imagePath = syncedMintingImg; imagePath = syncedMintingImg;
message = `${t(`core:result.status.${isSynchronizing ? 'synchronizing' : 'synchronized'}`, { postProcess: 'capitalize' })} ${t('core:result.status.minting')}`; message = `${t(`core:message.status.${isSynchronizing ? 'synchronizing' : 'synchronized'}`, { postProcess: 'capitalize' })} ${t('core:message.status.minting')}`;
} else if (isSynchronizing === true && syncPercent === 99) { } else if (isSynchronizing === true && syncPercent === 99) {
imagePath = syncingImg; imagePath = syncingImg;
} else if (isSynchronizing && !isMintingPossible && syncPercent === 100) { } else if (isSynchronizing && !isMintingPossible && syncPercent === 100) {
imagePath = syncingImg; imagePath = syncingImg;
message = `${t('core:result.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.not_minting') : ''}`; message = `${t('core:message.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.not_minting') : ''}`;
} else if (!isSynchronizing && !isMintingPossible && syncPercent === 100) { } else if (!isSynchronizing && !isMintingPossible && syncPercent === 100) {
imagePath = syncedImg; imagePath = syncedImg;
message = `${t('core:result.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.not_minting') : ''}`; message = `${t('core:message.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.not_minting') : ''}`;
} else if (isSynchronizing && isMintingPossible && syncPercent === 100) { } else if (isSynchronizing && isMintingPossible && syncPercent === 100) {
imagePath = syncingImg; imagePath = syncingImg;
message = `${t('core:result.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.minting') : ''}`; message = `${t('core:message.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.minting') : ''}`;
} else if (!isSynchronizing && isMintingPossible && syncPercent === 100) { } else if (!isSynchronizing && isMintingPossible && syncPercent === 100) {
imagePath = syncedMintingImg; imagePath = syncedMintingImg;
message = `${t('core:result.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.minting') : ''}`; message = `${t('core:message.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.minting') : ''}`;
} }
return ( return (

View File

@ -8,6 +8,7 @@ import { enabledDevModeAtom } from '../atoms/global';
import { AppsIcon } from '../assets/Icons/AppsIcon'; import { AppsIcon } from '../assets/Icons/AppsIcon';
import ThemeSelector from './Theme/ThemeSelector'; import ThemeSelector from './Theme/ThemeSelector';
import { CoreSyncStatus } from './CoreSyncStatus'; import { CoreSyncStatus } from './CoreSyncStatus';
import LanguageSelector from './Language/LanguageSelector';
export const DesktopSideBar = ({ export const DesktopSideBar = ({
goToHome, goToHome,
@ -143,6 +144,7 @@ export const DesktopSideBar = ({
</ButtonBase> </ButtonBase>
)} )}
<LanguageSelector />
<ThemeSelector /> <ThemeSelector />
</Box> </Box>
); );

View File

@ -1,4 +1,13 @@
import * as React from 'react'; import {
forwardRef,
Fragment,
ReactElement,
Ref,
SyntheticEvent,
useContext,
useEffect,
useState,
} from 'react';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';
import AppBar from '@mui/material/AppBar'; import AppBar from '@mui/material/AppBar';
@ -28,6 +37,7 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { MyContext } from '../../App'; import { MyContext } from '../../App';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
import { useTranslation } from 'react-i18next';
export const Label = styled('label')` export const Label = styled('label')`
display: block; display: block;
@ -37,30 +47,29 @@ export const Label = styled('label')`
margin-bottom: 4px; margin-bottom: 4px;
`; `;
const Transition = React.forwardRef(function Transition( const Transition = forwardRef(function Transition(
props: TransitionProps & { props: TransitionProps & {
children: React.ReactElement; children: ReactElement;
}, },
ref: React.Ref<unknown> ref: Ref<unknown>
) { ) {
return <Slide direction="up" ref={ref} {...props} />; return <Slide direction="up" ref={ref} {...props} />;
}); });
export const AddGroup = ({ address, open, setOpen }) => { export const AddGroup = ({ address, open, setOpen }) => {
const { show, setTxList } = React.useContext(MyContext); const { show, setTxList } = useContext(MyContext);
const [tab, setTab] = React.useState('create'); const [openAdvance, setOpenAdvance] = useState(false);
const [openAdvance, setOpenAdvance] = React.useState(false); const [name, setName] = useState('');
const [name, setName] = React.useState(''); const [description, setDescription] = useState('');
const [description, setDescription] = React.useState(''); const [groupType, setGroupType] = useState('1');
const [groupType, setGroupType] = React.useState('1'); const [approvalThreshold, setApprovalThreshold] = useState('40');
const [approvalThreshold, setApprovalThreshold] = React.useState('40'); const [minBlock, setMinBlock] = useState('5');
const [minBlock, setMinBlock] = React.useState('5'); const [maxBlock, setMaxBlock] = useState('21600');
const [maxBlock, setMaxBlock] = React.useState('21600'); const [value, setValue] = useState(0);
const [value, setValue] = React.useState(0); const [openSnack, setOpenSnack] = useState(false);
const [openSnack, setOpenSnack] = React.useState(false); const [infoSnack, setInfoSnack] = useState(null);
const [infoSnack, setInfoSnack] = React.useState(null);
const handleChange = (event: React.SyntheticEvent, newValue: number) => { const handleChange = (event: SyntheticEvent, newValue: number) => {
setValue(newValue); setValue(newValue);
}; };
@ -84,16 +93,30 @@ export const AddGroup = ({ address, open, setOpen }) => {
setMaxBlock(event.target.value as string); setMaxBlock(event.target.value as string);
}; };
const { t } = useTranslation(['core', 'group']);
const theme = useTheme(); const theme = useTheme();
const handleCreateGroup = async () => { const handleCreateGroup = async () => {
try { try {
if (!name) throw new Error('Please provide a name'); if (!name)
if (!description) throw new Error('Please provide a description'); throw new Error(
t('group:message.error.name_required', {
postProcess: 'capitalize',
})
);
if (!description)
throw new Error(
t('group:message.error.description_required', {
postProcess: 'capitalize',
})
);
const fee = await getFee('CREATE_GROUP');
const fee = await getFee('CREATE_GROUP'); // TODO translate
await show({ await show({
message: 'Would you like to perform an CREATE_GROUP transaction?', message: t('group:question.create_group', {
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
@ -111,16 +134,23 @@ export const AddGroup = ({ address, open, setOpen }) => {
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.group_creation', {
'Successfully created group. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
setTxList((prev) => [ setTxList((prev) => [
{ {
...response, ...response,
type: 'created-group', type: 'created-group',
label: `Created group ${name}: awaiting confirmation`, label: t('group:message.success.group_creation_name', {
labelDone: `Created group ${name}: success!`, group_name: name,
postProcess: 'capitalize',
}),
labelDone: t('group:message.success.group_creation_label', {
group_name: name,
postProcess: 'capitalize',
}),
done: false, done: false,
}, },
...prev, ...prev,
@ -131,7 +161,11 @@ export const AddGroup = ({ address, open, setOpen }) => {
rej({ message: response.error }); rej({ message: response.error });
}) })
.catch((error) => { .catch((error) => {
rej({ message: error.message || 'An error occurred' }); rej({
message:
error.message ||
t('core:message.error.generic', { postProcess: 'capitalize' }),
});
}); });
}); });
} catch (error) { } catch (error) {
@ -143,22 +177,6 @@ export const AddGroup = ({ address, open, setOpen }) => {
} }
}; };
function CustomTabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && <Box sx={{ p: 3 }}>{children}</Box>}
</div>
);
}
function a11yProps(index: number) { function a11yProps(index: number) {
return { return {
id: `simple-tab-${index}`, id: `simple-tab-${index}`,
@ -170,7 +188,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
setValue(2); setValue(2);
}; };
React.useEffect(() => { useEffect(() => {
subscribeToEvent('openGroupInvitesRequest', openGroupInvitesRequestFunc); subscribeToEvent('openGroupInvitesRequest', openGroupInvitesRequestFunc);
return () => { return () => {
@ -182,7 +200,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
}, []); }, []);
return ( return (
<React.Fragment> <Fragment>
<Dialog <Dialog
fullScreen fullScreen
open={open} open={open}
@ -197,7 +215,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<Toolbar> <Toolbar>
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div"> <Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
Group Management {t('group:group.management', { postProcess: 'capitalize' })}
</Typography> </Typography>
<IconButton <IconButton
@ -208,12 +226,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
> >
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
{/* <Button autoFocus color="inherit" onClick={handleClose}>
save
</Button> */}
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Box <Box
sx={{ sx={{
bgcolor: theme.palette.background.default, bgcolor: theme.palette.background.default,
@ -241,7 +256,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
}} }}
> >
<Tab <Tab
label="Create Group" label={t('group:action.create_group', {
postProcess: 'capitalize',
})}
{...a11yProps(0)} {...a11yProps(0)}
sx={{ sx={{
'&.Mui-selected': { '&.Mui-selected': {
@ -251,7 +268,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
}} }}
/> />
<Tab <Tab
label="Find Group" label={t('group:action.find_group', {
postProcess: 'capitalize',
})}
{...a11yProps(1)} {...a11yProps(1)}
sx={{ sx={{
'&.Mui-selected': { '&.Mui-selected': {
@ -261,7 +280,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
}} }}
/> />
<Tab <Tab
label="Group Invites" label={t('group:group.invites', {
postProcess: 'capitalize',
})}
{...a11yProps(2)} {...a11yProps(2)}
sx={{ sx={{
'&.Mui-selected': { '&.Mui-selected': {
@ -295,9 +316,15 @@ export const AddGroup = ({ address, open, setOpen }) => {
gap: '5px', gap: '5px',
}} }}
> >
<Label>Name of group</Label> <Label>
{t('group:group.name', {
postProcess: 'capitalize',
})}
</Label>
<Input <Input
placeholder="Name of group" placeholder={t('group:group.name', {
postProcess: 'capitalize',
})}
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
/> />
@ -309,14 +336,21 @@ export const AddGroup = ({ address, open, setOpen }) => {
gap: '5px', gap: '5px',
}} }}
> >
<Label>Description of group</Label> <Label>
{t('group:group.description', {
postProcess: 'capitalize',
})}
</Label>
<Input <Input
placeholder="Description of group" placeholder={t('group:group.description', {
postProcess: 'capitalize',
})}
value={description} value={description}
onChange={(e) => setDescription(e.target.value)} onChange={(e) => setDescription(e.target.value)}
/> />
</Box> </Box>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -324,7 +358,13 @@ export const AddGroup = ({ address, open, setOpen }) => {
gap: '5px', gap: '5px',
}} }}
> >
<Label>Group type</Label> <Label>
{' '}
{t('group:group.type', {
postProcess: 'capitalize',
})}
</Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
id="demo-simple-select" id="demo-simple-select"
@ -332,12 +372,19 @@ export const AddGroup = ({ address, open, setOpen }) => {
label="Group Type" label="Group Type"
onChange={handleChangeGroupType} onChange={handleChangeGroupType}
> >
<MenuItem value={1}>Open (public)</MenuItem> <MenuItem value={1}>
{t('group:group.open', {
postProcess: 'capitalize',
})}
</MenuItem>
<MenuItem value={0}> <MenuItem value={0}>
Closed (private) - users need permission to join {t('group:group.closed', {
postProcess: 'capitalize',
})}
</MenuItem> </MenuItem>
</Select> </Select>
</Box> </Box>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -347,10 +394,15 @@ export const AddGroup = ({ address, open, setOpen }) => {
}} }}
onClick={() => setOpenAdvance((prev) => !prev)} onClick={() => setOpenAdvance((prev) => !prev)}
> >
<Typography>Advanced options</Typography> <Typography>
{t('group:advanced_options', {
postProcess: 'capitalize',
})}
</Typography>
{openAdvance ? <ExpandLess /> : <ExpandMore />} {openAdvance ? <ExpandLess /> : <ExpandMore />}
</Box> </Box>
<Collapse in={openAdvance} timeout="auto" unmountOnExit> <Collapse in={openAdvance} timeout="auto" unmountOnExit>
<Box <Box
sx={{ sx={{
@ -360,8 +412,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
}} }}
> >
<Label> <Label>
Group Approval Threshold (number / percentage of Admins {t('group:approval_threshold', {
that must approve a transaction) postProcess: 'capitalize',
})}
</Label> </Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
@ -370,14 +423,21 @@ export const AddGroup = ({ address, open, setOpen }) => {
label="Group Approval Threshold" label="Group Approval Threshold"
onChange={handleChangeApprovalThreshold} onChange={handleChangeApprovalThreshold}
> >
<MenuItem value={0}>NONE</MenuItem> <MenuItem value={0}>
<MenuItem value={1}>ONE </MenuItem> {t('core.count.none', {
postProcess: 'capitalize',
<MenuItem value={20}>20% </MenuItem> })}
<MenuItem value={40}>40% </MenuItem> </MenuItem>
<MenuItem value={60}>60% </MenuItem> <MenuItem value={1}>
<MenuItem value={80}>80% </MenuItem> {t('core.count.one', {
<MenuItem value={100}>100% </MenuItem> postProcess: 'capitalize',
})}
</MenuItem>
<MenuItem value={20}>20%</MenuItem>
<MenuItem value={40}>40%</MenuItem>
<MenuItem value={60}>60%</MenuItem>
<MenuItem value={80}>80%</MenuItem>
<MenuItem value={100}>100%</MenuItem>
</Select> </Select>
</Box> </Box>
<Box <Box
@ -388,7 +448,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
}} }}
> >
<Label> <Label>
Minimum Block delay for Group Transaction Approvals {t('group.block_delay.minimum', {
postProcess: 'capitalize',
})}
</Label> </Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
@ -397,18 +459,42 @@ export const AddGroup = ({ address, open, setOpen }) => {
label="Minimum Block delay" label="Minimum Block delay"
onChange={handleChangeMinBlock} onChange={handleChangeMinBlock}
> >
<MenuItem value={5}>5 minutes</MenuItem> <MenuItem value={5}>
<MenuItem value={10}>10 minutes</MenuItem> {t('core.time.minute', { count: 5 })}
<MenuItem value={30}>30 minutes</MenuItem> </MenuItem>
<MenuItem value={60}>1 hour</MenuItem> <MenuItem value={10}>
<MenuItem value={180}>3 hours</MenuItem> {t('core.time.minute', { count: 10 })}
<MenuItem value={300}>5 hours</MenuItem> </MenuItem>
<MenuItem value={420}>7 hours</MenuItem> <MenuItem value={30}>
<MenuItem value={720}>12 hours</MenuItem> {t('core.time.minute', { count: 30 })}
<MenuItem value={1440}>1 day</MenuItem> </MenuItem>
<MenuItem value={4320}>3 days</MenuItem> <MenuItem value={60}>
<MenuItem value={7200}>5 days</MenuItem> {t('core.time.hour', { count: 1 })}
<MenuItem value={10080}>7 days</MenuItem> </MenuItem>
<MenuItem value={180}>
{t('core.time.hour', { count: 3 })}
</MenuItem>
<MenuItem value={300}>
{t('core.time.hour', { count: 5 })}
</MenuItem>
<MenuItem value={420}>
{t('core.time.hour', { count: 7 })}
</MenuItem>
<MenuItem value={720}>
{t('core.time.hour', { count: 12 })}
</MenuItem>
<MenuItem value={1440}>
{t('core.time.day', { count: 1 })}
</MenuItem>
<MenuItem value={4320}>
{t('core.time.day', { count: 3 })}
</MenuItem>
<MenuItem value={7200}>
{t('core.time.day', { count: 5 })}
</MenuItem>
<MenuItem value={10080}>
{t('core.time.day', { count: 7 })}
</MenuItem>
</Select> </Select>
</Box> </Box>
<Box <Box
@ -419,7 +505,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
}} }}
> >
<Label> <Label>
Maximum Block delay for Group Transaction Approvals {t('group.block_delay.maximum', {
postProcess: 'capitalize',
})}
</Label> </Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
@ -428,17 +516,39 @@ export const AddGroup = ({ address, open, setOpen }) => {
label="Maximum Block delay" label="Maximum Block delay"
onChange={handleChangeMaxBlock} onChange={handleChangeMaxBlock}
> >
<MenuItem value={60}>1 hour</MenuItem> <MenuItem value={60}>
<MenuItem value={180}>3 hours</MenuItem> {t('core.time.hour', { count: 1 })}
<MenuItem value={300}>5 hours</MenuItem> </MenuItem>
<MenuItem value={420}>7 hours</MenuItem> <MenuItem value={180}>
<MenuItem value={720}>12 hours</MenuItem> 3{t('core.time.hour', { count: 3 })}
<MenuItem value={1440}>1 day</MenuItem> </MenuItem>
<MenuItem value={4320}>3 days</MenuItem> <MenuItem value={300}>
<MenuItem value={7200}>5 days</MenuItem> {t('core.time.hour', { count: 5 })}
<MenuItem value={10080}>7 days</MenuItem> </MenuItem>
<MenuItem value={14400}>10 days</MenuItem> <MenuItem value={420}>
<MenuItem value={21600}>15 days</MenuItem> {t('core.time.hour', { count: 7 })}
</MenuItem>
<MenuItem value={720}>
{t('core.time.hour', { count: 12 })}
</MenuItem>
<MenuItem value={1440}>
{t('core.time.day', { count: 1 })}
</MenuItem>
<MenuItem value={4320}>
{t('core.time.day', { count: 3 })}
</MenuItem>
<MenuItem value={7200}>
{t('core.time.day', { count: 5 })}
</MenuItem>
<MenuItem value={10080}>
{t('core.time.day', { count: 7 })}
</MenuItem>
<MenuItem value={14400}>
{t('core.time.day', { count: 10 })}
</MenuItem>
<MenuItem value={21600}>
{t('core.time.day', { count: 15 })}
</MenuItem>
</Select> </Select>
</Box> </Box>
</Collapse> </Collapse>
@ -454,7 +564,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
color="primary" color="primary"
onClick={handleCreateGroup} onClick={handleCreateGroup}
> >
Create Group {t('group.action.create', {
postProcess: 'capitalize',
})}
</Button> </Button>
</Box> </Box>
</Box> </Box>
@ -503,6 +615,6 @@ export const AddGroup = ({ address, open, setOpen }) => {
setInfo={setInfoSnack} setInfo={setInfoSnack}
/> />
</Dialog> </Dialog>
</React.Fragment> </Fragment>
); );
}; };

View File

@ -1,6 +1,5 @@
import { import {
Box, Box,
Button,
ListItem, ListItem,
ListItemButton, ListItemButton,
ListItemText, ListItemText,
@ -8,7 +7,7 @@ import {
TextField, TextField,
Typography, Typography,
} from '@mui/material'; } from '@mui/material';
import React, { import {
useCallback, useCallback,
useContext, useContext,
useEffect, useEffect,
@ -25,10 +24,12 @@ import {
import _ from 'lodash'; import _ from 'lodash';
import { MyContext, getBaseApiReact } from '../../App'; import { MyContext, getBaseApiReact } from '../../App';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { getBaseApi, getFee } from '../../background'; import { getFee } from '../../background';
import LockIcon from '@mui/icons-material/Lock'; import LockIcon from '@mui/icons-material/Lock';
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { useTranslation } from 'react-i18next';
const cache = new CellMeasurerCache({ const cache = new CellMeasurerCache({
fixedWidth: true, fixedWidth: true,
defaultHeight: 50, defaultHeight: 50,
@ -36,7 +37,7 @@ const cache = new CellMeasurerCache({
export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => { export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
const { memberGroups, show, setTxList } = useContext(MyContext); const { memberGroups, show, setTxList } = useContext(MyContext);
const { t } = useTranslation(['core', 'group']);
const [groups, setGroups] = useState([]); const [groups, setGroups] = useState([]);
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
@ -101,12 +102,17 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
const handleJoinGroup = async (group, isOpen) => { const handleJoinGroup = async (group, isOpen) => {
try { try {
const groupId = group.groupId; const groupId = group.groupId;
const fee = await getFee('JOIN_GROUP'); // TODO translate
const fee = await getFee('JOIN_GROUP');
await show({ await show({
message: 'Would you like to perform an JOIN_GROUP transaction?', message: t('group:question.join_group', {
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoading(true); setIsLoading(true);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window window
.sendMessage('joinGroup', { .sendMessage('joinGroup', {
@ -116,8 +122,9 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
if (!response?.error) { if (!response?.error) {
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.join_group', {
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
if (isOpen) { if (isOpen) {
@ -125,8 +132,14 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
{ {
...response, ...response,
type: 'joined-group', type: 'joined-group',
label: `Joined Group ${group?.groupName}: awaiting confirmation`, label: t('group:message.success.group_join_label', {
labelDone: `Joined Group ${group?.groupName}: success!`, group_name: group?.groupName,
postProcess: 'capitalize',
}),
labelDone: t('group:message.success.group_join_label', {
group_name: group?.groupName,
postProcess: 'capitalize',
}),
done: false, done: false,
groupId, groupId,
}, },
@ -215,7 +228,10 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
padding: '10px', padding: '10px',
}} }}
> >
<Typography>Join {group?.groupName}</Typography> <Typography>
{t('core:action.join', { postProcess: 'capitalize' })}{' '}
{group?.groupName}
</Typography>
<Typography> <Typography>
{group?.isOpen === false && {group?.isOpen === false &&
'This is a closed/private group, so you will need to wait until an admin accepts your request'} 'This is a closed/private group, so you will need to wait until an admin accepts your request'}
@ -226,7 +242,9 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
variant="contained" variant="contained"
onClick={() => handleJoinGroup(group, group?.isOpen)} onClick={() => handleJoinGroup(group, group?.isOpen)}
> >
Join group {t('group:action.join_group', {
postProcess: 'capitalize',
})}
</LoadingButton> </LoadingButton>
</Box> </Box>
</Popover> </Popover>

View File

@ -129,7 +129,7 @@ export const BlockedUsersModal = () => {
executeEvent('updateChatMessagesWithBlocks', true); executeEvent('updateChatMessagesWithBlocks', true);
} }
} catch (error) { } catch (error) {
setOpenSnackGlobal(true); // TODO translate setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: 'error', type: 'error',
message: error?.message || 'Unable to block user', message: error?.message || 'Unable to block user',

View File

@ -1,5 +1,4 @@
import React, { import React, {
FC,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
@ -17,7 +16,6 @@ import {
ComposeIcon, ComposeIcon,
ComposeP, ComposeP,
GroupContainer, GroupContainer,
GroupNameP,
InstanceFooter, InstanceFooter,
InstanceListContainer, InstanceListContainer,
InstanceListContainerRow, InstanceListContainerRow,
@ -58,10 +56,12 @@ import { executeEvent } from '../../../utils/events';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import { getArbitraryEndpointReact, getBaseApiReact } from '../../../App'; import { getArbitraryEndpointReact, getBaseApiReact } from '../../../App';
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group'; import { addDataPublishesFunc, getDataPublishesFunc } from '../Group';
import { useTranslation } from 'react-i18next';
const filterOptions = ['Recently active', 'Newest', 'Oldest']; const filterOptions = ['Recently active', 'Newest', 'Oldest'];
export const threadIdentifier = 'DOCUMENT'; export const threadIdentifier = 'DOCUMENT';
export const GroupMail = ({ export const GroupMail = ({
selectedGroup, selectedGroup,
userInfo, userInfo,
@ -82,6 +82,7 @@ export const GroupMail = ({
const anchorElInstanceFilter = useRef<any>(null); const anchorElInstanceFilter = useRef<any>(null);
const [tempPublishedList, setTempPublishedList] = useState([]); const [tempPublishedList, setTempPublishedList] = useState([]);
const dataPublishes = useRef({}); const dataPublishes = useRef({});
const { t } = useTranslation(['core']);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const groupIdRef = useRef<any>(null); const groupIdRef = useRef<any>(null);
@ -120,7 +121,9 @@ export const GroupMail = ({
}); });
setTempPublishedList(tempData); setTempPublishedList(tempData);
} }
} catch (error) {} } catch (error) {
console.log(error);
}
}; };
const getEncryptedResource = async ( const getEncryptedResource = async (
@ -627,9 +630,9 @@ export const GroupMail = ({
<ThreadContainer> <ThreadContainer>
<Box <Box
sx={{ sx={{
alignItems: 'center',
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center',
}} }}
> >
<NewThread <NewThread
@ -667,8 +670,8 @@ export const GroupMail = ({
<Spacer height="30px" /> <Spacer height="30px" />
<Box <Box
sx={{ sx={{
display: 'flex',
alignItems: 'center', alignItems: 'center',
display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
> >
@ -682,6 +685,7 @@ export const GroupMail = ({
}} }}
/> />
</Box> </Box>
<Spacer height="30px" /> <Spacer height="30px" />
{combinedListTempAndReal.map((thread) => { {combinedListTempAndReal.map((thread) => {
@ -754,8 +758,8 @@ export const GroupMail = ({
{filterMode === 'Recently active' && ( {filterMode === 'Recently active' && (
<div <div
style={{ style={{
display: 'flex',
alignItems: 'center', alignItems: 'center',
display: 'flex',
}} }}
> >
<ThreadSingleLastMessageP> <ThreadSingleLastMessageP>
@ -776,16 +780,16 @@ export const GroupMail = ({
}, 300); }, 300);
}} }}
sx={{ sx={{
position: 'absolute', alignItems: 'center',
bottom: '2px',
right: '2px',
borderRadius: '5px',
backgroundColor: '#27282c', backgroundColor: '#27282c',
borderRadius: '5px',
bottom: '2px',
cursor: 'pointer',
display: 'flex', display: 'flex',
gap: '10px', gap: '10px',
alignItems: 'center',
padding: '5px', padding: '5px',
cursor: 'pointer', position: 'absolute',
right: '2px',
'&:hover': { '&:hover': {
background: 'rgba(255, 255, 255, 0.60)', background: 'rgba(255, 255, 255, 0.60)',
}, },
@ -795,9 +799,11 @@ export const GroupMail = ({
sx={{ sx={{
color: 'white', color: 'white',
fontSize: '12px', fontSize: '12px',
}} // TODO translate }}
> >
Last page {t('core:page.last', {
postProcess: 'capitalize',
})}
</Typography> </Typography>
<ArrowForwardIosIcon <ArrowForwardIosIcon
sx={{ sx={{
@ -828,7 +834,9 @@ export const GroupMail = ({
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{
message: 'Loading threads... please wait.', message: t('group:message.success.loading_threads', {
postProcess: 'capitalize',
}),
}} }}
/> />
</GroupContainer> </GroupContainer>

View File

@ -1,11 +1,9 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { Box, CircularProgress, Input } from '@mui/material'; import { Box, CircularProgress, Input } from '@mui/material';
import ShortUniqueId from 'short-unique-id'; import ShortUniqueId from 'short-unique-id';
import CloseIcon from '@mui/icons-material/Close';
import ModalCloseSVG from '../../../assets/svgs/ModalClose.svg'; import ModalCloseSVG from '../../../assets/svgs/ModalClose.svg';
import ComposeIconSVG from '../../../assets/svgs/ComposeIcon.svg'; import ComposeIconSVG from '../../../assets/svgs/ComposeIcon.svg';
import { import {
AttachmentContainer,
CloseContainer, CloseContainer,
ComposeContainer, ComposeContainer,
ComposeIcon, ComposeIcon,
@ -30,6 +28,7 @@ import TipTap from '../../Chat/TipTap';
import { MessageDisplay } from '../../Chat/MessageDisplay'; import { MessageDisplay } from '../../Chat/MessageDisplay';
import { CustomizedSnackbars } from '../../Snackbar/Snackbar'; import { CustomizedSnackbars } from '../../Snackbar/Snackbar';
import { saveTempPublish } from '../../Chat/GroupAnnouncements'; import { saveTempPublish } from '../../Chat/GroupAnnouncements';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 8 }); const uid = new ShortUniqueId({ length: 8 });
@ -129,6 +128,7 @@ export const encryptSingleFunc = async (data: string, secretKeyObject: any) => {
console.log(error); console.log(error);
} }
}; };
export const NewThread = ({ export const NewThread = ({
groupInfo, groupInfo,
members, members,
@ -143,8 +143,8 @@ export const NewThread = ({
setPostReply, setPostReply,
isPrivate, isPrivate,
}: NewMessageProps) => { }: NewMessageProps) => {
const { t } = useTranslation(['core', 'group']);
const { show } = React.useContext(MyContext); const { show } = React.useContext(MyContext);
const [isOpen, setIsOpen] = useState<boolean>(false); const [isOpen, setIsOpen] = useState<boolean>(false);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const [isSending, setIsSending] = useState(false); const [isSending, setIsSending] = useState(false);
@ -183,21 +183,28 @@ export const NewThread = ({
const missingFields: string[] = []; const missingFields: string[] = [];
if (!isMessage && !threadTitle) { if (!isMessage && !threadTitle) {
errorMsg = 'Please provide a thread title'; errorMsg = t('group:question.provide_thread', {
postProcess: 'capitalize',
});
} }
if (!name) { if (!name) {
errorMsg = 'Cannot send a message without a access to your name'; errorMsg = t('group:message.error.access_name', {
postProcess: 'capitalize',
});
} }
if (!groupInfo) { if (!groupInfo) {
errorMsg = 'Cannot access group information'; errorMsg = t('group:message.error.group_info', {
} // TODO translate postProcess: 'capitalize',
});
}
// if (!description) missingFields.push('subject') // if (!description) missingFields.push('subject')
if (missingFields.length > 0) { if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', '); const missingFieldsString = missingFields.join(', ');
const errMsg = `Missing: ${missingFieldsString}`; const errMsg = `Missing: ${missingFieldsString}`;
errorMsg = errMsg; errorMsg = errMsg; // TODO translate
} }
if (errorMsg) { if (errorMsg) {

View File

@ -3,7 +3,6 @@ import { Avatar, Box, IconButton } from '@mui/material';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import FormatQuoteIcon from '@mui/icons-material/FormatQuote'; import FormatQuoteIcon from '@mui/icons-material/FormatQuote';
import MoreSVG from '../../../assets/svgs/More.svg'; import MoreSVG from '../../../assets/svgs/More.svg';
import { import {
MoreImg, MoreImg,
MoreP, MoreP,
@ -38,16 +37,16 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
> >
<Box <Box
sx={{ sx={{
alignItems: 'flex-start',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'flex-start',
width: '100%', width: '100%',
}} }}
> >
<Box <Box
sx={{ sx={{
display: 'flex',
alignItems: 'flex-start', alignItems: 'flex-start',
display: 'flex',
gap: '10px', gap: '10px',
}} }}
> >
@ -67,6 +66,7 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
{message?.name?.charAt(0)} {message?.name?.charAt(0)}
</Avatar> </Avatar>
</WrapperUserAction> </WrapperUserAction>
<ThreadInfoColumn> <ThreadInfoColumn>
<WrapperUserAction <WrapperUserAction
disabled={myName === message?.name} disabled={myName === message?.name}
@ -75,6 +75,7 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
> >
<ThreadInfoColumnNameP>{message?.name}</ThreadInfoColumnNameP> <ThreadInfoColumnNameP>{message?.name}</ThreadInfoColumnNameP>
</WrapperUserAction> </WrapperUserAction>
<ThreadInfoColumnTime> <ThreadInfoColumnTime>
{formatTimestampForum(message?.created)} {formatTimestampForum(message?.created)}
</ThreadInfoColumnTime> </ThreadInfoColumnTime>
@ -205,6 +206,7 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
> >
{message?.reply?.name?.charAt(0)} {message?.reply?.name?.charAt(0)}
</Avatar> </Avatar>
<ThreadInfoColumn> <ThreadInfoColumn>
<ThreadInfoColumnNameP <ThreadInfoColumnNameP
sx={{ sx={{
@ -215,6 +217,7 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
</ThreadInfoColumnNameP> </ThreadInfoColumnNameP>
</ThreadInfoColumn> </ThreadInfoColumn>
</Box> </Box>
<MessageDisplay htmlContent={message?.reply?.textContentV2} /> <MessageDisplay htmlContent={message?.reply?.textContentV2} />
</Box> </Box>
<Spacer height="20px" /> <Spacer height="20px" />

View File

@ -1,20 +1,11 @@
import React, { import React, {
FC,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
import { import { Avatar, Box, Button, ButtonBase, Typography } from '@mui/material';
Avatar,
Box,
Button,
ButtonBase,
IconButton,
Skeleton,
Typography,
} from '@mui/material';
import { ShowMessage } from './ShowMessageWithoutModal'; import { ShowMessage } from './ShowMessageWithoutModal';
import { import {
ComposeP, ComposeP,
@ -51,8 +42,12 @@ import { RequestQueueWithPromise } from '../../../utils/queue/queue';
import { CustomLoader } from '../../../common/CustomLoader'; import { CustomLoader } from '../../../common/CustomLoader';
import { WrapperUserAction } from '../../WrapperUserAction'; import { WrapperUserAction } from '../../WrapperUserAction';
import { formatTimestampForum } from '../../../utils/time'; import { formatTimestampForum } from '../../../utils/time';
import { useTranslation } from 'react-i18next';
const requestQueueSaveToLocal = new RequestQueueWithPromise(1); const requestQueueSaveToLocal = new RequestQueueWithPromise(1);
const requestQueueDownloadPost = new RequestQueueWithPromise(3); const requestQueueDownloadPost = new RequestQueueWithPromise(3);
interface ThreadProps { interface ThreadProps {
currentThread: any; currentThread: any;
groupInfo: any; groupInfo: any;
@ -120,6 +115,7 @@ export const Thread = ({
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [postReply, setPostReply] = useState(null); const [postReply, setPostReply] = useState(null);
const [hasLastPage, setHasLastPage] = useState(false); const [hasLastPage, setHasLastPage] = useState(false);
const { t } = useTranslation(['core']);
// Update: Use a new ref for the scrollable container // Update: Use a new ref for the scrollable container
const threadContainerRef = useRef(null); const threadContainerRef = useRef(null);
@ -251,6 +247,7 @@ export const Thread = ({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}); });
const responseData = await response.json(); const responseData = await response.json();
let fullArrayMsg = [...responseData]; let fullArrayMsg = [...responseData];
@ -431,6 +428,7 @@ export const Thread = ({
} }
const newArray = responseData.slice(0, findMessage).reverse(); const newArray = responseData.slice(0, findMessage).reverse();
let fullArrayMsg = [...messages]; let fullArrayMsg = [...messages];
for (const message of newArray) { for (const message of newArray) {
try { try {
const responseDataMessage = await getEncryptedResource({ const responseDataMessage = await getEncryptedResource({
@ -468,7 +466,6 @@ export const Thread = ({
setMessages(fullArrayMsg); setMessages(fullArrayMsg);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally {
} }
}, },
[messages] [messages]
@ -565,20 +562,20 @@ export const Thread = ({
return ( return (
<GroupContainer <GroupContainer
sx={{ sx={{
position: 'relative',
width: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
overflow: 'hidden', overflow: 'hidden',
position: 'relative',
width: '100%',
}} }}
// Removed the ref from here since the scrollable area has changed // Removed the ref from here since the scrollable area has changed
> >
<Box <Box
sx={{ sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
display: 'flex',
flexShrink: 0, // Corrected property name flexShrink: 0, // Corrected property name
justifyContent: 'space-between',
}} }}
> >
<NewThread <NewThread
@ -598,9 +595,9 @@ export const Thread = ({
/> />
<Box <Box
sx={{ sx={{
alignItems: 'center',
display: 'flex', display: 'flex',
gap: '35px', gap: '35px',
alignItems: 'center',
}} }}
> >
<ShowMessageReturnButton <ShowMessageReturnButton
@ -610,7 +607,11 @@ export const Thread = ({
}} }}
> >
<MailIconImg src={ReturnSVG} /> <MailIconImg src={ReturnSVG} />
<ComposeP>Return to Threads</ComposeP> <ComposeP>
{t('group:action.return_to_thread', {
postProcess: 'capitalize',
})}
</ComposeP>
</ShowMessageReturnButton> </ShowMessageReturnButton>
{/* Conditionally render the scroll buttons */} {/* Conditionally render the scroll buttons */}
{showScrollButton && {showScrollButton &&
@ -658,6 +659,7 @@ export const Thread = ({
> >
<GroupNameP>{currentThread?.threadData?.title}</GroupNameP> <GroupNameP>{currentThread?.threadData?.title}</GroupNameP>
</Box> </Box>
<Spacer height={'15px'} /> <Spacer height={'15px'} />
<Box <Box
@ -685,8 +687,9 @@ export const Thread = ({
disabled={!hasFirstPage} disabled={!hasFirstPage}
variant="contained" variant="contained"
> >
First {t('core:page.first', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
textTransformation: 'capitalize', textTransformation: 'capitalize',
@ -701,9 +704,9 @@ export const Thread = ({
); );
}} }}
disabled={!hasPreviousPage} disabled={!hasPreviousPage}
variant="contained" // TODO translate variant="contained"
> >
Previous {t('core:page.previous', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
@ -721,7 +724,7 @@ export const Thread = ({
disabled={!hasNextPage} disabled={!hasNextPage}
variant="contained" variant="contained"
> >
Next {t('core:page.next', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
@ -739,7 +742,7 @@ export const Thread = ({
disabled={!hasLastPage} disabled={!hasLastPage}
variant="contained" variant="contained"
> >
Last {t('core:page.last', { postProcess: 'capitalize' })}
</Button> </Button>
</Box> </Box>
@ -925,7 +928,7 @@ export const Thread = ({
color: 'white', color: 'white',
}} }}
> >
Downloading from QDN {t('core:downloading_qdn', { postProcess: 'capitalize' })}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
@ -959,7 +962,9 @@ export const Thread = ({
color: 'white', color: 'white',
}} }}
> >
Refetch page {t('group:action.refetch_page', {
postProcess: 'capitalize',
})}
</Button> </Button>
</Box> </Box>
</> </>
@ -997,7 +1002,7 @@ export const Thread = ({
disabled={!hasFirstPage} disabled={!hasFirstPage}
variant="contained" variant="contained"
> >
First {t('core:page.first', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
@ -1015,7 +1020,7 @@ export const Thread = ({
disabled={!hasPreviousPage} disabled={!hasPreviousPage}
variant="contained" variant="contained"
> >
Previous {t('core:page.previous', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
@ -1033,7 +1038,7 @@ export const Thread = ({
disabled={!hasNextPage} disabled={!hasNextPage}
variant="contained" variant="contained"
> >
Next {t('core:page.next', { postProcess: 'capitalize' })}
</Button> </Button>
<Button <Button
sx={{ sx={{
@ -1051,7 +1056,7 @@ export const Thread = ({
disabled={!hasLastPage} disabled={!hasLastPage}
variant="contained" variant="contained"
> >
Last {t('core:page.last', { postProcess: 'capitalize' })}
</Button> </Button>
</Box> </Box>
<Spacer height="30px" /> <Spacer height="30px" />
@ -1063,7 +1068,7 @@ export const Thread = ({
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{
message: 'Loading posts... please wait.', message: t('core:loading_posts', { postProcess: 'capitalize' }),
}} }}
/> />
</GroupContainer> </GroupContainer>

View File

@ -55,8 +55,6 @@ import { RequestQueueWithPromise } from '../../utils/queue/queue';
import { WebSocketActive } from './WebsocketActive'; import { WebSocketActive } from './WebsocketActive';
import { useMessageQueue } from '../../MessageQueueContext'; import { useMessageQueue } from '../../MessageQueueContext';
import { ContextMenu } from '../ContextMenu'; import { ContextMenu } from '../ContextMenu';
import { ReturnIcon } from '../../assets/Icons/ReturnIcon';
import { ExitIcon } from '../../assets/Icons/ExitIcon';
import { HomeDesktop } from './HomeDesktop'; import { HomeDesktop } from './HomeDesktop';
import { IconWrapper } from '../Desktop/DesktopFooter'; import { IconWrapper } from '../Desktop/DesktopFooter';
import { DesktopHeader } from '../Desktop/DesktopHeader'; import { DesktopHeader } from '../Desktop/DesktopHeader';
@ -80,6 +78,7 @@ import LockIcon from '@mui/icons-material/Lock';
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
import { BlockedUsersModal } from './BlockedUsersModal'; import { BlockedUsersModal } from './BlockedUsersModal';
import { WalletsAppWrapper } from './WalletsAppWrapper'; import { WalletsAppWrapper } from './WalletsAppWrapper';
import { useTranslation } from 'react-i18next';
export const getPublishesFromAdmins = async (admins: string[], groupId) => { export const getPublishesFromAdmins = async (admins: string[], groupId) => {
const queryString = admins.map((name) => `name=${name}`).join('&'); const queryString = admins.map((name) => `name=${name}`).join('&');
@ -450,6 +449,7 @@ export const Group = ({
const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false); const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false);
const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] =
useState(false); useState(false);
const { t } = useTranslation(['core', 'group']);
const [groupsProperties, setGroupsProperties] = const [groupsProperties, setGroupsProperties] =
useRecoilState(groupsPropertiesAtom); useRecoilState(groupsPropertiesAtom);
@ -2219,9 +2219,10 @@ export const Group = ({
color: theme.palette.text.primary, color: theme.palette.text.primary,
}} }}
> >
No group selected {t('group:message.generic.no_selection', {
</Typography>{' '} postProcess: 'capitalize',
// TODO translate })}
</Typography>
</Box> </Box>
)} )}
@ -2317,9 +2318,9 @@ export const Group = ({
> >
{' '} {' '}
<Typography> <Typography>
The group's first common encryption key is in the process {t('group:message.generic.encryption_key', {
of creation. Please wait a few minutes for it to be postProcess: 'capitalize',
retrieved by the network. Checking every 2 minutes... })}
</Typography> </Typography>
</div> </div>
)} )}
@ -2343,18 +2344,23 @@ export const Group = ({
> >
{' '} {' '}
<Typography> <Typography>
You are not part of the encrypted group of members. Wait {t('group:message.generic.not_part_group', {
until an admin re-encrypts the keys. postProcess: 'capitalize',
})}
</Typography> </Typography>
<Spacer height="25px" /> <Spacer height="25px" />
<Typography> <Typography>
<strong> <strong>
Only unencrypted messages will be displayed. {t('group:message.generic.only_encrypted', {
postProcess: 'capitalize',
})}
</strong> </strong>
</Typography> </Typography>
<Spacer height="25px" /> <Spacer height="25px" />
<Typography> <Typography>
Try notifying an admin from the list of admins below: {t('group:message.generic.notify_admins', {
postProcess: 'capitalize',
})}
</Typography> </Typography>
<Spacer height="25px" /> <Spacer height="25px" />
{adminsWithNames.map((admin) => { {adminsWithNames.map((admin) => {
@ -2374,7 +2380,9 @@ export const Group = ({
variant="contained" variant="contained"
onClick={() => notifyAdmin(admin)} onClick={() => notifyAdmin(admin)}
> >
Notify {t('core:action.notify', {
postProcess: 'capitalize',
})}
</LoadingButton> </LoadingButton>
</Box> </Box>
); );
@ -2594,14 +2602,19 @@ export const Group = ({
open={isLoadingGroup} open={isLoadingGroup}
info={{ info={{
message: message:
isLoadingGroupMessage || 'Setting up group... please wait.', isLoadingGroupMessage ||
t('group:message.generic.setting_group', {
postProcess: 'capitalize',
}),
}} }}
/> />
<LoadingSnackbar <LoadingSnackbar
open={isLoadingGroups} open={isLoadingGroups}
info={{ info={{
message: 'Setting up groups... please wait.', message: t('group:message.generic.setting_group', {
postProcess: 'capitalize',
}),
}} }}
/> />
<WalletsAppWrapper /> <WalletsAppWrapper />

View File

@ -1,4 +1,4 @@
import * as React from 'react'; import { useEffect, useState } from 'react';
import List from '@mui/material/List'; import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem'; import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton'; import ListItemButton from '@mui/material/ListItemButton';
@ -12,14 +12,13 @@ import { CustomLoader } from '../../common/CustomLoader';
import { getBaseApiReact } from '../../App'; import { getBaseApiReact } from '../../App';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { useTranslation } from 'react-i18next';
export const GroupInvites = ({ myAddress, setOpenAddGroup }) => { export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState( const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]);
[] const [isExpanded, setIsExpanded] = useState(false);
);
const [isExpanded, setIsExpanded] = React.useState(false);
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = useState(true);
const getJoinRequests = async () => { const getJoinRequests = async () => {
try { try {
@ -38,9 +37,10 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
} }
}; };
const { t } = useTranslation(['core', 'group']);
const theme = useTheme(); const theme = useTheme();
React.useEffect(() => { useEffect(() => {
if (myAddress) { if (myAddress) {
getJoinRequests(); getJoinRequests();
} }
@ -69,9 +69,9 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
<Typography <Typography
sx={{ sx={{
fontSize: '1rem', fontSize: '1rem',
}} // TODO translate }}
> >
Group Invites{' '} {t('group:group_invites', { postProcess: 'capitalize' })}{' '}
{groupsWithJoinRequests?.length > 0 && {groupsWithJoinRequests?.length > 0 &&
` (${groupsWithJoinRequests?.length})`} ` (${groupsWithJoinRequests?.length})`}
</Typography> </Typography>
@ -130,7 +130,9 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
fontWeight: 400, fontWeight: 400,
}} }}
> >
Nothing to display {t('group:message.generic.no_display', {
postProcess: 'capitalize',
})}
</Typography> </Typography>
</Box> </Box>
)} )}
@ -177,7 +179,10 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
fontWeight: 400, fontWeight: 400,
}, },
}} }}
primary={`${group?.groupName} has invited you`} primary={t('group:message.generic.group_invited_you', {
group: group?.groupName,
postProcess: 'capitalize',
})}
/> />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>

View File

@ -14,6 +14,7 @@ import { myGroupsWhereIAmAdminAtom } from '../../atoms/global';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { useTranslation } from 'react-i18next';
export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2); export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2);
export const GroupJoinRequests = ({ export const GroupJoinRequests = ({
@ -27,7 +28,7 @@ export const GroupJoinRequests = ({
setDesktopViewMode, setDesktopViewMode,
}) => { }) => {
const [isExpanded, setIsExpanded] = React.useState(false); const [isExpanded, setIsExpanded] = React.useState(false);
const { t } = useTranslation(['core', 'group']);
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState( const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState(
[] []
); );
@ -139,9 +140,9 @@ export const GroupJoinRequests = ({
<Typography <Typography
sx={{ sx={{
fontSize: '1rem', fontSize: '1rem',
}} // TODO translate }}
> >
Join Requests{' '} {t('group:join_requests', { postProcess: 'capitalize' })}{' '}
{filteredJoinRequests?.filter((group) => group?.data?.length > 0) {filteredJoinRequests?.filter((group) => group?.data?.length > 0)
?.length > 0 && ?.length > 0 &&
` (${filteredJoinRequests?.filter((group) => group?.data?.length > 0)?.length})`} ` (${filteredJoinRequests?.filter((group) => group?.data?.length > 0)?.length})`}
@ -163,14 +164,13 @@ export const GroupJoinRequests = ({
<Collapse in={isExpanded} timeout="auto" unmountOnExit> <Collapse in={isExpanded} timeout="auto" unmountOnExit>
<Box <Box
sx={{ sx={{
width: '322px', bgcolor: 'background.paper',
height: '250px', borderRadius: '19px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
bgcolor: 'background.paper', height: '250px',
padding: '20px', padding: '20px',
borderRadius: '19px', width: '322px',
}} }}
> >
{loading && filteredJoinRequests.length === 0 && ( {loading && filteredJoinRequests.length === 0 && (
@ -204,18 +204,20 @@ export const GroupJoinRequests = ({
color: 'rgba(255, 255, 255, 0.2)', color: 'rgba(255, 255, 255, 0.2)',
}} }}
> >
Nothing to display {t('group:message.generic.no_display', {
postProcess: 'capitalize',
})}
</Typography> </Typography>
</Box> </Box>
)} )}
<List <List
className="scrollable-container" className="scrollable-container"
sx={{ sx={{
width: '100%',
maxWidth: 360,
bgcolor: 'background.paper', bgcolor: 'background.paper',
maxHeight: '300px', maxHeight: '300px',
maxWidth: 360,
overflow: 'auto', overflow: 'auto',
width: '100%',
}} }}
> >
{filteredJoinRequests?.map((group) => { {filteredJoinRequests?.map((group) => {

View File

@ -4,16 +4,21 @@ import { useState } from 'react';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { Label } from './AddGroup'; import { Label } from './AddGroup';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { useTranslation } from 'react-i18next';
export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => { export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const [expiryTime, setExpiryTime] = useState<string>('259200'); const [expiryTime, setExpiryTime] = useState<string>('259200');
const [isLoadingInvite, setIsLoadingInvite] = useState(false); const [isLoadingInvite, setIsLoadingInvite] = useState(false);
const { t } = useTranslation(['core', 'group']);
const inviteMember = async () => { const inviteMember = async () => {
try { try {
const fee = await getFee('GROUP_INVITE'); const fee = await getFee('GROUP_INVITE');
await show({ await show({
message: 'Would you like to perform a GROUP_INVITE transaction?', message: t('group:question.group_invite', {
postProcess: 'capitalize',
}),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingInvite(true); setIsLoadingInvite(true);
@ -27,10 +32,12 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
}) })
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
// TODO translate
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: `Successfully invited ${value}. It may take a couple of minutes for the changes to propagate`, message: t('group:message.success.group_invite', {
value: value,
postProcess: 'capitalize',
}),
}); });
setOpenSnack(true); setOpenSnack(true);
res(response); res(response);
@ -72,7 +79,7 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
flexDirection: 'column', flexDirection: 'column',
}} }}
> >
Invite member {t('group:action.invite_member', { postProcess: 'capitalize' })}
<Spacer height="20px" /> <Spacer height="20px" />
<Input <Input
value={value} value={value}
@ -80,24 +87,26 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
onChange={(e) => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
/> />
<Spacer height="20px" /> <Spacer height="20px" />
<Label>Invitation Expiry Time</Label> <Label>
{t('group:invitation_expiry', { postProcess: 'capitalize' })}
</Label>
<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
id="demo-simple-select" id="demo-simple-select"
value={expiryTime} value={expiryTime}
label="Invitation Expiry Time" label={t('group:invitation_expiry', { postProcess: 'capitalize' })}
onChange={handleChange} onChange={handleChange}
> >
<MenuItem value={10800}>3 hours</MenuItem> <MenuItem value={10800}>{t('core.time.hour', { count: 3 })}</MenuItem>
<MenuItem value={21600}>6 hours</MenuItem> <MenuItem value={21600}>{t('core.time.hour', { count: 6 })}</MenuItem>
<MenuItem value={43200}>12 hours</MenuItem> <MenuItem value={43200}>{t('core.time.hour', { count: 12 })}</MenuItem>
<MenuItem value={86400}>1 day</MenuItem> <MenuItem value={86400}>{t('core.time.day', { count: 1 })}</MenuItem>
<MenuItem value={259200}>3 days</MenuItem> <MenuItem value={259200}>{t('core.time.day', { count: 3 })}</MenuItem>
<MenuItem value={432000}>5 days</MenuItem> <MenuItem value={432000}>{t('core.time.day', { count: 5 })}</MenuItem>
<MenuItem value={604800}>7 days</MenuItem> <MenuItem value={604800}>{t('core.time.day', { count: 7 })}</MenuItem>
<MenuItem value={864000}>10 days</MenuItem> <MenuItem value={864000}>{t('core.time.day', { count: 10 })}</MenuItem>
<MenuItem value={1296000}>15 days</MenuItem> <MenuItem value={1296000}>{t('core.time.day', { count: 15 })}</MenuItem>
<MenuItem value={2592000}>30 days</MenuItem> <MenuItem value={2592000}>{t('core.time.day', { count: 30 })}</MenuItem>
</Select> </Select>
<Spacer height="20px" /> <Spacer height="20px" />
<LoadingButton <LoadingButton
@ -106,7 +115,7 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
loading={isLoadingInvite} loading={isLoadingInvite}
onClick={inviteMember} onClick={inviteMember}
> >
Invite {t('core:action.invite', { postProcess: 'capitalize' })}
</LoadingButton> </LoadingButton>
</Box> </Box>
); );

View File

@ -18,6 +18,7 @@ import { getNameInfo } from './Group';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { getBaseApiReact } from '../../App'; import { getBaseApiReact } from '../../App';
import { useTranslation } from 'react-i18next';
export const getMemberInvites = async (groupNumber) => { export const getMemberInvites = async (groupNumber) => {
const response = await fetch( const response = await fetch(
@ -55,6 +56,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
const listRef = useRef(); const listRef = useRef();
const [isLoadingUnban, setIsLoadingUnban] = useState(false); const [isLoadingUnban, setIsLoadingUnban] = useState(false);
const { t } = useTranslation(['core', 'group']);
const getInvites = async (groupId) => { const getInvites = async (groupId) => {
try { try {
@ -84,10 +86,9 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
const handleCancelBan = async (address) => { const handleCancelBan = async (address) => {
try { try {
// TODO translate
const fee = await getFee('CANCEL_GROUP_BAN'); const fee = await getFee('CANCEL_GROUP_BAN');
await show({ await show({
message: 'Would you like to perform a CANCEL_GROUP_BAN transaction?', message: t('group:question.cancel_ban', { postProcess: 'capitalize' }),
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',
}); });
setIsLoadingUnban(true); setIsLoadingUnban(true);
@ -103,8 +104,9 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
setIsLoadingUnban(false); setIsLoadingUnban(false);
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: message: t('group:message.success.unbanned_user', {
'Successfully unbanned user. It may take a couple of minutes for the changes to propagate', postProcess: 'capitalize',
}),
}); });
handlePopoverClose(); handlePopoverClose();
setOpenSnack(true); setOpenSnack(true);
@ -127,6 +129,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
}); });
}); });
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setIsLoadingUnban(false); setIsLoadingUnban(false);
} }
@ -177,10 +180,13 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
variant="contained" variant="contained"
onClick={() => handleCancelBan(member?.offender)} onClick={() => handleCancelBan(member?.offender)}
> >
Cancel Ban {t('group:action.cancel_ban', {
postProcess: 'capitalize',
})}
</LoadingButton> </LoadingButton>
</Box> </Box>
</Popover> </Popover>
<ListItemButton <ListItemButton
onClick={(event) => handlePopoverOpen(event, index)} onClick={(event) => handlePopoverOpen(event, index)}
> >
@ -205,7 +211,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
return ( return (
<div> <div>
<p>Ban list</p> <p>{t('group:ban_list', { postProcess: 'capitalize' })}</p>
<div <div
style={{ style={{
position: 'relative', position: 'relative',

View File

@ -1,4 +1,4 @@
import * as React from 'react'; import { forwardRef, Fragment, ReactElement, Ref, useEffect } from 'react';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';
import AppBar from '@mui/material/AppBar'; import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
@ -11,13 +11,6 @@ import { Box, FormControlLabel, Switch, styled, useTheme } from '@mui/material';
import { enabledDevModeAtom } from '../../atoms/global'; import { enabledDevModeAtom } from '../../atoms/global';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
function a11yProps(index: number) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
const LocalNodeSwitch = styled(Switch)(({ theme }) => ({ const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
padding: 8, padding: 8,
'& .MuiSwitch-track': { '& .MuiSwitch-track': {
@ -51,11 +44,11 @@ const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
}, },
})); }));
const Transition = React.forwardRef(function Transition( const Transition = forwardRef(function Transition(
props: TransitionProps & { props: TransitionProps & {
children: React.ReactElement; children: ReactElement;
}, },
ref: React.Ref<unknown> ref: Ref<unknown>
) { ) {
return <Slide direction="up" ref={ref} {...props} />; return <Slide direction="up" ref={ref} {...props} />;
}); });
@ -118,12 +111,12 @@ export const Settings = ({ address, open, setOpen }) => {
} }
}; };
React.useEffect(() => { useEffect(() => {
getUserSettings(); getUserSettings();
}, []); }, []);
return ( return (
<React.Fragment> <Fragment>
<Dialog <Dialog
fullScreen fullScreen
open={open} open={open}
@ -192,6 +185,6 @@ export const Settings = ({ address, open, setOpen }) => {
)} )}
</Box> </Box>
</Dialog> </Dialog>
</React.Fragment> </Fragment>
); );
}; };

View File

@ -141,21 +141,6 @@ export const ThingsToDoInitial = ({
outline: '1px solid rgba(9, 182, 232, 1)', outline: '1px solid rgba(9, 182, 232, 1)',
}} }}
/> />
{/* <Checkbox
edge="start"
checked={checked1}
tabIndex={-1}
disableRipple
disabled={true}
sx={{
"&.Mui-checked": {
color: "white", // Customize the color when checked
},
"& .MuiSvgIcon-root": {
color: "white",
},
}}
/> */}
</ListItemIcon> </ListItemIcon>
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
@ -163,15 +148,6 @@ export const ThingsToDoInitial = ({
sx={{ sx={{
marginBottom: '20px', marginBottom: '20px',
}} }}
// secondaryAction={
// <IconButton edge="end" aria-label="comments">
// <InfoIcon
// sx={{
// color: "white",
// }}
// />
// </IconButton>
// }
disablePadding disablePadding
> >
<ListItemButton <ListItemButton
@ -215,34 +191,6 @@ export const ThingsToDoInitial = ({
</ListItemIcon> </ListItemIcon>
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
{/* <ListItem
disablePadding
>
<ListItemButton sx={{
padding: "0px",
}} disableRipple role={undefined} dense>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "13px",
fontWeight: 400,
},
}} primary={`Join a group`} />
<ListItemIcon sx={{
justifyContent: "flex-end",
}}>
<Box
sx={{
height: "18px",
width: "18px",
borderRadius: "50%",
backgroundColor: checked3 ? "rgba(9, 182, 232, 1)" : "transparent",
outline: "1px solid rgba(9, 182, 232, 1)",
}}
/>
</ListItemIcon>
</ListItemButton>
</ListItem> */}
</List> </List>
)} )}
</Box> </Box>

View File

@ -88,7 +88,7 @@ export const WalletsAppWrapper = () => {
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
> >
<Typography>Q-Wallets</Typography> // TODO translate <Typography>Q-Wallets</Typography>
<ButtonBase onClick={handleClose}> <ButtonBase onClick={handleClose}>
<CloseIcon <CloseIcon
sx={{ sx={{

View File

@ -107,7 +107,6 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
directs: sortedDirects, directs: sortedDirects,
}) })
.catch((error) => { .catch((error) => {
// TODO translate
console.error( console.error(
'Failed to handle active group data from socket:', 'Failed to handle active group data from socket:',
error.message || 'An error occurred' error.message || 'An error occurred'

View File

@ -17,7 +17,7 @@ export const useBlockedAddresses = () => {
if (userBlockedRef.current[address]) return true; if (userBlockedRef.current[address]) return true;
return false; return false;
} catch (error) { } catch (error) {
//error console.log(error);
} }
}, []); }, []);
@ -42,10 +42,13 @@ export const useBlockedAddresses = () => {
console.error('Failed qortalRequest', error); console.error('Failed qortalRequest', error);
}); });
}); });
const blockedUsers = {}; const blockedUsers = {};
response?.forEach((item) => { response?.forEach((item) => {
blockedUsers[item] = true; blockedUsers[item] = true;
}); });
userBlockedRef.current = blockedUsers; userBlockedRef.current = blockedUsers;
const response2 = await new Promise((res, rej) => { const response2 = await new Promise((res, rej) => {
@ -66,10 +69,13 @@ export const useBlockedAddresses = () => {
console.error('Failed qortalRequest', error); console.error('Failed qortalRequest', error);
}); });
}); });
const blockedUsers2 = {}; const blockedUsers2 = {};
response2?.forEach((item) => { response2?.forEach((item) => {
blockedUsers2[item] = true; blockedUsers2[item] = true;
}); });
userNamesBlockedRef.current = blockedUsers2; userNamesBlockedRef.current = blockedUsers2;
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View File

@ -22,7 +22,7 @@ export const useHandleUserInfo = () => {
}; };
return data?.level; return data?.level;
} catch (error) { } catch (error) {
//error console.log(error);
} }
}, []); }, []);

View File

@ -1,46 +1,52 @@
import { Box, ButtonBase, Typography } from '@mui/material'; import { Box, ButtonBase, Typography } from '@mui/material';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { useTranslation } from 'react-i18next';
export const NewUsersCTA = ({ balance }) => { export const NewUsersCTA = ({ balance }) => {
const { t } = useTranslation(['core']);
if (balance === undefined || +balance > 0) return null; if (balance === undefined || +balance > 0) return null;
return ( return (
<Box <Box
sx={{ sx={{
width: '100%', alignItems: 'center',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', width: '100%',
}} }}
> >
<Spacer height="40px" /> <Spacer height="40px" />
<Box <Box
sx={{ sx={{
width: '320px',
justifyContent: 'center',
flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
padding: '15px',
outline: '1px solid gray',
borderRadius: '4px', borderRadius: '4px',
flexDirection: 'column',
justifyContent: 'center',
outline: '1px solid gray',
padding: '15px',
width: '320px',
}} }}
> >
<Typography <Typography
sx={{ sx={{
textAlign: 'center',
fontSize: '1.2rem', fontSize: '1.2rem',
fontWeight: 'bold', fontWeight: 'bold',
textAlign: 'center',
}} }}
> >
Are you a new user? {t('core:new_user', { postProcess: 'capitalize' })}
</Typography>{' '}
// TODO translate
<Spacer height="20px" />
<Typography>
Please message us on Telegram or Discord if you need 4 QORT to start
chatting without any limitations
</Typography> </Typography>
<Spacer height="20px" /> <Spacer height="20px" />
<Typography>
{t('core:message_us', { postProcess: 'capitalize' })}
</Typography>
<Spacer height="20px" />
<Box <Box
sx={{ sx={{
width: '100%', width: '100%',
@ -68,6 +74,7 @@ export const NewUsersCTA = ({ balance }) => {
> >
Telegram Telegram
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
sx={{ sx={{
textDecoration: 'underline', textDecoration: 'underline',

View File

@ -0,0 +1,92 @@
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { supportedLanguages } from '../../../i18n';
import { Tooltip, useTheme } from '@mui/material';
const LanguageSelector = () => {
const { i18n, t } = useTranslation(['core']);
const [showSelect, setShowSelect] = useState(false);
const theme = useTheme();
const selectorRef = useRef(null);
const handleChange = (e) => {
const newLang = e.target.value;
i18n.changeLanguage(newLang);
setShowSelect(false);
};
const currentLang = i18n.language;
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}
style={{
bottom: '5%',
display: 'flex',
gap: '12px',
left: '1.5vh',
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>
) : (
<button
onClick={() => setShowSelect(true)}
style={{
fontSize: '1.5rem',
border: 'none',
background: 'none',
cursor: 'pointer',
}}
aria-label={`Current language: ${name}`}
>
{showSelect ? undefined : flag}
</button>
)}
</Tooltip>
</div>
);
};
export default LanguageSelector;

View File

@ -265,7 +265,7 @@ export const Minting = ({
rej({ message: response.error }); rej({ message: response.error });
}) })
.catch((error) => { .catch((error) => {
rej({ message: error.message || 'An error occurred' }); //TODO translate rej({ message: error.message || 'An error occurred' });
}); });
}); });
} catch (error) { } catch (error) {
@ -280,7 +280,7 @@ export const Minting = ({
}, []); }, []);
const createRewardShare = useCallback(async (publicKey, recipient) => { const createRewardShare = useCallback(async (publicKey, recipient) => {
const fee = await getFee('REWARD_SHARE'); const fee = await getFee('REWARD_SHARE'); // TODO translate
await show({ await show({
message: 'Would you like to perform an REWARD_SHARE transaction?', message: 'Would you like to perform an REWARD_SHARE transaction?',
publishFee: fee.fee + ' QORT', publishFee: fee.fee + ' QORT',

View File

@ -62,6 +62,7 @@ export const QMailStatus = () => {
color: theme.palette.text.primary, color: theme.palette.text.primary,
fontSize: '14px', fontSize: '14px',
fontWeight: 700, fontWeight: 700,
textTransform: 'uppercase',
}} }}
> >
{t('core:q_mail', { {t('core:q_mail', {

View File

@ -176,7 +176,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
.catch((error) => { .catch((error) => {
rej( rej(
error.message || error.message ||
t('core:result.error.generic', { postProcess: 'capitalize' }) t('core:message.error.generic', { postProcess: 'capitalize' })
); );
}); });
}); });
@ -185,7 +185,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
setSettingsQdnLastUpdated(Date.now()); setSettingsQdnLastUpdated(Date.now());
setInfoSnack({ setInfoSnack({
type: 'success', type: 'success',
message: t('core:result.success.publish_qdn', { message: t('core:message.success.publish_qdn', {
postProcess: 'capitalize', postProcess: 'capitalize',
}), }),
}); });
@ -198,7 +198,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
type: 'error', type: 'error',
message: message:
error?.message || error?.message ||
t('core:result.error.save_qdn', { t('core:message.error.save_qdn', {
postProcess: 'capitalize', postProcess: 'capitalize',
}), }),
}); });
@ -591,7 +591,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
} }
}} }}
> >
{t('core:import', { {t('core:action.import', {
postProcess: 'capitalize', postProcess: 'capitalize',
})} })}
</ButtonBase> </ButtonBase>
@ -616,7 +616,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
} }
}} }}
> >
{t('core:export', { {t('core:action.export', {
postProcess: 'capitalize', postProcess: 'capitalize',
})} })}
</ButtonBase> </ButtonBase>

View File

@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
const ThemeSelector = () => { const ThemeSelector = () => {
const { t } = useTranslation(['core']); const { t } = useTranslation(['core']);
const { themeMode, toggleTheme } = useThemeContext(); const { themeMode, toggleTheme } = useThemeContext();
return ( return (
@ -14,7 +15,7 @@ const ThemeSelector = () => {
bottom: '1%', bottom: '1%',
display: 'flex', display: 'flex',
gap: '12px', gap: '12px',
left: '1.5vh', left: '1.2vh',
position: 'absolute', position: 'absolute',
}} }}
> >

View File

@ -91,7 +91,7 @@ export const Tutorials = () => {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button variant="contained" onClick={handleClose}> <Button variant="contained" onClick={handleClose}>
{t('core:close', { postProcess: 'capitalize' })} {t('core:action.close', { postProcess: 'capitalize' })}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@ -138,7 +138,7 @@ export const Tutorials = () => {
<DialogActions> <DialogActions>
<Button variant="contained" onClick={handleClose}> <Button variant="contained" onClick={handleClose}>
{t('core:close', { postProcess: 'capitalize' })} {t('core:action.close', { postProcess: 'capitalize' })}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil'; import { useRecoilState, useSetRecoilState } from 'recoil';
import { import {
canSaveSettingToQdnAtom, canSaveSettingToQdnAtom,
@ -46,6 +46,7 @@ const getPublishRecord = async (myName) => {
return { hasPublishRecord: false }; return { hasPublishRecord: false };
}; };
const getPublish = async (myName) => { const getPublish = async (myName) => {
try { try {
let data; let data;
@ -57,7 +58,6 @@ const getPublish = async (myName) => {
if (!data) throw new Error('Unable to fetch publish'); if (!data) throw new Error('Unable to fetch publish');
const decryptedKey: any = await decryptResource(data); const decryptedKey: any = await decryptResource(data);
const dataint8Array = base64ToUint8Array(decryptedKey.data); const dataint8Array = base64ToUint8Array(decryptedKey.data);
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
return decryptedKeyToObject; return decryptedKeyToObject;
@ -112,6 +112,7 @@ export const useQortalGetSaveSettings = (myName, isAuthenticated) => {
}, },
[] []
); );
useEffect(() => { useEffect(() => {
if ( if (
!myName || !myName ||

View File

@ -1,55 +1,69 @@
import React, { useCallback, useEffect } from 'react' import { useCallback, useEffect } from 'react';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { isUsingImportExportSettingsAtom, oldPinnedAppsAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, sortablePinnedAppsAtom } from './atoms/global'; import {
isUsingImportExportSettingsAtom,
oldPinnedAppsAtom,
settingsLocalLastUpdatedAtom,
settingsQDNLastUpdatedAtom,
sortablePinnedAppsAtom,
} from './atoms/global';
function fetchFromLocalStorage(key) { function fetchFromLocalStorage(key) {
try { try {
const serializedValue = localStorage.getItem(key); const serializedValue = localStorage.getItem(key);
if (serializedValue === null) { if (serializedValue === null) {
return null; return null;
}
return JSON.parse(serializedValue);
} catch (error) {
console.error('Error fetching from localStorage:', error);
return null;
} }
return JSON.parse(serializedValue);
} catch (error) {
console.error('Error fetching from localStorage:', error);
return null;
}
} }
export const useRetrieveDataLocalStorage = (address) => { export const useRetrieveDataLocalStorage = (address) => {
const setSortablePinnedApps = useSetRecoilState(sortablePinnedAppsAtom); const setSortablePinnedApps = useSetRecoilState(sortablePinnedAppsAtom);
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
const setIsUsingImportExportSettings = useSetRecoilState(isUsingImportExportSettingsAtom)
const setSettingsQDNLastUpdated = useSetRecoilState(settingsQDNLastUpdatedAtom);
const setOldPinnedApps = useSetRecoilState(oldPinnedAppsAtom)
const getSortablePinnedApps = useCallback(()=> { const setSettingsLocalLastUpdated = useSetRecoilState(
const pinnedAppsLocal = fetchFromLocalStorage('ext_saved_settings') settingsLocalLastUpdatedAtom
if(pinnedAppsLocal?.sortablePinnedApps){ );
setSortablePinnedApps(pinnedAppsLocal?.sortablePinnedApps)
setSettingsLocalLastUpdated(pinnedAppsLocal?.timestamp || -1) const setIsUsingImportExportSettings = useSetRecoilState(
} else { isUsingImportExportSettingsAtom
setSettingsLocalLastUpdated(-1) );
}
const setSettingsQDNLastUpdated = useSetRecoilState(
}, []) settingsQDNLastUpdatedAtom
const getSortablePinnedAppsImportExport = useCallback(()=> { );
const pinnedAppsLocal = fetchFromLocalStorage('ext_saved_settings_import_export')
if(pinnedAppsLocal?.sortablePinnedApps){ const setOldPinnedApps = useSetRecoilState(oldPinnedAppsAtom);
setOldPinnedApps(pinnedAppsLocal?.sortablePinnedApps)
const getSortablePinnedApps = useCallback(() => {
const pinnedAppsLocal = fetchFromLocalStorage('ext_saved_settings');
setIsUsingImportExportSettings(true)
setSettingsQDNLastUpdated(pinnedAppsLocal?.timestamp || 0) if (pinnedAppsLocal?.sortablePinnedApps) {
setSortablePinnedApps(pinnedAppsLocal?.sortablePinnedApps);
} else { setSettingsLocalLastUpdated(pinnedAppsLocal?.timestamp || -1);
setIsUsingImportExportSettings(false) } else {
} setSettingsLocalLastUpdated(-1);
}
}, []) }, []);
useEffect(()=> {
const getSortablePinnedAppsImportExport = useCallback(() => {
getSortablePinnedApps() const pinnedAppsLocal = fetchFromLocalStorage(
getSortablePinnedAppsImportExport() 'ext_saved_settings_import_export'
}, [getSortablePinnedApps, address]) );
if (pinnedAppsLocal?.sortablePinnedApps) {
} setOldPinnedApps(pinnedAppsLocal?.sortablePinnedApps);
setIsUsingImportExportSettings(true);
setSettingsQDNLastUpdated(pinnedAppsLocal?.timestamp || 0);
} else {
setIsUsingImportExportSettings(false);
}
}, []);
useEffect(() => {
getSortablePinnedApps();
getSortablePinnedAppsImportExport();
}, [getSortablePinnedApps, address]);
};