diff --git a/README.md b/README.md index 25f4e90..34cfbbb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Qortal Hub - Desktop Interface for Qortal -Qortal Hub is the newest interface for Qortal, part of the 'Qortal Trifecta' series of new User Interfaces for the platform/network. +Qortal Hub is the newest interface for Qortal, part of the 'Qortal Trifecta' series of new User Interfaces for the platform/network. It is likely that Qortal Hub will become the new 'primary interface' for Qortal, and that the primary development focus surrounding Qortal Interface development, will be focused here instead of the previous 'qortal-ui' repo. @@ -8,14 +8,20 @@ It is likely that Qortal Hub will become the new 'primary interface' for Qortal, Qortal Hub came along with the new Group Encryption methodologies applied, which provide **encrypted chat in Q-Chat for private groups.** Qortal Hub was the first to implement the new method of group encryption, which allows new users to see previously published data, unlike the previous group encryption methodology of things like 'threads' in Q-Mail. -Allowing new users to view older messages also comes along with a massive boost to the usability of the group encryption, and as such has been leveraged in multiple places inside Qortal Hub, Qortal Extension, and Qortal Go. +Allowing new users to view older messages also comes along with a massive boost to the usability of the group encryption, and as such has been leveraged in multiple places inside Qortal Hub, Qortal Extension, and Qortal Go. ## Ease of Use Expanded -Qortal Hub has a focus on ease of use for new users. Providing both the ability to utlilize Qortal without needing to run a local node (though running a local node is still the recommended method to access Qortal), and multiple built-in (QDN-published) walk-thru videos (by Qortal Justin) that explain the various basics of any given section of the application. This allows new users to 'jump right in' to utilizing Qortal Hub, and Qortal overall, in a much more streamlined fashion than that which was previously required by the 'legacy UI' (qortal-ui). +Qortal Hub has a focus on ease of use for new users. Providing both the ability to utlilize Qortal without needing to run a local node (though running a local node is still the recommended method to access Qortal), and multiple built-in (QDN-published) walk-thru videos (by Qortal Justin) that explain the various basics of any given section of the application. This allows new users to 'jump right in' to utilizing Qortal Hub, and Qortal overall, in a much more streamlined fashion than that which was previously required by the 'legacy UI' (qortal-ui). -Leveraging a redundant set of publicly accessible nodes provided by crowetic, Qortal Hub, Qortal Go, and Qortal Extension, all allow the use of Qortal without running a node, making it very simple to 'install and go' and start making use of the extensive functionality provided within the Qortal Ecosystem. +Leveraging a redundant set of publicly accessible nodes provided by crowetic, Qortal Hub, Qortal Go, and Qortal Extension, all allow the use of Qortal without running a node, making it very simple to 'install and go' and start making use of the extensive functionality provided within the Qortal Ecosystem. Many additional details and a fully featured wiki will be created over time. Reach out on the chat on https://qortal.dev or in any of the community locations for Qortal, if you have any issues. Thank you! +## Internationalization (i18n) +Qortal-Hub supports internationalization (i18n) using [i18next](https://www.i18next.com/), allowing seamless translation of UI text into multiple languages. +The setup includes modularized translation files, language detection, context and runtime language switching. +Files with translation are in `public/locales/` folder. + +See [guidelines](./docs/i18n_languages.md). diff --git a/docs/contribution.md b/docs/contribution.md new file mode 100644 index 0000000..07d06fe --- /dev/null +++ b/docs/contribution.md @@ -0,0 +1,84 @@ + +# 🤝 Contributing Guide + +Thank you for your interest in contributing! We follow a structured Git workflow to keep the project clean, stable, and production-ready at all times. + +--- + +## 📦 Branch Overview + +| Branch | Purpose | +|------------------|----------------------------------------------------------| +| `master` | Stable, production-ready code. All releases are tagged from here. | +| `develop` | Active development branch. All new features go here first. | +| `release/x.y.z` | Pre-release branch for staging, QA, and final polish. | + +--- + +## 🌿 Creating a Feature or Fix + +1. **Start from `develop`:** + + ```bash + git checkout develop + git checkout -b feature/your-feature-name + ``` + +2. **Make your changes and commit them.** + +3. **Push your branch:** + + ```bash + git push origin feature/your-feature-name + ``` + +4. **Open a Pull Request into `develop`.** + +--- + +## 🚀 Releasing Code (Maintainers Only) + +A new `release/x.y.z` branch must be created for **every release**. + +1. **Create a `release/` branch from `master`:** + + ```bash + git checkout master + git checkout -b release/1.2.0 + ``` + +2. **Merge in `develop` or selected branches if `develop` is not ready:** + + ```bash + git merge develop + # or + git merge feature/finished-feature + git merge feature/another-complete-feature + ``` + +3. **Polish, test, and fix issues as needed.** + +4. **Merge back into `develop`:** + + ```bash + git checkout develop + git merge release/1.2.0 + git push origin develop + ``` + +5. **Finalize the release:** + + ```bash + git checkout master + git merge release/1.2.0 + git tag v1.2.0 + git push origin master --tags + ``` + +6. **Delete the release branch:** + + ```bash + git branch -d release/1.2.0 + git push origin --delete release/1.2.0 + ``` + diff --git a/docs/i18n_languages.md b/docs/i18n_languages.md new file mode 100644 index 0000000..7447193 --- /dev/null +++ b/docs/i18n_languages.md @@ -0,0 +1,10 @@ +# I18N Guidelines + +In JSON file: + +- Keep the file sorted +- Always write in lowercase + +In GUI: + +- If the first letter of the translation must be uppercase, use the postProcess, for example: `{t_auth('advanced_users', { postProcess: 'capitalize' })}` diff --git a/i18n.js b/i18n.js new file mode 100644 index 0000000..78df3c1 --- /dev/null +++ b/i18n.js @@ -0,0 +1,49 @@ +import { initReactI18next } from 'react-i18next'; +import HttpBackend from 'i18next-http-backend'; +import LocalStorageBackend from 'i18next-localstorage-backend'; +import HttpApi from 'i18next-http-backend'; +import i18n from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; + +// Detect environment +const isDev = process.env.NODE_ENV === 'development'; + +// Register custom postProcessor: it capitalizes the first letter of a translation- +// Usage: +// t('greeting', { postProcess: 'capitalize' }) +const capitalize = { + type: 'postProcessor', + name: 'capitalize', + process: (value) => { + return value.charAt(0).toUpperCase() + value.slice(1); + }, +}; + +i18n + .use(HttpApi) + .use(LanguageDetector) + .use(initReactI18next) + .use(capitalize) + .init({ + backend: { + backends: [LocalStorageBackend, HttpBackend], + backendOptions: [ + { + expirationTime: 7 * 24 * 60 * 60 * 1000, // 7 days + }, + { + loadPath: '/locales/{{lng}}/{{ns}}.json', + }, + ], + }, + debug: isDev, + fallbackLng: 'en', + interpolation: { + escapeValue: false, + }, + lng: navigator.language, + ns: ['auth', 'core', 'tutorial'], + supportedLngs: ['en', 'it', 'es', 'fr', 'de', 'ru'], + }); + +export default i18n; diff --git a/package-lock.json b/package-lock.json index 5e2803b..071586f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,10 @@ "emoji-picker-react": "^4.12.0", "file-saver": "^2.0.5", "html-to-text": "^9.0.5", + "i18next": "^25.0.1", + "i18next-browser-languagedetector": "^8.0.5", + "i18next-http-backend": "^3.0.2", + "i18next-localstorage-backend": "^4.2.0", "jssha": "3.3.1", "lit": "^3.2.1", "lodash": "^4.17.21", @@ -69,6 +73,7 @@ "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-frame-component": "^5.2.7", + "react-i18next": "^15.4.1", "react-infinite-scroller": "^1.2.6", "react-intersection-observer": "^9.13.0", "react-json-view-lite": "^2.0.1", @@ -1479,9 +1484,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", - "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -8044,6 +8050,15 @@ "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==" }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -10680,6 +10695,15 @@ "node": ">=18" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-to-text": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", @@ -10774,6 +10798,64 @@ "ms": "^2.0.0" } }, + "node_modules/i18next": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.0.1.tgz", + "integrity": "sha512-8S8PyZbrymJZn3DaN70/34JYWNhsqrU6yA4MuzcygJBv+41dgNMocEA8h+kV1P7MCc1ll03lOTOIXE7mpNCicw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.10" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.5.tgz", + "integrity": "sha512-OstebRKqKiQw8xEvQF5aRyUujsCatanj7Q9eo5iiH2gJpoXGZ7483ol3sVBwfqbobTQPNH1J+NAyJ1aCQoEC+w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", + "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", + "license": "MIT", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, + "node_modules/i18next-localstorage-backend": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/i18next-localstorage-backend/-/i18next-localstorage-backend-4.2.0.tgz", + "integrity": "sha512-vglEQF0AnLriX7dLA2drHnqAYzHxnLwWQzBDw8YxcIDjOvYZz5rvpal59Dq4In+IHNmGNM32YgF0TDjBT0fHmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.15" + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -12891,6 +12973,48 @@ "semver": "^7.3.5" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-gyp": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", @@ -16533,6 +16657,28 @@ "react-dom": ">= 16.3" } }, + "node_modules/react-i18next": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.1.tgz", + "integrity": "sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-infinite-scroller": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/react-infinite-scroller/-/react-infinite-scroller-1.2.6.tgz", @@ -19462,6 +19608,15 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", diff --git a/package.json b/package.json index 7e08596..0060144 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,10 @@ "emoji-picker-react": "^4.12.0", "file-saver": "^2.0.5", "html-to-text": "^9.0.5", + "i18next": "^25.0.1", + "i18next-browser-languagedetector": "^8.0.5", + "i18next-http-backend": "^3.0.2", + "i18next-localstorage-backend": "^4.2.0", "jssha": "3.3.1", "lit": "^3.2.1", "lodash": "^4.17.21", @@ -74,6 +78,7 @@ "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-frame-component": "^5.2.7", + "react-i18next": "^15.4.1", "react-infinite-scroller": "^1.2.6", "react-intersection-observer": "^9.13.0", "react-json-view-lite": "^2.0.1", diff --git a/public/locales/de/auth.json b/public/locales/de/auth.json new file mode 100644 index 0000000..fc83d42 --- /dev/null +++ b/public/locales/de/auth.json @@ -0,0 +1,37 @@ +{ + "account": { + "your": "ihr Konto", + "account_many": "Konten", + "account_one": "Konto" + }, + "advanced_users": "für fortgeschrittene Benutzer", + "apikey": { + "alternative": "Alternative: Datei auswählen", + "change": "API-Schlüssel ändern", + "enter": "API-Schlüssel eingeben", + "import": "API-Schlüssel importieren", + "key": "API-Schlüssel", + "select_valid": "gültigen API-Schlüssel auswählen" + }, + "authenticate": "authentifizieren", + "build_version": "Build-Version", + "create_account": "Konto erstellen", + "download_account": "Konto herunterladen", + "keep_secure": "Bewahren Sie Ihre Kontodatei sicher auf", + "node": { + "choose": "benutzerdefinierten Node auswählen", + "custom_many": "benutzerdefinierte Nodes", + "use_custom": "benutzerdefinierten Node verwenden", + "use_local": "lokalen Node verwenden", + "using": "verwende Node", + "using_public": "öffentlichen Node verwenden" + }, + "password": "Passwort", + "password_confirmation": "Passwort bestätigen", + "return_to_list": "zurück zur Liste", + "wallet": { + "password_confirmation": "Wallet-Passwort bestätigen", + "password": "Wallet-Passwort" + }, + "welcome": "willkommen bei" +} diff --git a/public/locales/de/core.json b/public/locales/de/core.json new file mode 100644 index 0000000..c2b7a29 --- /dev/null +++ b/public/locales/de/core.json @@ -0,0 +1,71 @@ +{ + "add": "hinzufügen", + "cancel": "abbrechen", + "choose": "auswählen", + "close": "schließen", + "continue": "fortfahren", + "core": { + "block_height": "Blockhöhe", + "information": "Kerninformationen", + "peers": "verbundene Peers", + "version": "Kernversion" + }, + "description": "Beschreibung", + "edit": "bearbeiten", + "export": "exportieren", + "import": "importieren", + "last_height": "letzte Höhe", + "loading": "Lade...", + "logout": "abmelden", + "minting_status": "Präge-Status", + "payment_notification": "Zahlungsbenachrichtigung", + "price": "Preis", + "q_mail": "Q-Mail", + "result": { + "error": { + "generic": "Ein Fehler ist aufgetreten", + "incorrect_password": "Falsches Passwort", + "save_qdn": "Speichern in QDN nicht möglich" + }, + "status": { + "minting": "(Prägung)", + "not_minting": "(keine Prägung)", + "synchronized": "synchronisiert", + "synchronizing": "synchronisiere" + }, + "success": { + "publish_qdn": "Erfolgreich in QDN veröffentlicht" + } + }, + "save_options": { + "no_pinned_changes": "Derzeit keine Änderungen an Ihren angehefteten Apps", + "overwrite_changes": "Die App konnte Ihre vorhandenen in QDN gespeicherten angehefteten Apps nicht herunterladen. Möchten Sie diese Änderungen überschreiben?", + "overwrite_qdn": "In QDN überschreiben", + "publish_qdn": "Möchten Sie Ihre Einstellungen in QDN (verschlüsselt) veröffentlichen?", + "qdn": "QDN-Speicherung verwenden", + "register_name": "Sie benötigen einen registrierten Qortal-Namen, um Ihre angehefteten Apps in QDN zu speichern.", + "reset_pinned": "Gefällt Ihnen Ihre aktuellen lokalen Änderungen nicht? Möchten Sie zu den Standard-Anheftungen zurückkehren?", + "reset_qdn": "Gefällt Ihnen Ihre aktuellen lokalen Änderungen nicht? Möchten Sie zu Ihren in QDN gespeicherten Anheftungen zurückkehren?", + "revert_default": "Auf Standard zurücksetzen", + "revert_qdn": "Auf QDN zurücksetzen", + "save_qdn": "In QDN speichern", + "save": "speichern", + "settings": "Sie verwenden die Export/Import-Methode zum Speichern von Einstellungen.", + "unsaved_changes": "Sie haben nicht gespeicherte Änderungen an Ihren angehefteten Apps. Speichern Sie sie in QDN." + }, + "settings": "Einstellungen", + "supply": "Angebot", + "theme": { + "dark": "Dunkelmodus", + "light": "Hellmodus" + }, + "title": "Titel", + "tutorial": "Tutorial", + "user_lookup": "Benutzersuche", + "wallet": { + "backup_wallet": "Wallet sichern", + "wallet": "Wallet", + "wallet_other": "Wallets" + }, + "welcome": "Willkommen" +} diff --git a/public/locales/de/tutorial.json b/public/locales/de/tutorial.json new file mode 100644 index 0000000..14a7021 --- /dev/null +++ b/public/locales/de/tutorial.json @@ -0,0 +1,21 @@ +{ + "1_getting_started": "1. Erste Schritte", + "2_overview": "2. Überblick", + "3_groups": "3. Qortal-Gruppen", + "4_obtain_qort": "4. QORT erhalten", + "account_creation": "Kontoerstellung", + "important_info": "wichtige Informationen!", + "apps": { + "dashboard": "1. App-Dashboard", + "navigation": "2. App-Navigation" + }, + "initial": { + "6_qort": "mindestens 6 QORT im Wallet haben", + "explore": "erkunden", + "general_chat": "allgemeiner Chat", + "getting_started": "erste Schritte", + "register_name": "einen Namen registrieren", + "see_apps": "apps ansehen", + "trade_qort": "QORT handeln" + } +} diff --git a/public/locales/en/auth.json b/public/locales/en/auth.json new file mode 100644 index 0000000..26cab11 --- /dev/null +++ b/public/locales/en/auth.json @@ -0,0 +1,37 @@ +{ + "account": { + "your": "your account", + "account_many": "accounts", + "account_one": "account" + }, + "advanced_users": "for advanced users", + "apikey": { + "alternative": "alternative: File select", + "change": "change APIkey", + "enter": "enter APIkey", + "import": "import APIkey", + "key": "API key", + "select_valid": "select a valid apikey" + }, + "authenticate": "authenticate", + "build_version": "build version", + "create_account": "create account", + "download_account": "download account", + "keep_secure": "keep your account file secure", + "node": { + "choose": "choose custom node", + "custom_many": "custom nodes", + "use_custom": "use custom node", + "use_local": "use local node", + "using": "using node", + "using_public": "using public node" + }, + "password": "password", + "password_confirmation": "confirm password", + "return_to_list": "return to list", + "wallet": { + "password_confirmation": "confirm wallet password", + "password": "wallet password" + }, + "welcome": "welcome to" +} diff --git a/public/locales/en/core.json b/public/locales/en/core.json new file mode 100644 index 0000000..8988c4a --- /dev/null +++ b/public/locales/en/core.json @@ -0,0 +1,71 @@ +{ + "add": "add", + "cancel": "cancel", + "choose": "choose", + "close": "close", + "continue": "continue", + "core": { + "block_height": "block height", + "information": "core information", + "peers": "connected peers", + "version": "core version" + }, + "description": "description", + "edit": "edit", + "export": "export", + "import": "import", + "last_height": "last height", + "loading": "loading...", + "logout": "logout", + "minting_status": "minting status", + "payment_notification": "payment notification", + "price": "price", + "q_mail": "q-mail", + "result": { + "error": { + "generic": "an error occurred", + "incorrect_password": "incorrect password", + "save_qdn": "unable to save to QDN" + }, + "status": { + "minting": "(minting)", + "not_minting": "(not minting)", + "synchronized": "synchronized", + "synchronizing": "synchronizing" + }, + "success": { + "publish_qdn": "successfully published to QDN" + } + }, + "save_options": { + "no_pinned_changes": "you currently do not have any changes to your pinned apps", + "overwrite_changes": "the app was unable to download your existing QDN-saved pinned apps. Would you like to overwrite those changes?", + "overwrite_qdn": "overwrite to QDN", + "publish_qdn": "would you like to publish your settings to QDN (encrypted)?", + "qdn": "use QDN saving", + "register_name": "you need a registered Qortal name to save your pinned apps to QDN.", + "reset_pinned": "don't like your current local changes? Would you like to reset to the default pinned apps?", + "reset_qdn": "don't like your current local changes? Would you like to reset to your saved QDN pinned apps?", + "revert_default": "revert to default", + "revert_qdn": "revert to QDN", + "save_qdn": "save to QDN", + "save": "save", + "settings": "you are using the export/import way of saving settings.", + "unsaved_changes": " you have unsaved changes to your pinned apps. Save them to QDN." + }, + "settings": "settings", + "supply": "supply", + "theme": { + "dark": "dark mode", + "light": "light mode" + }, + "title": "title", + "tutorial": "tutorial", + "user_lookup": "user lookup", + "wallet": { + "backup_wallet": "backup wallet", + "wallet": "wallet", + "wallet_other": "wallets" + }, + "welcome": "welcome" +} diff --git a/public/locales/en/tutorial.json b/public/locales/en/tutorial.json new file mode 100644 index 0000000..08c7628 --- /dev/null +++ b/public/locales/en/tutorial.json @@ -0,0 +1,21 @@ +{ + "1_getting_started": "1. Getting Started", + "2_overview": "2. Overview", + "3_groups": "3. Qortal Groups", + "4_obtain_qort": "4. Obtaining Qort", + "account_creation": "account creation", + "important_info": "important information!", + "apps": { + "dashboard": "1. Apps Dashboard", + "navigation": "2. Apps Navigation" + }, + "initial": { + "6_qort": "have at least 6 QORT in your wallet", + "explore": "explore", + "general_chat": "general chat", + "getting_started": "getting started", + "register_name": "register a name", + "see_apps": "see apps", + "trade_qort": "trade QORT" + } +} diff --git a/public/locales/es/auth.json b/public/locales/es/auth.json new file mode 100644 index 0000000..3c62c24 --- /dev/null +++ b/public/locales/es/auth.json @@ -0,0 +1,37 @@ +{ + "account": { + "your": "tu cuenta", + "account_many": "cuentas", + "account_one": "cuenta" + }, + "advanced_users": "para usuarios avanzados", + "apikey": { + "alternative": "alternativa: Seleccionar archivo", + "change": "cambiar clave API", + "enter": "ingresar clave API", + "import": "importar clave API", + "key": "clave API", + "select_valid": "selecciona una clave API válida" + }, + "authenticate": "autenticar", + "build_version": "versión de compilación", + "create_account": "crear cuenta", + "download_account": "descargar cuenta", + "keep_secure": "mantén tu archivo de cuenta seguro", + "node": { + "choose": "elegir nodo personalizado", + "custom_many": "nodos personalizados", + "use_custom": "usar nodo personalizado", + "use_local": "usar nodo local", + "using": "usando nodo", + "using_public": "usando nodo público" + }, + "password": "contraseña", + "password_confirmation": "confirmar contraseña", + "return_to_list": "volver a la lista", + "wallet": { + "password_confirmation": "confirmar contraseña del monedero", + "password": "contraseña del monedero" + }, + "welcome": "bienvenido a" +} diff --git a/public/locales/es/core.json b/public/locales/es/core.json new file mode 100644 index 0000000..78b06e9 --- /dev/null +++ b/public/locales/es/core.json @@ -0,0 +1,71 @@ +{ + "add": "agregar", + "cancel": "cancelar", + "choose": "elegir", + "close": "cerrar", + "continue": "continuar", + "core": { + "block_height": "altura de bloque", + "information": "información del núcleo", + "peers": "pares conectados", + "version": "versión del núcleo" + }, + "description": "descripción", + "edit": "editar", + "export": "exportar", + "import": "importar", + "last_height": "última altura", + "loading": "cargando...", + "logout": "cerrar sesión", + "minting_status": "estado de acuñación", + "payment_notification": "notificación de pago", + "price": "precio", + "q_mail": "q-mail", + "result": { + "error": { + "generic": "ocurrió un error", + "incorrect_password": "contraseña incorrecta", + "save_qdn": "no se pudo guardar en QDN" + }, + "status": { + "minting": "(acuñando)", + "not_minting": "(no acuñando)", + "synchronized": "sincronizado", + "synchronizing": "sincronizando" + }, + "success": { + "publish_qdn": "publicado exitosamente en QDN" + } + }, + "save_options": { + "no_pinned_changes": "actualmente no tienes cambios en tus aplicaciones fijadas", + "overwrite_changes": "la aplicación no pudo descargar tus aplicaciones fijadas existentes guardadas en QDN. ¿Deseas sobrescribir esos cambios?", + "overwrite_qdn": "sobrescribir en QDN", + "publish_qdn": "¿Deseas publicar tus configuraciones en QDN (cifrado)?", + "qdn": "usar guardado en QDN", + "register_name": "necesitas un nombre Qortal registrado para guardar tus aplicaciones fijadas en QDN.", + "reset_pinned": "¿No te gustan tus cambios locales actuales? ¿Deseas restablecer las aplicaciones fijadas predeterminadas?", + "reset_qdn": "¿No te gustan tus cambios locales actuales? ¿Deseas restablecer tus aplicaciones fijadas guardadas en QDN?", + "revert_default": "restablecer a predeterminado", + "revert_qdn": "restablecer a QDN", + "save_qdn": "guardar en QDN", + "save": "guardar", + "settings": "estás utilizando el método de exportación/importación para guardar configuraciones.", + "unsaved_changes": "tienes cambios no guardados en tus aplicaciones fijadas. Guárdalos en QDN." + }, + "settings": "configuraciones", + "supply": "suministro", + "theme": { + "dark": "modo oscuro", + "light": "modo claro" + }, + "title": "título", + "tutorial": "tutorial", + "user_lookup": "búsqueda de usuario", + "wallet": { + "backup_wallet": "respaldar billetera", + "wallet": "billetera", + "wallet_other": "billeteras" + }, + "welcome": "bienvenido" +} diff --git a/public/locales/es/tutorial.json b/public/locales/es/tutorial.json new file mode 100644 index 0000000..c60e829 --- /dev/null +++ b/public/locales/es/tutorial.json @@ -0,0 +1,21 @@ +{ + "1_getting_started": "1. Comenzando", + "2_overview": "2. Visión general", + "3_groups": "3. Grupos de Qortal", + "4_obtain_qort": "4. Obtener QORT", + "account_creation": "creación de cuenta", + "important_info": "¡información importante!", + "apps": { + "dashboard": "1. Panel de aplicaciones", + "navigation": "2. Navegación de aplicaciones" + }, + "initial": { + "6_qort": "tener al menos 6 QORT en tu monedero", + "explore": "explorar", + "general_chat": "chat general", + "getting_started": "comenzando", + "register_name": "registrar un nombre", + "see_apps": "ver aplicaciones", + "trade_qort": "intercambiar QORT" + } +} diff --git a/public/locales/fr/auth.json b/public/locales/fr/auth.json new file mode 100644 index 0000000..06d08d5 --- /dev/null +++ b/public/locales/fr/auth.json @@ -0,0 +1,37 @@ +{ + "account": { + "your": "ton compte", + "account_many": "comptes", + "account_one": "compte" + }, + "advanced_users": "pour les utilisateurs avancés", + "apikey": { + "alternative": "alternative : Sélectionner un fichier", + "change": "changer la clé API", + "enter": "entrer la clé API", + "import": "importer la clé API", + "key": "clé API", + "select_valid": "sélectionnez une clé API valide" + }, + "authenticate": "authentifier", + "build_version": "version de build", + "create_account": "créer un compte", + "download_account": "télécharger le compte", + "keep_secure": "Gardez votre fichier de compte en sécurité", + "node": { + "choose": "choisir un nœud personnalisé", + "custom_many": "nœuds personnalisés", + "use_custom": "utiliser un nœud personnalisé", + "use_local": "utiliser un nœud local", + "using": "utilise le nœud", + "using_public": "utilise un nœud public" + }, + "password": "mot de passe", + "password_confirmation": "confirmer le mot de passe", + "return_to_list": "retour à la liste", + "wallet": { + "password_confirmation": "confirmer le mot de passe du portefeuille", + "password": "mot de passe du portefeuille" + }, + "welcome": "bienvenue sur" +} diff --git a/public/locales/fr/core.json b/public/locales/fr/core.json new file mode 100644 index 0000000..61d231d --- /dev/null +++ b/public/locales/fr/core.json @@ -0,0 +1,71 @@ +{ + "add": "ajouter", + "cancel": "annuler", + "choose": "choisir", + "close": "fermer", + "continue": "continuer", + "core": { + "block_height": "hauteur de bloc", + "information": "informations du noyau", + "peers": "pairs connectés", + "version": "version du noyau" + }, + "description": "description", + "edit": "éditer", + "export": "exporter", + "import": "importer", + "last_height": "dernière hauteur", + "loading": "chargement...", + "logout": "se déconnecter", + "minting_status": "statut de frappe", + "payment_notification": "notification de paiement", + "price": "prix", + "q_mail": "q-mail", + "result": { + "error": { + "generic": "une erreur s'est produite", + "incorrect_password": "mot de passe incorrect", + "save_qdn": "impossible d'enregistrer dans QDN" + }, + "status": { + "minting": "(frappe en cours)", + "not_minting": "(pas de frappe)", + "synchronized": "synchronisé", + "synchronizing": "synchronisation en cours" + }, + "success": { + "publish_qdn": "publié avec succès dans QDN" + } + }, + "save_options": { + "no_pinned_changes": "vous n'avez actuellement aucune modification de vos applications épinglées", + "overwrite_changes": "l'application n'a pas pu télécharger vos applications épinglées existantes enregistrées dans QDN. Voulez-vous écraser ces modifications ?", + "overwrite_qdn": "écraser dans QDN", + "publish_qdn": "souhaitez-vous publier vos paramètres dans QDN (chiffré) ?", + "qdn": "utiliser l'enregistrement QDN", + "register_name": "vous devez avoir un nom Qortal enregistré pour enregistrer vos applications épinglées dans QDN.", + "reset_pinned": "vous n'aimez pas vos modifications locales actuelles ? Voulez-vous réinitialiser les applications épinglées par défaut ?", + "reset_qdn": "vous n'aimez pas vos modifications locales actuelles ? Voulez-vous réinitialiser vos applications épinglées enregistrées dans QDN ?", + "revert_default": "revenir aux paramètres par défaut", + "revert_qdn": "revenir à QDN", + "save_qdn": "enregistrer dans QDN", + "save": "enregistrer", + "settings": "vous utilisez la méthode d'exportation/importation pour enregistrer les paramètres.", + "unsaved_changes": "vous avez des modifications non enregistrées de vos applications épinglées. Enregistrez-les dans QDN." + }, + "settings": "paramètres", + "supply": "approvisionnement", + "theme": { + "dark": "mode sombre", + "light": "mode clair" + }, + "title": "titre", + "tutorial": "tutoriel", + "user_lookup": "recherche d'utilisateur", + "wallet": { + "backup_wallet": "sauvegarder le portefeuille", + "wallet": "portefeuille", + "wallet_other": "portefeuilles" + }, + "welcome": "bienvenue" +} diff --git a/public/locales/fr/tutorial.json b/public/locales/fr/tutorial.json new file mode 100644 index 0000000..0d3ef04 --- /dev/null +++ b/public/locales/fr/tutorial.json @@ -0,0 +1,21 @@ +{ + "1_getting_started": "1. Démarrer", + "2_overview": "2. Aperçu", + "3_groups": "3. Groupes Qortal", + "4_obtain_qort": "4. Obtenir des QORT", + "account_creation": "création de compte", + "important_info": "informations importantes !", + "apps": { + "dashboard": "1. Tableau de bord des applications", + "navigation": "2. Navigation des applications" + }, + "initial": { + "6_qort": "avoir au moins 6 QORT dans votre portefeuille", + "explore": "explorer", + "general_chat": "chat général", + "getting_started": "démarrer", + "register_name": "enregistrer un nom", + "see_apps": "voir les applications", + "trade_qort": "échanger des QORT" + } +} diff --git a/public/locales/it/auth.json b/public/locales/it/auth.json new file mode 100644 index 0000000..345c154 --- /dev/null +++ b/public/locales/it/auth.json @@ -0,0 +1,37 @@ +{ + "account": { + "your": "il tuo account", + "account_many": "account", + "account_one": "account" + }, + "advanced_users": "per utenti avanzati", + "apikey": { + "alternative": "alternativa: seleziona un file", + "change": "cambia la chiave API", + "enter": "inserisci la chiave API", + "import": "importa chiave API", + "key": "chiave API", + "select_valid": "selezione una chiave API valida" + }, + "authenticate": "autenticazione", + "build_version": "versione build", + "create_account": "crea un account", + "download_account": "scarica account", + "keep_secure": "metti al sicuro il file del tuo account", + "node": { + "choose": "scegli un nodo custom", + "custom_many": "nodi custom", + "use_custom": "use nodo custom", + "use_local": "usa nodo locale", + "using": "nodo in uso", + "using_public": "utilizzo nodo pubblico" + }, + "password_confirmation": "confirma la password", + "password": "password", + "wallet": { + "password_confirmation": "conferma la password del wallet", + "password": "password del wallet" + }, + "return_to_list": "ritorna alla lista", + "welcome": "benvenuto in" +} diff --git a/public/locales/it/core.json b/public/locales/it/core.json new file mode 100644 index 0000000..8a6c4a2 --- /dev/null +++ b/public/locales/it/core.json @@ -0,0 +1,71 @@ +{ + "add": "aggiungi", + "cancel": "annulla", + "choose": "scegli", + "close": "chiudi", + "continue": "continua", + "core": { + "block_height": "altezza del blocco", + "information": "informazioni core", + "peers": "peer connessi", + "version": "versione core" + }, + "description": "descrizione", + "edit": "modifica", + "export": "esporta", + "import": "importa", + "last_height": "ultima altezza", + "loading": "caricamento...", + "logout": "disconnetti", + "minting_status": "stato del conio", + "payment_notification": "notifica di pagamento", + "price": "prezzo", + "q_mail": "q-mail", + "result": { + "error": { + "generic": "si è verificato un errore", + "incorrect_password": "password errata", + "save_qdn": "impossibile salvare su QDN" + }, + "status": { + "minting": "(conio in corso)", + "not_minting": "(conio non attivo)", + "synchronized": "sincronizzato", + "synchronizing": "sincronizzazione in corso" + }, + "success": { + "publish_qdn": "pubblicato con successo su QDN" + } + }, + "save_options": { + "no_pinned_changes": "attualmente non hai modifiche alle tue app appuntate", + "overwrite_changes": "l'app non è riuscita a scaricare le tue app appuntate salvate su QDN. Vuoi sovrascrivere queste modifiche?", + "overwrite_qdn": "sovrascrivi su QDN", + "publish_qdn": "vuoi pubblicare le tue impostazioni su QDN (crittografate)?", + "qdn": "usa il salvataggio QDN", + "register_name": "hai bisogno di un nome Qortal registrato per salvare le tue app appuntate su QDN.", + "reset_pinned": "non ti piacciono le modifiche locali attuali? Vuoi ripristinare le app appuntate predefinite?", + "reset_qdn": "non ti piacciono le modifiche locali attuali? Vuoi ripristinare le tue app appuntate salvate su QDN?", + "revert_default": "ripristina predefinite", + "revert_qdn": "ripristina da QDN", + "save_qdn": "salva su QDN", + "save": "salva", + "settings": "stai utilizzando il metodo esporta/importa per salvare le impostazioni.", + "unsaved_changes": "hai modifiche non salvate alle tue app appuntate. Salvale su QDN." + }, + "settings": "impostazioni", + "supply": "offerta", + "theme": { + "dark": "modalità scura", + "light": "modalità chiara" + }, + "title": "titolo", + "tutorial": "tutorial", + "user_lookup": "ricerca utente", + "wallet": { + "backup_wallet": "backup portafoglio", + "wallet": "portafoglio", + "wallet_other": "portafogli" + }, + "welcome": "benvenuto" +} diff --git a/public/locales/it/tutorial.json b/public/locales/it/tutorial.json new file mode 100644 index 0000000..511b7cb --- /dev/null +++ b/public/locales/it/tutorial.json @@ -0,0 +1,21 @@ +{ + "1_getting_started": "1. Come iniziare", + "2_overview": "2. Overview", + "3_groups": "3. I gruppi in Qortal", + "4_obtain_qort": "4. Ottenere Qort", + "account_creation": "creazione account", + "important_info": "important information!", + "apps": { + "dashboard": "1. Dashboard delle app", + "navigation": "2. Navigation tra le app" + }, + "initial": { + "6_qort": "avere almeno 6 QORT nel proprio wallet", + "explore": "esplora", + "general_chat": "chat generale", + "getting_started": "come iniziare", + "register_name": "registra un nome", + "see_apps": "vedi le apps", + "trade_qort": "scambia i QORT" + } +} diff --git a/public/locales/ru/auth.json b/public/locales/ru/auth.json new file mode 100644 index 0000000..3ae2327 --- /dev/null +++ b/public/locales/ru/auth.json @@ -0,0 +1,37 @@ +{ + "account": { + "your": "Ваш аккаунт", + "account_many": "аккаунты", + "account_one": "аккаунт" + }, + "advanced_users": "для продвинутых пользователей", + "apikey": { + "alternative": "альтернатива: выбрать файл", + "change": "изменить API-ключ", + "enter": "введите API-ключ", + "import": "импортировать API-ключ", + "key": "API-ключ", + "select_valid": "выберите действительный API-ключ" + }, + "authenticate": "аутентификация", + "build_version": "версия сборки", + "create_account": "создать аккаунт", + "download_account": "скачать аккаунт", + "keep_secure": "Храните файл аккаунта в безопасности", + "node": { + "choose": "выбрать пользовательский узел", + "custom_many": "пользовательские узлы", + "use_custom": "использовать пользовательский узел", + "use_local": "использовать локальный узел", + "using": "используется узел", + "using_public": "используется публичный узел" + }, + "password": "пароль", + "password_confirmation": "подтвердите пароль", + "return_to_list": "вернуться к списку", + "wallet": { + "password_confirmation": "подтвердите пароль кошелька", + "password": "пароль кошелька" + }, + "welcome": "добро пожаловать в" +} diff --git a/public/locales/ru/core.json b/public/locales/ru/core.json new file mode 100644 index 0000000..04bf898 --- /dev/null +++ b/public/locales/ru/core.json @@ -0,0 +1,71 @@ +{ + "add": "добавить", + "cancel": "отмена", + "choose": "выбрать", + "close": "закрыть", + "continue": "продолжить", + "core": { + "block_height": "высота блока", + "information": "информация ядра", + "peers": "подключенные узлы", + "version": "версия ядра" + }, + "description": "описание", + "edit": "редактировать", + "export": "экспорт", + "import": "импорт", + "last_height": "последняя высота", + "loading": "загрузка...", + "logout": "выйти", + "minting_status": "статус чеканки", + "payment_notification": "уведомление о платеже", + "price": "цена", + "q_mail": "q-mail", + "result": { + "error": { + "generic": "произошла ошибка", + "incorrect_password": "неверный пароль", + "save_qdn": "не удалось сохранить в QDN" + }, + "status": { + "minting": "(чеканка)", + "not_minting": "(не чеканится)", + "synchronized": "синхронизировано", + "synchronizing": "синхронизация" + }, + "success": { + "publish_qdn": "успешно опубликовано в QDN" + } + }, + "save_options": { + "no_pinned_changes": "у вас нет изменений в закреплённых приложениях", + "overwrite_changes": "приложению не удалось загрузить ваши закреплённые приложения, сохранённые в QDN. Хотите перезаписать эти изменения?", + "overwrite_qdn": "перезаписать в QDN", + "publish_qdn": "хотите опубликовать свои настройки в QDN (зашифровано)?", + "qdn": "использовать сохранение в QDN", + "register_name": "вам необходимо зарегистрированное имя Qortal для сохранения закреплённых приложений в QDN.", + "reset_pinned": "не устраивают текущие локальные изменения? Хотите сбросить до приложений по умолчанию?", + "reset_qdn": "не устраивают текущие локальные изменения? Хотите сбросить до сохранённых в QDN приложений?", + "revert_default": "сбросить до стандартных", + "revert_qdn": "сбросить до QDN", + "save_qdn": "сохранить в QDN", + "save": "сохранить", + "settings": "вы используете метод экспорта/импорта для сохранения настроек.", + "unsaved_changes": "у вас есть несохранённые изменения в закреплённых приложениях. Сохраните их в QDN." + }, + "settings": "настройки", + "supply": "предложение", + "theme": { + "dark": "тёмная тема", + "light": "светлая тема" + }, + "title": "заголовок", + "tutorial": "учебник", + "user_lookup": "поиск пользователя", + "wallet": { + "backup_wallet": "резервная копия кошелька", + "wallet": "кошелёк", + "wallet_other": "кошельки" + }, + "welcome": "добро пожаловать" +} diff --git a/public/locales/ru/tutorial.json b/public/locales/ru/tutorial.json new file mode 100644 index 0000000..405fdd0 --- /dev/null +++ b/public/locales/ru/tutorial.json @@ -0,0 +1,21 @@ +{ + "1_getting_started": "1. Начало работы", + "2_overview": "2. Обзор", + "3_groups": "3. Группы Qortal", + "4_obtain_qort": "4. Получение QORT", + "account_creation": "создание аккаунта", + "important_info": "важная информация!", + "apps": { + "dashboard": "1. Панель приложений", + "navigation": "2. Навигация по приложениям" + }, + "initial": { + "6_qort": "иметь не менее 6 QORT в кошельке", + "explore": "исследовать", + "general_chat": "общий чат", + "getting_started": "начало работы", + "register_name": "зарегистрировать имя", + "see_apps": "посмотреть приложения", + "trade_qort": "обмен QORT" + } +} diff --git a/src/App.tsx b/src/App.tsx index 7ce75de..ba57d99 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -136,6 +136,7 @@ import { QortPayment } from './components/QortPayment'; import { GeneralNotifications } from './components/GeneralNotifications'; import { PdfViewer } from './common/PdfViewer'; import ThemeSelector from './components/Theme/ThemeSelector.tsx'; +import { useTranslation } from 'react-i18next'; type extStates = | 'not-authenticated' @@ -316,6 +317,8 @@ function App() { const [sendqortState, setSendqortState] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isLoadingSendCoin, setIsLoadingSendCoin] = useState(false); + + const { t } = useTranslation(['auth', 'core']); const theme = useTheme(); const [ @@ -1550,9 +1553,10 @@ function App() { style={{ fontSize: '14px', fontWeight: 700, + textTransform: 'uppercase', }} > - LOGOUT + {t('core:logout')} } placement="left" @@ -1589,9 +1593,10 @@ function App() { style={{ fontSize: '14px', fontWeight: 700, + textTransform: 'uppercase', }} > - SETTINGS + {t('core:settings')} } placement="left" @@ -1628,9 +1633,10 @@ function App() { style={{ fontSize: '14px', fontWeight: 700, + textTransform: 'uppercase', }} > - USER LOOKUP + {t('core:user_lookup')} } placement="left" @@ -1667,9 +1673,10 @@ function App() { style={{ fontSize: '14px', fontWeight: 700, + textTransform: 'uppercase', }} > - WALLETS + {t('core:wallet_other')} } placement="left" @@ -1703,9 +1710,10 @@ function App() { style={{ fontSize: '14px', fontWeight: 700, + textTransform: 'uppercase', }} > - YOUR ACCOUNT + {t('auth:account.your')} } placement="left" @@ -1813,9 +1821,10 @@ function App() { style={{ fontSize: '14px', fontWeight: 700, + textTransform: 'uppercase', }} > - MINTING STATUS + {t('core:minting_status')} } placement="left" @@ -1857,9 +1866,10 @@ function App() { style={{ fontSize: '14px', fontWeight: 700, + textTransform: 'uppercase', }} > - TUTORIAL + {t('core:tutorial')} } placement="left" @@ -1894,8 +1904,14 @@ function App() { > - BACKUP WALLET + + {t('core:backup_wallet')} } placement="left" @@ -2083,6 +2099,7 @@ function App() { {messageQortalRequest?.text1} + {messageQortalRequest?.text2 && ( <> @@ -2124,6 +2141,7 @@ function App() { > {messageQortalRequest?.text3} + @@ -2158,11 +2176,11 @@ function App() { {messageQortalRequest?.highlightedText} @@ -2379,17 +2397,7 @@ function App() { > {sendqortState?.amount} QORT - {/* - - Confirm Wallet Password - - - setPaymentPassword(e.target.value)} - /> */} - Create account + {t('auth:create_account', { postProcess: 'capitalize' })} )} @@ -2624,7 +2632,7 @@ function App() { fontWeight: 600, }} > - Authenticate + {t('auth:authenticate', { postProcess: 'capitalize' })} @@ -2632,7 +2640,7 @@ function App() { <> - Wallet Password + {t('auth:wallet.password', { postProcess: 'capitalize' })} @@ -2656,7 +2664,8 @@ function App() { fontSize: '12px', }} > - {'Using node: '} {currentNode?.url} + {t('auth:node.using', { postProcess: 'capitalize' })}:{' '} + {currentNode?.url} ) : ( @@ -2667,7 +2676,7 @@ function App() { fontSize: '12px', }} > - {'Using public node'} + {t('auth:node.using_public', { postProcess: 'capitalize' })} )} @@ -2675,7 +2684,7 @@ function App() { - Authenticate + {t('auth:authenticate', { postProcess: 'capitalize' })} {walletToBeDecryptedError} @@ -2703,7 +2712,9 @@ function App() { onClick={returnToMain} /> + +
- Download Account + {t('auth:download_account', { postProcess: 'capitalize' })} @@ -2740,9 +2751,13 @@ function App() { {!walletToBeDownloaded && ( <> - Confirm Wallet Password + {t('auth:wallet.password_confirmation', { + postProcess: 'capitalize', + })} + + + + - Confirm password + {t('auth:password_confirmation', { + postProcess: 'capitalize', + })} {walletToBeDownloadedError} @@ -2764,11 +2783,15 @@ function App() { onClick={async () => { await saveFileToDiskFunc(); await showInfo({ - message: `Keep your account file secure.`, + message: t('auth:keep_secure', { + postProcess: 'capitalize', + }), }); }} > - Download account + {t('auth:download_account', { + postProcess: 'capitalize', + })} )} @@ -3024,7 +3047,7 @@ function App() { - Create Account + {t('auth:create_account', { postProcess: 'capitalize' })} {walletToBeDownloadedError} diff --git a/src/ExtStates/NotAuthenticated.tsx b/src/ExtStates/NotAuthenticated.tsx index 017e7ae..28892a9 100644 --- a/src/ExtStates/NotAuthenticated.tsx +++ b/src/ExtStates/NotAuthenticated.tsx @@ -30,6 +30,7 @@ import { cleanUrl, gateways } from '../background'; import { GlobalContext } from '../App'; import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; import ThemeSelector from '../components/Theme/ThemeSelector'; +import { useTranslation } from 'react-i18next'; const manifestData = { version: '0.5.3', @@ -39,13 +40,14 @@ export const HtmlTooltip = styled(({ className, ...props }: TooltipProps) => ( ))(({ theme }) => ({ [`& .${tooltipClasses.tooltip}`]: { - backgroundColor: '#232428', - color: 'white', + backgroundColor: theme.palette.background.paper, + color: theme.palette.text.primary, maxWidth: 320, padding: '20px', fontSize: theme.typography.pxToRem(12), }, })); + function removeTrailingSlash(url) { return url.replace(/\/+$/, ''); } @@ -84,6 +86,7 @@ export const NotAuthenticated = ({ React.useState(null); const { showTutorial, hasSeenGettingStarted } = useContext(GlobalContext); const theme = useTheme(); + const { t } = useTranslation(['auth', 'core']); const importedApiKeyRef = useRef(null); const currentNodeRef = useRef(null); @@ -183,6 +186,7 @@ export const NotAuthenticated = ({ useEffect(() => { importedApiKeyRef.current = importedApiKey; }, [importedApiKey]); + useEffect(() => { currentNodeRef.current = currentNode; }, [currentNode]); @@ -309,6 +313,7 @@ export const NotAuthenticated = ({ } else if (currentNodeRef.current) { payload = currentNodeRef.current; } + let isValid = false; const url = `${payload?.url}/admin/settings/localAuthBypassEnabled`; @@ -345,7 +350,7 @@ export const NotAuthenticated = ({ .catch((error) => { console.error( 'Failed to set API key:', - error.message || 'An error occurred' + error.message || t('core:error', { postProcess: 'capitalize' }) ); }); } else { @@ -354,7 +359,9 @@ export const NotAuthenticated = ({ if (!fromStartUp) { setInfoSnack({ type: 'error', - message: 'Select a valid apikey', + message: t('auth:apikey.select_valid', { + postProcess: 'capitalize', + }), }); setOpenSnack(true); } @@ -377,7 +384,10 @@ export const NotAuthenticated = ({ .catch((error) => { console.error( 'Failed to set API key:', - error.message || 'An error occurred' + error.message || + t('core:error', { + postProcess: 'capitalize', + }) ); }); return; @@ -385,7 +395,11 @@ export const NotAuthenticated = ({ if (!fromStartUp) { setInfoSnack({ type: 'error', - message: error?.message || 'Select a valid apikey', + message: + error?.message || + t('auth:apikey.select_valid', { + postProcess: 'capitalize', + }), }); setOpenSnack(true); } @@ -402,6 +416,7 @@ export const NotAuthenticated = ({ const addCustomNode = () => { setMode('add-node'); }; + const saveCustomNodes = (myNodes, isFullListOfNodes) => { let nodes = [...(myNodes || [])]; if (!isFullListOfNodes && customNodeToSaveIndex !== null) { @@ -455,7 +470,9 @@ export const NotAuthenticated = ({ >
+ + - WELCOME TO + {t('auth:welcome', { postProcess: 'capitalize' })} + {' '} + // TODO translate } > setExtstate('wallets')}> - {/* */} - Accounts + {t('auth:account.account_many', { postProcess: 'capitalize' })} - {/* - - */} @@ -534,7 +548,8 @@ export const NotAuthenticated = ({ }} > New users start here! - + {' '} + // TODO translate + {' '} + // TODO translate } > @@ -565,10 +581,11 @@ export const NotAuthenticated = ({ }, }} > - Create account + {t('auth:create_account', { postProcess: 'capitalize' })} + - {'Using node: '} {currentNode?.url} + {t('auth:node.using', { postProcess: 'capitalize' })}:{' '} + {currentNode?.url} + <> - For advanced users + {t('auth:advanced_users', { postProcess: 'capitalize' })} } - label={`Use ${isLocal ? 'Local' : 'Custom'} Node`} + label={ + isLocal + ? t('auth:node.use_local', { postProcess: 'capitalize' }) + : t('auth:node.use_custom', { postProcess: 'capitalize' }) + } /> {currentNode?.url === 'http://127.0.0.1:12391' && ( @@ -670,14 +693,19 @@ export const NotAuthenticated = ({ variant="contained" component="label" > - {apiKey ? 'Change ' : 'Import '} apikey + {apiKey + ? t('auth:node.use_local', { postProcess: 'capitalize' }) + : t('auth:apikey.import', { postProcess: 'capitalize' })} {`api key : ${importedApiKey}`} + > + {t('auth:apikey.key', { postProcess: 'capitalize' })}: $ + {importedApiKey} + )} - Build version: {manifestData?.version} + {t('auth:build_version', { postProcess: 'capitalize' })}: + {manifestData?.version} + - {'Custom nodes'} + + {' '} + {t('auth:node.custom_many', { postProcess: 'capitalize' })}: + @@ -783,7 +816,7 @@ export const NotAuthenticated = ({ }} variant="contained" > - Choose + {t('core:choose', { postProcess: 'capitalize' })} @@ -799,7 +832,7 @@ export const NotAuthenticated = ({ > @@ -842,8 +875,9 @@ export const NotAuthenticated = ({ }} variant="contained" > - Choose + {t('core:choose', { postProcess: 'capitalize' })} + + @@ -902,7 +936,14 @@ export const NotAuthenticated = ({ )} + + {mode === 'list' && ( + + )} + {mode === 'list' && ( <> )} - {mode === 'list' && ( - - )} {mode === 'add-node' && ( <> @@ -931,7 +967,7 @@ export const NotAuthenticated = ({ setCustomNodeToSaveIndex(null); }} > - Return to list + {t('auth:return_to_list', { postProcess: 'capitalize' })} )} @@ -954,7 +990,9 @@ export const NotAuthenticated = ({ aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" > - {'Enter apikey'} + + {t('auth:apikey.enter', { postProcess: 'capitalize' })} + - Alternative: File select + {t('auth:apikey.alternative', { postProcess: 'capitalize' })} - - + + + diff --git a/src/components/Apps/AppInfo.tsx b/src/components/Apps/AppInfo.tsx index 53a155a..f9e72b3 100644 --- a/src/components/Apps/AppInfo.tsx +++ b/src/components/Apps/AppInfo.tsx @@ -1,3 +1,4 @@ +import React, { useEffect, useMemo, useState } from 'react'; import { AppCircle, AppCircleContainer, diff --git a/src/components/Apps/AppInfoSnippet.tsx b/src/components/Apps/AppInfoSnippet.tsx index c877bba..dfe4ea6 100644 --- a/src/components/Apps/AppInfoSnippet.tsx +++ b/src/components/Apps/AppInfoSnippet.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { AppCircle, AppCircleContainer, diff --git a/src/components/CoreSyncStatus.tsx b/src/components/CoreSyncStatus.tsx index 585b2e0..f77580c 100644 --- a/src/components/CoreSyncStatus.tsx +++ b/src/components/CoreSyncStatus.tsx @@ -5,11 +5,14 @@ import syncingImg from '../assets/syncStatus/syncing.png'; import { getBaseApiReact } from '../App'; import '../styles/CoreSyncStatus.css'; import { useTheme } from '@mui/material'; +import { useTranslation } from 'react-i18next'; export const CoreSyncStatus = () => { const [nodeInfos, setNodeInfos] = useState({}); const [coreInfos, setCoreInfos] = useState({}); const [isUsingGateway, setIsUsingGateway] = useState(false); + + const { t } = useTranslation(['auth', 'core']); const theme = useTheme(); useEffect(() => { @@ -72,25 +75,25 @@ export const CoreSyncStatus = () => { : ''; let imagePath = syncingImg; - let message = `Synchronizing`; + let message = t('core:status.synchronizing', { postProcess: 'capitalize' }); if (isMintingPossible && !isUsingGateway) { imagePath = syncedMintingImg; - message = `${isSynchronizing ? 'Synchronizing' : 'Synchronized'} ${'(Minting)'}`; + message = `${t(`core:result.status.${isSynchronizing ? 'synchronizing' : 'synchronized'}`, { postProcess: 'capitalize' })} ${t('core:result.status.minting')}`; } else if (isSynchronizing === true && syncPercent === 99) { imagePath = syncingImg; } else if (isSynchronizing && !isMintingPossible && syncPercent === 100) { imagePath = syncingImg; - message = `Synchronizing ${isUsingGateway ? '' : '(Not Minting)'}`; + message = `${t('core:result.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.not_minting') : ''}`; } else if (!isSynchronizing && !isMintingPossible && syncPercent === 100) { imagePath = syncedImg; - message = `Synchronized ${isUsingGateway ? '' : '(Not Minting)'}`; + message = `${t('core:result.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.not_minting') : ''}`; } else if (isSynchronizing && isMintingPossible && syncPercent === 100) { imagePath = syncingImg; - message = `Synchronizing ${isUsingGateway ? '' : '(Minting)'}`; + message = `${t('core:result.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.minting') : ''}`; } else if (!isSynchronizing && isMintingPossible && syncPercent === 100) { imagePath = syncedMintingImg; - message = `Synchronized ${isUsingGateway ? '' : '(Minting)'}`; + message = `${t('core:result.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.minting') : ''}`; } return ( @@ -115,24 +118,24 @@ export const CoreSyncStatus = () => { top: '10px', }} > -

Core Information

+

{t('core:core.information', { postProcess: 'capitalize' })}

- Core Version:{' '} + {t('core:core.version', { postProcess: 'capitalize' })}:{' '} {buildVersion}

{message}

- Block Height:{' '} + {t('core:core.block_height', { postProcess: 'capitalize' })}:{' '} {height || ''}

- Connected Peers:{' '} + {t('core:core.peers', { postProcess: 'capitalize' })}:{' '} {numberOfConnections || ''}

- Using public node:{' '} + {t('auth:node.using_public', { postProcess: 'capitalize' })}:{' '} {isUsingGateway?.toString()} diff --git a/src/components/Explore/Explore.tsx b/src/components/Explore/Explore.tsx index 61037d7..ae1988e 100644 --- a/src/components/Explore/Explore.tsx +++ b/src/components/Explore/Explore.tsx @@ -1,129 +1,130 @@ -import { Box, ButtonBase, Typography } from "@mui/material"; -import React from "react"; -import ChatIcon from "@mui/icons-material/Chat"; -import qTradeLogo from "../../assets/Icons/q-trade-logo.webp"; -import AppsIcon from "@mui/icons-material/Apps"; -import { executeEvent } from "../../utils/events"; +import { Box, ButtonBase, Typography, useTheme } from '@mui/material'; +import ChatIcon from '@mui/icons-material/Chat'; +import qTradeLogo from '../../assets/Icons/q-trade-logo.webp'; +import AppsIcon from '@mui/icons-material/Apps'; +import { executeEvent } from '../../utils/events'; import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; +import { useTranslation } from 'react-i18next'; +export const Explore = ({ setDesktopViewMode }) => { + const theme = useTheme(); + const { t } = useTranslation(['core', 'tutorial']); -export const Explore = ({setDesktopViewMode}) => { return ( { - executeEvent("addTab", { - data: { service: "APP", name: "q-trade" }, - }); - executeEvent("open-apps-mode", {}); - }} + executeEvent('addTab', { + data: { service: 'APP', name: 'q-trade' }, + }); + executeEvent('open-apps-mode', {}); + }} > - Trade QORT + {t('tutorial:initial.trade_qort', { postProcess: 'capitalize' })} + { + setDesktopViewMode('apps'); }} - onClick={()=> { - setDesktopViewMode('apps') - - }} > - See Apps + {t('tutorial:initial.see_apps', { postProcess: 'capitalize' })} + { - executeEvent("openGroupMessage", { - from: "0" , - }); - }} + executeEvent('openGroupMessage', { + from: '0', + }); + }} > - General Chat + {t('tutorial:initial.general_chat', { postProcess: 'capitalize' })} { - executeEvent("openWalletsApp", { - - }); - }} + executeEvent('openWalletsApp', {}); + }} > - Wallets + {t('core:wallet_other', { postProcess: 'capitalize' })} diff --git a/src/components/GeneralNotifications.tsx b/src/components/GeneralNotifications.tsx index 97c498b..6335dbf 100644 --- a/src/components/GeneralNotifications.tsx +++ b/src/components/GeneralNotifications.tsx @@ -14,6 +14,7 @@ import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; import { formatDate } from '../utils/time'; import { useHandlePaymentNotification } from '../hooks/useHandlePaymentNotification'; import { executeEvent } from '../utils/events'; +import { useTranslation } from 'react-i18next'; export const GeneralNotifications = ({ address }) => { const [anchorEl, setAnchorEl] = useState(null); @@ -31,6 +32,7 @@ export const GeneralNotifications = ({ address }) => { setAnchorEl(event.currentTarget); }; + const { t } = useTranslation(['core']); const theme = useTheme(); return ( @@ -48,9 +50,10 @@ export const GeneralNotifications = ({ address }) => { color: theme.palette.text.primary, fontSize: '14px', fontWeight: 700, + textTransform: 'uppercase', }} > - PAYMENT NOTIFICATION + {t('core:payment_notification')} } placement="left" diff --git a/src/components/Group/AddGroup.tsx b/src/components/Group/AddGroup.tsx index 470cd3f..91552cb 100644 --- a/src/components/Group/AddGroup.tsx +++ b/src/components/Group/AddGroup.tsx @@ -91,7 +91,7 @@ export const AddGroup = ({ address, open, setOpen }) => { if (!name) throw new Error('Please provide a name'); if (!description) throw new Error('Please provide a description'); - const fee = await getFee('CREATE_GROUP'); + const fee = await getFee('CREATE_GROUP'); // TODO translate await show({ message: 'Would you like to perform an CREATE_GROUP transaction?', publishFee: fee.fee + ' QORT', diff --git a/src/components/Group/AddGroupList.tsx b/src/components/Group/AddGroupList.tsx index 433d0e4..b80cc34 100644 --- a/src/components/Group/AddGroupList.tsx +++ b/src/components/Group/AddGroupList.tsx @@ -101,7 +101,7 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => { const handleJoinGroup = async (group, isOpen) => { try { const groupId = group.groupId; - const fee = await getFee('JOIN_GROUP'); + const fee = await getFee('JOIN_GROUP'); // TODO translate await show({ message: 'Would you like to perform an JOIN_GROUP transaction?', publishFee: fee.fee + ' QORT', diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx index b2d58e7..80d9c4c 100644 --- a/src/components/Group/BlockedUsersModal.tsx +++ b/src/components/Group/BlockedUsersModal.tsx @@ -129,7 +129,7 @@ export const BlockedUsersModal = () => { executeEvent('updateChatMessagesWithBlocks', true); } } catch (error) { - setOpenSnackGlobal(true); + setOpenSnackGlobal(true); // TODO translate setInfoSnackCustom({ type: 'error', message: error?.message || 'Unable to block user', diff --git a/src/components/Group/Forum/DisplayHtml.tsx b/src/components/Group/Forum/DisplayHtml.tsx index 5bae4a7..786672a 100644 --- a/src/components/Group/Forum/DisplayHtml.tsx +++ b/src/components/Group/Forum/DisplayHtml.tsx @@ -1,21 +1,20 @@ -import { useMemo } from "react"; -import DOMPurify from "dompurify"; -import "react-quill/dist/quill.snow.css"; -import "react-quill/dist/quill.core.css"; -import "react-quill/dist/quill.bubble.css"; -import { Box, styled } from "@mui/material"; -import { convertQortalLinks } from "../../../utils/qortalLink"; - +import { useMemo } from 'react'; +import DOMPurify from 'dompurify'; +import 'react-quill/dist/quill.snow.css'; +import 'react-quill/dist/quill.core.css'; +import 'react-quill/dist/quill.bubble.css'; +import { Box, styled } from '@mui/material'; +import { convertQortalLinks } from '../../../utils/qortalLink'; const CrowdfundInlineContent = styled(Box)(({ theme }) => ({ - display: "flex", - fontFamily: "Mulish", - fontSize: "19px", - fontWeight: 400, - letterSpacing: 0, - color: theme.palette.text.primary, - width: '100%' - })); + display: 'flex', + fontFamily: 'Mulish', + fontSize: '19px', + fontWeight: 400, + letterSpacing: 0, + color: theme.palette.text.primary, + width: '100%', +})); export const DisplayHtml = ({ html, textColor }: any) => { const cleanContent = useMemo(() => { @@ -29,6 +28,7 @@ export const DisplayHtml = ({ html, textColor }: any) => { }, [html]); if (!cleanContent) return null; + return (
{ style={{ color: textColor || 'white', fontWeight: 400, - fontSize: '16px' + fontSize: '16px', }} dangerouslySetInnerHTML={{ __html: cleanContent }} /> diff --git a/src/components/Group/Forum/GroupMail.tsx b/src/components/Group/Forum/GroupMail.tsx index 544634b..3c8972f 100644 --- a/src/components/Group/Forum/GroupMail.tsx +++ b/src/components/Group/Forum/GroupMail.tsx @@ -45,7 +45,6 @@ import { formatDate, formatTimestamp } from '../../../utils/time'; import LazyLoad from '../../../common/LazyLoad'; import { delay } from '../../../utils/helpers'; import { NewThread } from './NewThread'; -import { getBaseApi } from '../../../background'; import { decryptPublishes, getTempPublish, @@ -55,15 +54,11 @@ import CheckSVG from '../../../assets/svgs/Check.svg'; import SortSVG from '../../../assets/svgs/Sort.svg'; import ArrowDownSVG from '../../../assets/svgs/ArrowDown.svg'; import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar'; -import { - executeEvent, - subscribeToEvent, - unsubscribeFromEvent, -} from '../../../utils/events'; +import { executeEvent } from '../../../utils/events'; import RefreshIcon from '@mui/icons-material/Refresh'; import { getArbitraryEndpointReact, getBaseApiReact } from '../../../App'; -import { WrapperUserAction } from '../../WrapperUserAction'; import { addDataPublishesFunc, getDataPublishesFunc } from '../Group'; + const filterOptions = ['Recently active', 'Newest', 'Oldest']; export const threadIdentifier = 'DOCUMENT'; @@ -183,7 +178,7 @@ export const GroupMail = ({ }); }); } catch (error) { - } finally { + console.log(error); } }; @@ -266,7 +261,6 @@ export const GroupMail = ({ } else { sorted = fullArrayMsg.sort((a: any, b: any) => a.created - b.created); } - setAllThreads(sorted); } catch (error) { console.log({ error }); @@ -376,6 +370,7 @@ export const GroupMail = ({ ); setRecentThreads(sorted); } catch (error) { + console.log(error); } finally { setIsLoading(false); // dispatch(setIsLoadingCustom(null)); @@ -444,7 +439,6 @@ export const GroupMail = ({ } } } - setMembers(members); } catch (error) { console.log({ error }); @@ -754,7 +748,9 @@ export const GroupMail = ({ > {thread?.threadData?.title} + + {filterMode === 'Recently active' && (
Last page diff --git a/src/components/Group/Forum/Mail-styles.ts b/src/components/Group/Forum/Mail-styles.ts index 534304d..368e4cb 100644 --- a/src/components/Group/Forum/Mail-styles.ts +++ b/src/components/Group/Forum/Mail-styles.ts @@ -1,52 +1,47 @@ -import { - AppBar, - Button, - Toolbar, - Typography, - Box, - TextField, -} from "@mui/material"; -import { styled } from "@mui/system"; +import { Typography, Box, TextField } from '@mui/material'; +import { styled } from '@mui/system'; export const InstanceContainer = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - width: "100%", - backgroundColor: "var(--color-instance)", - height: "59px", + display: 'flex', + alignItems: 'center', + width: '100%', + backgroundColor: 'var(--color-instance)', + height: '59px', flexShrink: 0, - justifyContent: "space-between", + justifyContent: 'space-between', })); + export const MailContainer = styled(Box)(({ theme }) => ({ - display: "flex", - flexDirection: "column", - width: "100%", - height: "calc(100vh - 78px)", - overflow: "hidden", + display: 'flex', + flexDirection: 'column', + width: '100%', + height: 'calc(100vh - 78px)', + overflow: 'hidden', })); export const MailBody = styled(Box)(({ theme }) => ({ - display: "flex", - flexDirection: "row", - width: "100%", - height: "calc(100% - 59px)", - // overflow: 'auto !important' + display: 'flex', + flexDirection: 'row', + width: '100%', + height: 'calc(100% - 59px)', })); + export const MailBodyInner = styled(Box)(({ theme }) => ({ - display: "flex", - flexDirection: "column", - width: "50%", - height: "100%", + display: 'flex', + flexDirection: 'column', + width: '50%', + height: '100%', })); + export const MailBodyInnerHeader = styled(Box)(({ theme }) => ({ - display: "flex", - width: "100%", - height: "25px", - marginTop: "50px", - marginBottom: "35px", - justifyContent: "center", - alignItems: "center", - gap: "11px", + display: 'flex', + width: '100%', + height: '25px', + marginTop: '50px', + marginBottom: '35px', + justifyContent: 'center', + alignItems: 'center', + gap: '11px', })); export const MailBodyInnerScroll = styled(Box)` @@ -84,163 +79,174 @@ export const MailBodyInnerScroll = styled(Box)` `; export const ComposeContainer = styled(Box)(({ theme }) => ({ - display: "flex", - width: "150px", - alignItems: "center", - gap: "7px", - height: "100%", - cursor: "pointer", - transition: "0.2s background-color", - justifyContent: "center", - "&:hover": { - backgroundColor: "rgba(67, 68, 72, 1)", + display: 'flex', + width: '150px', + alignItems: 'center', + gap: '7px', + height: '100%', + cursor: 'pointer', + transition: '0.2s background-color', + justifyContent: 'center', + '&:hover': { + backgroundColor: 'rgba(67, 68, 72, 1)', }, })); + export const ComposeContainerBlank = styled(Box)(({ theme }) => ({ - display: "flex", - width: "150px", - alignItems: "center", - gap: "7px", - height: "100%", + display: 'flex', + width: '150px', + alignItems: 'center', + gap: '7px', + height: '100%', })); + export const ComposeP = styled(Typography)(({ theme }) => ({ - fontSize: "15px", + fontSize: '15px', fontWeight: 500, })); -export const ComposeIcon = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", - cursor: "pointer", -}); -export const ArrowDownIcon = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", - cursor: "pointer", -}); -export const MailIconImg = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", +export const ComposeIcon = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', + cursor: 'pointer', }); -export const MailMessageRowInfoImg = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", +export const ArrowDownIcon = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', + cursor: 'pointer', +}); + +export const MailIconImg = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', +}); + +export const MailMessageRowInfoImg = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', }); export const SelectInstanceContainer = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - gap: "17px", + display: 'flex', + alignItems: 'center', + gap: '17px', })); + export const SelectInstanceContainerInner = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - gap: "3px", - cursor: "pointer", - padding: "8px", - transition: "all 0.2s", - "&:hover": { - borderRadius: "8px", - background: "#434448", + display: 'flex', + alignItems: 'center', + gap: '3px', + cursor: 'pointer', + padding: '8px', + transition: 'all 0.2s', + '&:hover': { + borderRadius: '8px', + background: '#434448', }, })); + export const SelectInstanceContainerFilterInner = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - gap: "3px", - cursor: "pointer", - padding: "8px", - transition: "all 0.2s" + display: 'flex', + alignItems: 'center', + gap: '3px', + cursor: 'pointer', + padding: '8px', + transition: 'all 0.2s', })); - export const InstanceLabel = styled(Typography)(({ theme }) => ({ - fontSize: "16px", + fontSize: '16px', fontWeight: 500, - color: "#FFFFFF33", + color: '#FFFFFF33', })); export const InstanceP = styled(Typography)(({ theme }) => ({ - fontSize: "16px", + fontSize: '16px', fontWeight: 500, })); export const MailMessageRowContainer = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - cursor: "pointer", - justifyContent: "space-between", - borderRadius: "56px 5px 10px 56px", - paddingRight: "15px", - transition: "background 0.2s", - gap: "10px", - "&:hover": { - background: "#434448", + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + justifyContent: 'space-between', + borderRadius: '56px 5px 10px 56px', + paddingRight: '15px', + transition: 'background 0.2s', + gap: '10px', + '&:hover': { + background: '#434448', }, })); + export const MailMessageRowProfile = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - cursor: "pointer", - justifyContent: "flex-start", - gap: "10px", - width: "50%", - overflow: "hidden", + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + justifyContent: 'flex-start', + gap: '10px', + width: '50%', + overflow: 'hidden', })); + export const MailMessageRowInfo = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - cursor: "pointer", - justifyContent: "flex-start", - gap: "7px", - width: "50%", + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + justifyContent: 'flex-start', + gap: '7px', + width: '50%', })); + export const MailMessageRowInfoStatusNotDecrypted = styled(Typography)( ({ theme }) => ({ - fontSize: "16px", + fontSize: '16px', fontWeight: 900, - textTransform: "uppercase", - paddingTop: "2px", + textTransform: 'uppercase', + paddingTop: '2px', }) ); + export const MailMessageRowInfoStatusRead = styled(Typography)(({ theme }) => ({ - fontSize: "16px", + fontSize: '16px', fontWeight: 300, })); export const MessageExtraInfo = styled(Box)(({ theme }) => ({ - display: "flex", - flexDirection: "column", - gap: "2px", - overflow: "hidden", + display: 'flex', + flexDirection: 'column', + gap: '2px', + overflow: 'hidden', })); + export const MessageExtraName = styled(Typography)(({ theme }) => ({ - fontSize: "16px", + fontSize: '16px', fontWeight: 900, - whiteSpace: "nowrap", - textOverflow: "ellipsis", - overflow: "hidden", + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + overflow: 'hidden', })); + export const MessageExtraDate = styled(Typography)(({ theme }) => ({ - fontSize: "15px", + fontSize: '15px', fontWeight: 500, })); export const MessagesContainer = styled(Box)(({ theme }) => ({ - width: "460px", - maxWidth: "90%", - display: "flex", - flexDirection: "column", - gap: "12px", + width: '460px', + maxWidth: '90%', + display: 'flex', + flexDirection: 'column', + gap: '12px', })); export const InstanceListParent = styled(Box)` @@ -254,18 +260,21 @@ export const InstanceListParent = styled(Box)` background-color: var(--color-instance-popover-bg); border: 1px solid rgba(0, 0, 0, 0.1); `; + export const InstanceListHeader = styled(Box)` display: flex; flex-direction: column; width: 100%; background-color: var(--color-instance-popover-bg); `; + export const InstanceFooter = styled(Box)` display: flex; flex-direction: column; width: 100%; flex-shrink: 0; `; + export const InstanceListContainer = styled(Box)` width: 100%; display: flex; @@ -301,126 +310,132 @@ export const InstanceListContainer = styled(Box)` } } `; + export const InstanceListContainerRowLabelContainer = styled(Box)( ({ theme }) => ({ - width: "100%", - display: "flex", - alignItems: "center", - gap: "10px", - height: "50px", + width: '100%', + display: 'flex', + alignItems: 'center', + gap: '10px', + height: '50px', }) ); + export const InstanceListContainerRow = styled(Box)(({ theme }) => ({ - width: "100%", - display: "flex", - alignItems: "center", - gap: "10px", - height: "50px", - cursor: "pointer", - transition: "0.2s background", - "&:hover": { - background: "rgba(67, 68, 72, 1)", + width: '100%', + display: 'flex', + alignItems: 'center', + gap: '10px', + height: '50px', + cursor: 'pointer', + transition: '0.2s background', + '&:hover': { + background: 'rgba(67, 68, 72, 1)', }, flexShrink: 0, })); + export const InstanceListContainerRowCheck = styled(Box)(({ theme }) => ({ - width: "47px", - display: "flex", - alignItems: "center", - justifyContent: "center", + width: '47px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', })); + export const InstanceListContainerRowMain = styled(Box)(({ theme }) => ({ - display: "flex", - justifyContent: "space-between", - width: "100%", - alignItems: "center", - paddingRight: "30px", - overflow: "hidden", + display: 'flex', + justifyContent: 'space-between', + width: '100%', + alignItems: 'center', + paddingRight: '30px', + overflow: 'hidden', })); + export const CloseParent = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - gap: "20px", + display: 'flex', + alignItems: 'center', + gap: '20px', })); + export const InstanceListContainerRowMainP = styled(Typography)( ({ theme }) => ({ fontWeight: 500, - fontSize: "16px", - textOverflow: "ellipsis", - overflow: "hidden", + fontSize: '16px', + textOverflow: 'ellipsis', + overflow: 'hidden', }) ); -export const InstanceListContainerRowCheckIcon = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", +export const InstanceListContainerRowCheckIcon = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', }); -export const InstanceListContainerRowGroupIcon = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", +export const InstanceListContainerRowGroupIcon = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', }); export const TypeInAliasTextfield = styled(TextField)({ - width: "340px", // Adjust the width as needed - borderRadius: "5px", - backgroundColor: "rgba(30, 30, 32, 1)", - border: "none", - outline: "none", + width: '340px', // Adjust the width as needed + borderRadius: '5px', + backgroundColor: 'rgba(30, 30, 32, 1)', + border: 'none', + outline: 'none', input: { fontSize: 16, - color: "white", - "&::placeholder": { + color: 'white', + '&::placeholder': { fontSize: 16, - color: "rgba(255, 255, 255, 0.2)", + color: 'rgba(255, 255, 255, 0.2)', }, - border: "none", - outline: "none", - padding: "10px", + border: 'none', + outline: 'none', + padding: '10px', }, - "& .MuiOutlinedInput-root": { - "& fieldset": { - border: "none", + '& .MuiOutlinedInput-root': { + '& fieldset': { + border: 'none', }, - "&:hover fieldset": { - border: "none", + '&:hover fieldset': { + border: 'none', }, - "&.Mui-focused fieldset": { - border: "none", + '&.Mui-focused fieldset': { + border: 'none', }, }, - "& .MuiInput-underline:before": { - borderBottom: "none", + '& .MuiInput-underline:before': { + borderBottom: 'none', }, - "& .MuiInput-underline:hover:not(.Mui-disabled):before": { - borderBottom: "none", + '& .MuiInput-underline:hover:not(.Mui-disabled):before': { + borderBottom: 'none', }, - "& .MuiInput-underline:after": { - borderBottom: "none", + '& .MuiInput-underline:after': { + borderBottom: 'none', }, }); -export const NewMessageCloseImg = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", - cursor: "pointer", +export const NewMessageCloseImg = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', + cursor: 'pointer', }); export const NewMessageHeaderP = styled(Typography)(({ theme }) => ({ - fontSize: "18px", + fontSize: '18px', fontWeight: 600, })); export const NewMessageInputRow = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - justifyContent: "space-between", - borderBottom: "3px solid rgba(237, 239, 241, 1)", - width: "100%", - paddingBottom: "6px", + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + borderBottom: '3px solid rgba(237, 239, 241, 1)', + width: '100%', + paddingBottom: '6px', })); export const NewMessageInputLabelP = styled(Typography)` color: rgba(84, 84, 84, 0.7); @@ -444,25 +459,25 @@ export const AliasLabelP = styled(Typography)` } `; export const NewMessageAliasContainer = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - gap: "12px", + display: 'flex', + alignItems: 'center', + gap: '12px', })); export const AttachmentContainer = styled(Box)(({ theme }) => ({ - height: "36px", - width: "100%", - display: "flex", - alignItems: "center", + height: '36px', + width: '100%', + display: 'flex', + alignItems: 'center', })); -export const NewMessageAttachmentImg = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", - cursor: "pointer", - padding: "10px", - border: "1px dashed #646464", +export const NewMessageAttachmentImg = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', + cursor: 'pointer', + padding: '10px', + border: '1px dashed #646464', }); export const NewMessageSendButton = styled(Box)` @@ -582,34 +597,36 @@ export const ShowMessageButtonP = styled(Typography)` color: white; `; -export const ShowMessageButtonImg = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", - cursor: "pointer", +export const ShowMessageButtonImg = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', + cursor: 'pointer', }); -export const MailAttachmentImg = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", +export const MailAttachmentImg = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', }); -export const AliasAvatarImg = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", + +export const AliasAvatarImg = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', }); -export const MoreImg = styled("img")({ - width: "auto", - height: "auto", - userSelect: "none", - objectFit: "contain", - transition: "0.2s all", - "&:hover": { - transform: "scale(1.3)", + +export const MoreImg = styled('img')({ + width: 'auto', + height: 'auto', + userSelect: 'none', + objectFit: 'contain', + transition: '0.2s all', + '&:hover': { + transform: 'scale(1.3)', }, }); @@ -625,17 +642,19 @@ export const MoreP = styled(Typography)` letter-spacing: -0.16px; white-space: nowrap; `; + export const ThreadContainerFullWidth = styled(Box)(({ theme }) => ({ - display: "flex", - flexDirection: "column", - width: "100%", - alignItems: "center", + display: 'flex', + flexDirection: 'column', + width: '100%', + alignItems: 'center', })); + export const ThreadContainer = styled(Box)(({ theme }) => ({ - display: "flex", - flexDirection: "column", - width: "100%", - maxWidth: "95%", + display: 'flex', + flexDirection: 'column', + width: '100%', + maxWidth: '95%', })); export const GroupNameP = styled(Typography)` @@ -648,130 +667,131 @@ export const GroupNameP = styled(Typography)` `; export const AllThreadP = styled(Typography)` - color: #FFF; -font-size: 20px; -font-style: normal; -font-weight: 400; -line-height: 120%; /* 24px */ -letter-spacing: 0.15px; + color: #fff; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: 120%; /* 24px */ + letter-spacing: 0.15px; `; export const SingleThreadParent = styled(Box)` -border-radius: 35px 4px 4px 35px; -position: relative; -background: #434448; -display: flex; -padding: 13px; -cursor: pointer; -margin-bottom: 5px; -height: 76px; -align-items:center; -transition: 0.2s all; -&:hover { -background: rgba(255, 255, 255, 0.20) -} + border-radius: 35px 4px 4px 35px; + position: relative; + background: #434448; + display: flex; + padding: 13px; + cursor: pointer; + margin-bottom: 5px; + height: 76px; + align-items: center; + transition: 0.2s all; + &:hover { + background: rgba(255, 255, 255, 0.2); + } `; -export const SingleTheadMessageParent = styled(Box)` -border-radius: 35px 4px 4px 35px; -background: #434448; -display: flex; -padding: 13px; -cursor: pointer; -margin-bottom: 5px; -height: 76px; -align-items:center; +export const SingleTheadMessageParent = styled(Box)` + border-radius: 35px 4px 4px 35px; + background: #434448; + display: flex; + padding: 13px; + cursor: pointer; + margin-bottom: 5px; + height: 76px; + align-items: center; `; export const ThreadInfoColumn = styled(Box)(({ theme }) => ({ - display: "flex", - flexDirection: "column", - width: "170px", + display: 'flex', + flexDirection: 'column', + width: '170px', gap: '2px', marginLeft: '10px', height: '100%', - justifyContent: 'center' + justifyContent: 'center', })); - export const ThreadInfoColumnNameP = styled(Typography)` -color: #FFF; -font-family: Roboto; -font-size: 16px; -font-style: normal; -font-weight: 900; -line-height: normal; -white-space: nowrap; + color: #fff; + font-family: Roboto; + font-size: 16px; + font-style: normal; + font-weight: 900; + line-height: normal; + white-space: nowrap; text-overflow: ellipsis; overflow: hidden; `; + export const ThreadInfoColumnbyP = styled('span')` -color: rgba(255, 255, 255, 0.80); -font-family: Roboto; -font-size: 16px; -font-style: normal; -font-weight: 500; -line-height: normal; + color: rgba(255, 255, 255, 0.8); + font-family: Roboto; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: normal; `; export const ThreadInfoColumnTime = styled(Typography)` -color: rgba(255, 255, 255, 0.80); -font-family: Roboto; -font-size: 15px; -font-style: normal; -font-weight: 500; -line-height: normal; -` + color: rgba(255, 255, 255, 0.8); + font-family: Roboto; + font-size: 15px; + font-style: normal; + font-weight: 500; + line-height: normal; +`; + export const ThreadSingleTitle = styled(Typography)` -color: #FFF; -font-family: Roboto; -font-size: 23px; -font-style: normal; -font-weight: 700; -line-height: normal; -white-space: wrap; + color: #fff; + font-family: Roboto; + font-size: 23px; + font-style: normal; + font-weight: 700; + line-height: normal; + white-space: wrap; text-overflow: ellipsis; overflow: hidden; -` +`; + export const ThreadSingleLastMessageP = styled(Typography)` -color: #FFF; -font-family: Roboto; -font-size: 12px; -font-style: normal; -font-weight: 600; -line-height: normal; -` + color: #fff; + font-family: Roboto; + font-size: 12px; + font-style: normal; + font-weight: 600; + line-height: normal; +`; + export const ThreadSingleLastMessageSpanP = styled('span')` -color: #FFF; -font-family: Roboto; -font-size: 12px; -font-style: normal; -font-weight: 400; -line-height: normal; + color: #fff; + font-family: Roboto; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: normal; `; export const GroupContainer = styled(Box)` -position: relative; - overflow: auto; - width: 100%; - - -` + position: relative; + overflow: auto; + width: 100%; +`; export const CloseContainer = styled(Box)(({ theme }) => ({ - display: "flex", - width: "50px", - overflow: "hidden", - alignItems: "center", - cursor: "pointer", - transition: "0.2s background-color", - justifyContent: "center", + display: 'flex', + width: '50px', + overflow: 'hidden', + alignItems: 'center', + cursor: 'pointer', + transition: '0.2s background-color', + justifyContent: 'center', position: 'absolute', top: '0px', right: '0px', height: '50px', borderRadius: '0px 12px 0px 0px', - "&:hover": { - backgroundColor: "rgba(162, 31, 31, 1)", + '&:hover': { + backgroundColor: 'rgba(162, 31, 31, 1)', }, -})); \ No newline at end of file +})); diff --git a/src/components/Group/Forum/NewThread.tsx b/src/components/Group/Forum/NewThread.tsx index dc987d9..d5c4384 100644 --- a/src/components/Group/Forum/NewThread.tsx +++ b/src/components/Group/Forum/NewThread.tsx @@ -191,7 +191,7 @@ export const NewThread = ({ } if (!groupInfo) { errorMsg = 'Cannot access group information'; - } + } // TODO translate // if (!description) missingFields.push('subject') if (missingFields.length > 0) { diff --git a/src/components/Group/Forum/ReadOnlySlate.tsx b/src/components/Group/Forum/ReadOnlySlate.tsx index d64cf9e..5724f92 100644 --- a/src/components/Group/Forum/ReadOnlySlate.tsx +++ b/src/components/Group/Forum/ReadOnlySlate.tsx @@ -1,18 +1,24 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { createEditor} from 'slate'; -import { withReact, Slate, Editable, RenderElementProps, RenderLeafProps } from 'slate-react'; +import { createEditor } from 'slate'; +import { + withReact, + Slate, + Editable, + RenderElementProps, + RenderLeafProps, +} from 'slate-react'; -type ExtendedRenderElementProps = RenderElementProps & { mode?: string } +type ExtendedRenderElementProps = RenderElementProps & { mode?: string }; export const renderElement = ({ attributes, children, element, - mode + mode, }: ExtendedRenderElementProps) => { switch (element.type) { case 'block-quote': - return
{children}
+ return
{children}
; case 'heading-2': return (

{children}

- ) + ); case 'heading-3': return (

{children}

- ) + ); case 'code-block': return (
           {children}
         
- ) + ); case 'code-line': - return
{children}
+ return
{children}
; case 'link': return ( {children} - ) + ); default: return (

{children}

- ) + ); } -} - +}; export const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => { - let el = children + let el = children; if (leaf.bold) { - el = {el} + el = {el}; } if (leaf.italic) { - el = {el} + el = {el}; } if (leaf.underline) { - el = {el} + el = {el}; } if (leaf.link) { @@ -81,39 +86,35 @@ export const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => { {el} - ) + ); } - return {el} -} + return {el}; +}; interface ReadOnlySlateProps { - content: any - mode?: string + content: any; + mode?: string; } const ReadOnlySlate: React.FC = ({ content, mode }) => { - const [load, setLoad] = useState(false) - const editor = useMemo(() => withReact(createEditor()), []) - const value = useMemo(() => content, [content]) + const [load, setLoad] = useState(false); + const editor = useMemo(() => withReact(createEditor()), []); + const value = useMemo(() => content, [content]); - const performUpdate = useCallback(async()=> { - setLoad(true) - await new Promise((res)=> { + const performUpdate = useCallback(async () => { + setLoad(true); + await new Promise((res) => { setTimeout(() => { - res() + res(); }, 250); - }) - setLoad(false) - }, []) - useEffect(()=> { + }); + setLoad(false); + }, []); + useEffect(() => { + performUpdate(); + }, [value]); - - - - performUpdate() - }, [value]) - - if(load) return null + if (load) return null; return ( {}}> @@ -123,7 +124,7 @@ const ReadOnlySlate: React.FC = ({ content, mode }) => { renderLeaf={renderLeaf} /> - ) -} + ); +}; -export default ReadOnlySlate; \ No newline at end of file +export default ReadOnlySlate; diff --git a/src/components/Group/Forum/ShowMessageWithoutModal.tsx b/src/components/Group/Forum/ShowMessageWithoutModal.tsx index 9a079d6..7a4e594 100644 --- a/src/components/Group/Forum/ShowMessageWithoutModal.tsx +++ b/src/components/Group/Forum/ShowMessageWithoutModal.tsx @@ -1,8 +1,8 @@ -import React, { useState } from "react"; -import { Avatar, Box, IconButton } from "@mui/material"; -import DOMPurify from "dompurify"; +import { useState } from 'react'; +import { Avatar, Box, IconButton } from '@mui/material'; +import DOMPurify from 'dompurify'; import FormatQuoteIcon from '@mui/icons-material/FormatQuote'; -import MoreSVG from '../../../assets/svgs/More.svg' +import MoreSVG from '../../../assets/svgs/More.svg'; import { MoreImg, @@ -11,20 +11,18 @@ import { ThreadInfoColumn, ThreadInfoColumnNameP, ThreadInfoColumnTime, -} from "./Mail-styles"; -import { Spacer } from "../../../common/Spacer"; -import { DisplayHtml } from "./DisplayHtml"; -import { formatTimestampForum } from "../../../utils/time"; -import ReadOnlySlate from "./ReadOnlySlate"; -import { MessageDisplay } from "../../Chat/MessageDisplay"; -import { getBaseApi } from "../../../background"; -import { getBaseApiReact } from "../../../App"; -import { WrapperUserAction } from "../../WrapperUserAction"; +} from './Mail-styles'; +import { Spacer } from '../../../common/Spacer'; +import { formatTimestampForum } from '../../../utils/time'; +import ReadOnlySlate from './ReadOnlySlate'; +import { MessageDisplay } from '../../Chat/MessageDisplay'; +import { getBaseApiReact } from '../../../App'; +import { WrapperUserAction } from '../../WrapperUserAction'; export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => { const [expandAttachments, setExpandAttachments] = useState(false); - let cleanHTML = ""; + let cleanHTML = ''; if (message?.htmlContent) { cleanHTML = DOMPurify.sanitize(message.htmlContent); } @@ -32,79 +30,94 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => { return ( - - {message?.name?.charAt(0)} - + + + {message?.name?.charAt(0)} + + - - - {message?.name} + + {message?.name} {formatTimestampForum(message?.created)}
- {message?.attachments?.length > 0 && ( - - {message?.attachments - .map((file: any, index: number) => { - const isFirst = index === 0 - return ( - + {message?.attachments?.length > 0 && ( + + {message?.attachments.map((file: any, index: number) => { + const isFirst = index === 0; + return ( - {/* + {/* { {file?.originalFilename || file?.filename} */} - {message?.attachments?.length > 1 && isFirst && ( - { - setExpandAttachments(prev => !prev); - }} - > - 1 && isFirst && ( + - - {expandAttachments ? 'hide' : `(${message?.attachments?.length - 1} more)`} - - - - )} + onClick={() => { + setExpandAttachments((prev) => !prev); + }} + > + + + {expandAttachments + ? 'hide' + : `(${message?.attachments?.length - 1} more)`} + + + )} + - - ); - }) - } - - )} - -
+ ); + })} +
+ )} +
+ + {message?.reply?.textContentV2 && ( <> - + - - {message?.reply?.name?.charAt(0)} - - {message?.reply?.name} - - - - - - + sx={{ + display: 'flex', + alignItems: 'flex-start', + gap: '10px', + }} + > + + {message?.reply?.name?.charAt(0)} + + + + {message?.reply?.name} + + + + + + - )} - + {message?.textContent && ( )} @@ -208,22 +230,18 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => { {message?.htmlContent && (
)} - - openNewPostWithQuote(message)} - + - - + openNewPostWithQuote(message)}> + + - - - ); }; diff --git a/src/components/Group/Forum/TextEditor.tsx b/src/components/Group/Forum/TextEditor.tsx index 874f2e3..ac26275 100644 --- a/src/components/Group/Forum/TextEditor.tsx +++ b/src/components/Group/Forum/TextEditor.tsx @@ -1,32 +1,33 @@ -import React from "react"; -import ReactQuill, { Quill } from "react-quill"; -import "react-quill/dist/quill.snow.css"; -import ImageResize from "quill-image-resize-module-react"; -import './texteditor.css' -Quill.register("modules/imageResize", ImageResize); +import ReactQuill, { Quill } from 'react-quill'; +import 'react-quill/dist/quill.snow.css'; +import ImageResize from 'quill-image-resize-module-react'; +import './texteditor.css'; + +Quill.register('modules/imageResize', ImageResize); const modules = { imageResize: { - parchment: Quill.import("parchment"), - modules: ["Resize", "DisplaySize"], + parchment: Quill.import('parchment'), + modules: ['Resize', 'DisplaySize'], }, toolbar: [ - ["bold", "italic", "underline", "strike"], // styled text - ["blockquote", "code-block"], // blocks + ['bold', 'italic', 'underline', 'strike'], // styled text + ['blockquote', 'code-block'], // blocks [{ header: 1 }, { header: 2 }], // custom button values - [{ list: "ordered" }, { list: "bullet" }], // lists - [{ script: "sub" }, { script: "super" }], // superscript/subscript - [{ indent: "-1" }, { indent: "+1" }], // outdent/indent - [{ direction: "rtl" }], // text direction - [{ size: ["small", false, "large", "huge"] }], // custom dropdown + [{ list: 'ordered' }, { list: 'bullet' }], // lists + [{ script: 'sub' }, { script: 'super' }], // superscript/subscript + [{ indent: '-1' }, { indent: '+1' }], // outdent/indent + [{ direction: 'rtl' }], // text direction + [{ size: ['small', false, 'large', 'huge'] }], // custom dropdown [{ header: [1, 2, 3, 4, 5, 6, false] }], // custom button values [{ color: [] }, { background: [] }], // dropdown with defaults [{ font: [] }], // font family [{ align: [] }], // text align - ["clean"], // remove formatting + ['clean'], // remove formatting // ["image"], // image ], }; + export const TextEditor = ({ inlineContent, setInlineContent }: any) => { return ( Previous diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 4573022..a4629ce 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -2218,7 +2218,8 @@ export const Group = ({ }} > No group selected - + {' '} + // TODO translate )} @@ -2235,39 +2236,39 @@ export const Group = ({ : '0px', }} > - - + - + + /> { Group Invites{' '} {groupsWithJoinRequests?.length > 0 && diff --git a/src/components/Group/GroupJoinRequests.tsx b/src/components/Group/GroupJoinRequests.tsx index 1847bd0..4bc6b21 100644 --- a/src/components/Group/GroupJoinRequests.tsx +++ b/src/components/Group/GroupJoinRequests.tsx @@ -82,6 +82,7 @@ export const GroupJoinRequests = ({ ); setGroupsWithJoinRequests(res); } catch (error) { + console.log(error); } finally { setLoading(false); } @@ -138,7 +139,7 @@ export const GroupJoinRequests = ({ Join Requests{' '} {filteredJoinRequests?.filter((group) => group?.data?.length > 0) diff --git a/src/components/Group/GroupMenu.tsx b/src/components/Group/GroupMenu.tsx index a44c480..0d81cd2 100644 --- a/src/components/Group/GroupMenu.tsx +++ b/src/components/Group/GroupMenu.tsx @@ -1,22 +1,27 @@ -import React, { useState } from "react"; +import { useState } from 'react'; import { Button, Menu, MenuItem, ListItemIcon, ListItemText, - Badge, Box, -} from "@mui/material"; -import ForumIcon from "@mui/icons-material/Forum"; -import GroupIcon from "@mui/icons-material/Group"; -import { ArrowDownIcon } from "../../assets/Icons/ArrowDownIcon"; -import { NotificationIcon2 } from "../../assets/Icons/NotificationIcon2"; -import { ChatIcon } from "../../assets/Icons/ChatIcon"; -import { ThreadsIcon } from "../../assets/Icons/ThreadsIcon"; -import { MembersIcon } from "../../assets/Icons/MembersIcon"; +} from '@mui/material'; +import { ArrowDownIcon } from '../../assets/Icons/ArrowDownIcon'; +import { NotificationIcon2 } from '../../assets/Icons/NotificationIcon2'; +import { ChatIcon } from '../../assets/Icons/ChatIcon'; +import { ThreadsIcon } from '../../assets/Icons/ThreadsIcon'; +import { MembersIcon } from '../../assets/Icons/MembersIcon'; -export const GroupMenu = ({ setGroupSection, groupSection, setOpenManageMembers, goToAnnouncements, goToChat, hasUnreadChat, hasUnreadAnnouncements }) => { +export const GroupMenu = ({ + setGroupSection, + groupSection, + setOpenManageMembers, + goToAnnouncements, + goToChat, + hasUnreadChat, + hasUnreadAnnouncements, +}) => { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); @@ -31,170 +36,217 @@ export const GroupMenu = ({ setGroupSection, groupSection, setOpenManageMembers, return ( + { - goToChat() + goToChat(); handleClose(); }} > - - + + - + { - goToAnnouncements() + goToAnnouncements(); handleClose(); }} > - - + + - + { - setGroupSection("forum"); + setGroupSection('forum'); handleClose(); }} > - - - + + - + { - setOpenManageMembers(true) + setOpenManageMembers(true); handleClose(); }} > - - - + + - + diff --git a/src/components/Group/Home.tsx b/src/components/Group/Home.tsx deleted file mode 100644 index a8c804c..0000000 --- a/src/components/Group/Home.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { Box, Button, Typography } from "@mui/material"; -import React from "react"; -import { Spacer } from "../../common/Spacer"; -import { ListOfThreadPostsWatched } from "./ListOfThreadPostsWatched"; -import { ThingsToDoInitial } from "./ThingsToDoInitial"; -import { GroupJoinRequests } from "./GroupJoinRequests"; -import { GroupInvites } from "./GroupInvites"; -import RefreshIcon from "@mui/icons-material/Refresh"; - -export const Home = ({ - refreshHomeDataFunc, - myAddress, - isLoadingGroups, - balance, - userInfo, - groups, - setGroupSection, - setSelectedGroup, - getTimestampEnterChat, - setOpenManageMembers, - setOpenAddGroup, - setMobileViewMode, - setDesktopViewMode -}) => { - return ( - - - 15 ? "16px" : "20px", - padding: '10px' - }} - > - Welcome - {userInfo?.name ? ( - {`, ${userInfo?.name}`} - ) : null} - - - - {/* - - */} - {!isLoadingGroups && ( - - - - - - - - )} - - - ); -}; diff --git a/src/components/Group/HomeDesktop.tsx b/src/components/Group/HomeDesktop.tsx index d83f774..d0f2459 100644 --- a/src/components/Group/HomeDesktop.tsx +++ b/src/components/Group/HomeDesktop.tsx @@ -1,16 +1,16 @@ -import { Box, Button, Divider, Typography } from "@mui/material"; -import React from "react"; -import { Spacer } from "../../common/Spacer"; -import { ListOfThreadPostsWatched } from "./ListOfThreadPostsWatched"; -import { ThingsToDoInitial } from "./ThingsToDoInitial"; -import { GroupJoinRequests } from "./GroupJoinRequests"; -import { GroupInvites } from "./GroupInvites"; -import RefreshIcon from "@mui/icons-material/Refresh"; -import { ListOfGroupPromotions } from "./ListOfGroupPromotions"; -import { QortPrice } from "../Home/QortPrice"; -import ExploreIcon from "@mui/icons-material/Explore"; -import { Explore } from "../Explore/Explore"; -import { NewUsersCTA } from "../Home/NewUsersCTA"; +import { Box, Divider, Typography, useTheme } from '@mui/material'; +import React from 'react'; +import { Spacer } from '../../common/Spacer'; +import { ThingsToDoInitial } from './ThingsToDoInitial'; +import { GroupJoinRequests } from './GroupJoinRequests'; +import { GroupInvites } from './GroupInvites'; +import { ListOfGroupPromotions } from './ListOfGroupPromotions'; +import { QortPrice } from '../Home/QortPrice'; +import ExploreIcon from '@mui/icons-material/Explore'; +import { Explore } from '../Explore/Explore'; +import { NewUsersCTA } from '../Home/NewUsersCTA'; +import { useTranslation } from 'react-i18next'; + export const HomeDesktop = ({ refreshHomeDataFunc, myAddress, @@ -30,93 +30,97 @@ export const HomeDesktop = ({ }) => { const [checked1, setChecked1] = React.useState(false); const [checked2, setChecked2] = React.useState(false); - React.useEffect(() => { - if (balance && +balance >= 6) { - setChecked1(true); - } - }, [balance]); - - - React.useEffect(() => { - if (name) setChecked2(true); - }, [name]); - - - const isLoaded = React.useMemo(()=> { - if(userInfo !== null) return true - return false - }, [ userInfo]) - - const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(()=> { - if(isLoaded && checked1 && checked2) return true - return false - }, [checked1, isLoaded, checked2]) - + const { t } = useTranslation(['core']); + const theme = useTheme(); + + React.useEffect(() => { + if (balance && +balance >= 6) { + setChecked1(true); + } + }, [balance]); + + React.useEffect(() => { + if (name) setChecked2(true); + }, [name]); + + const isLoaded = React.useMemo(() => { + if (userInfo !== null) return true; + return false; + }, [userInfo]); + + const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(() => { + if (isLoaded && checked1 && checked2) return true; + return false; + }, [checked1, isLoaded, checked2]); + return ( + 15 ? "16px" : "20px", - padding: "10px", + fontSize: userInfo?.name?.length > 15 ? '16px' : '20px', + padding: '10px', }} > - Welcome + {t('core:welcome', { postProcess: 'capitalize' })} {userInfo?.name ? ( {`, ${userInfo?.name}`} ) : null} + + {!isLoadingGroups && ( item?.groupId !== "0").length !== 0 + groups?.filter((item) => item?.groupId !== '0').length !== 0 } /> - - {desktopViewMode === "home" && ( + + {desktopViewMode === 'home' && ( <> {/* */} - {hasDoneNameAndBalanceAndIsLoaded && ( - <> - - - - - - - - )} - + {hasDoneNameAndBalanceAndIsLoaded && ( + <> + + + + + + + + + )} )} )} - + {!isLoadingGroups && ( <> - - - {" "} - - Explore - {" "} - - - {!hasDoneNameAndBalanceAndIsLoaded && ( - - )} - - {hasDoneNameAndBalanceAndIsLoaded && ( - + + {' '} + + {t('tutorial:initial.explore', { postProcess: 'capitalize' })} + {' '} + + - )} - - - - - + {!hasDoneNameAndBalanceAndIsLoaded && } + + {hasDoneNameAndBalanceAndIsLoaded && } + + + + + - )} + {/* { }) .then((response) => { if (!response?.error) { + // TODO translate setInfoSnack({ type: 'success', message: `Successfully invited ${value}. It may take a couple of minutes for the changes to propagate`, diff --git a/src/components/Group/ListOfBans.tsx b/src/components/Group/ListOfBans.tsx index db4c6ba..850ce13 100644 --- a/src/components/Group/ListOfBans.tsx +++ b/src/components/Group/ListOfBans.tsx @@ -1,16 +1,31 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Avatar, Box, Button, ListItem, ListItemAvatar, ListItemButton, ListItemText, Popover } from '@mui/material'; -import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized'; +import { useEffect, useRef, useState } from 'react'; +import { + Avatar, + Box, + ListItem, + ListItemAvatar, + ListItemButton, + ListItemText, + Popover, +} from '@mui/material'; +import { + AutoSizer, + CellMeasurer, + CellMeasurerCache, + List, +} from 'react-virtualized'; import { getNameInfo } from './Group'; -import { getBaseApi, getFee } from '../../background'; +import { getFee } from '../../background'; import { LoadingButton } from '@mui/lab'; import { getBaseApiReact } from '../../App'; export const getMemberInvites = async (groupNumber) => { - const response = await fetch(`${getBaseApiReact()}/groups/bans/${groupNumber}?limit=0`); + const response = await fetch( + `${getBaseApiReact()}/groups/bans/${groupNumber}?limit=0` + ); const groupData = await response.json(); return groupData; -} +}; const getNames = async (listOfMembers, includeNoNames) => { let members = []; @@ -20,14 +35,14 @@ const getNames = async (listOfMembers, includeNoNames) => { const name = await getNameInfo(member.offender); if (name) { members.push({ ...member, name }); - } else if(includeNoNames){ - members.push({ ...member, name: name || "" }); + } else if (includeNoNames) { + members.push({ ...member, name: name || '' }); } } } } return members; -} +}; const cache = new CellMeasurerCache({ fixedWidth: true, @@ -49,7 +64,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => { } catch (error) { console.error(error); } - } + }; useEffect(() => { if (groupId) { @@ -67,33 +82,36 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => { setOpenPopoverIndex(null); }; - const handleCancelBan = async (address)=> { + const handleCancelBan = async (address) => { try { - const fee = await getFee('CANCEL_GROUP_BAN') + // TODO translate + const fee = await getFee('CANCEL_GROUP_BAN'); await show({ - message: "Would you like to perform a CANCEL_GROUP_BAN transaction?" , - publishFee: fee.fee + ' QORT' - }) - setIsLoadingUnban(true) - new Promise((res, rej)=> { - window.sendMessage("cancelBan", { - groupId, - qortalAddress: address, - }) + message: 'Would you like to perform a CANCEL_GROUP_BAN transaction?', + publishFee: fee.fee + ' QORT', + }); + setIsLoadingUnban(true); + new Promise((res, rej) => { + window + .sendMessage('cancelBan', { + groupId, + qortalAddress: address, + }) .then((response) => { if (!response?.error) { res(response); setIsLoadingUnban(false); setInfoSnack({ - type: "success", - message: "Successfully unbanned user. It may take a couple of minutes for the changes to propagate", + type: 'success', + message: + 'Successfully unbanned user. It may take a couple of minutes for the changes to propagate', }); handlePopoverClose(); setOpenSnack(true); return; } setInfoSnack({ - type: "error", + type: 'error', message: response?.error, }); setOpenSnack(true); @@ -101,24 +119,22 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => { }) .catch((error) => { setInfoSnack({ - type: "error", - message: error.message || "An error occurred", + type: 'error', + message: error.message || 'An error occurred', }); setOpenSnack(true); rej(error); }); - - }) + }); } catch (error) { - } finally { - setIsLoadingUnban(false) + setIsLoadingUnban(false); } - } + }; const rowRenderer = ({ index, key, parent, style }) => { const member = bans[index]; - + return ( { anchorEl={popoverAnchor} onClose={handlePopoverClose} anchorOrigin={{ - vertical: "bottom", - horizontal: "center", + vertical: 'bottom', + horizontal: 'center', }} transformOrigin={{ - vertical: "top", - horizontal: "center", + vertical: 'top', + horizontal: 'center', }} - style={{ marginTop: "8px" }} + style={{ marginTop: '8px' }} > - - handleCancelBan(member?.offender)}>Cancel Ban + variant="contained" + onClick={() => handleCancelBan(member?.offender)} + > + Cancel Ban + - handlePopoverOpen(event, index)}> + handlePopoverOpen(event, index)} + > @@ -179,7 +206,16 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => { return (

Ban list

-
+
{({ height, width }) => ( {
); -} +}; diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index 9a8c170..35ac442 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -230,7 +230,7 @@ export const ListOfGroupPromotions = () => { .catch((error) => { rej(error.message || 'An error occurred'); }); - }); + }); // TODO translate setInfoSnack({ type: 'success', message: diff --git a/src/components/Group/ListOfInvites.tsx b/src/components/Group/ListOfInvites.tsx index a41bc47..a571775 100644 --- a/src/components/Group/ListOfInvites.tsx +++ b/src/components/Group/ListOfInvites.tsx @@ -1,16 +1,31 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Avatar, Box, Button, ListItem, ListItemAvatar, ListItemButton, ListItemText, Popover } from '@mui/material'; -import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized'; +import { useEffect, useRef, useState } from 'react'; +import { + Avatar, + Box, + ListItem, + ListItemAvatar, + ListItemButton, + ListItemText, + Popover, +} from '@mui/material'; +import { + AutoSizer, + CellMeasurer, + CellMeasurerCache, + List, +} from 'react-virtualized'; import { getNameInfo } from './Group'; -import { getBaseApi, getFee } from '../../background'; +import { getFee } from '../../background'; import { LoadingButton } from '@mui/lab'; import { getBaseApiReact } from '../../App'; export const getMemberInvites = async (groupNumber) => { - const response = await fetch(`${getBaseApiReact()}/groups/invites/group/${groupNumber}?limit=0`); + const response = await fetch( + `${getBaseApiReact()}/groups/invites/group/${groupNumber}?limit=0` + ); const groupData = await response.json(); return groupData; -} +}; const getNames = async (listOfMembers, includeNoNames) => { let members = []; @@ -20,21 +35,26 @@ const getNames = async (listOfMembers, includeNoNames) => { const name = await getNameInfo(member.invitee); if (name) { members.push({ ...member, name }); - } else if(includeNoNames){ - members.push({ ...member, name: name || "" }); + } else if (includeNoNames) { + members.push({ ...member, name: name || '' }); } } } } return members; -} +}; const cache = new CellMeasurerCache({ fixedWidth: true, defaultHeight: 50, }); -export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) => { +export const ListOfInvites = ({ + groupId, + setInfoSnack, + setOpenSnack, + show, +}) => { const [invites, setInvites] = useState([]); const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open @@ -50,7 +70,7 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) => } catch (error) { console.error(error); } - } + }; useEffect(() => { if (groupId) { @@ -68,24 +88,27 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) => setOpenPopoverIndex(null); }; - const handleCancelInvitation = async (address)=> { + const handleCancelInvitation = async (address) => { try { - const fee = await getFee('CANCEL_GROUP_INVITE') + // TODO translate + const fee = await getFee('CANCEL_GROUP_INVITE'); await show({ - message: "Would you like to perform a CANCEL_GROUP_INVITE transaction?" , - publishFee: fee.fee + ' QORT' - }) - setIsLoadingCancelInvite(true) - await new Promise((res, rej)=> { - window.sendMessage("cancelInvitationToGroup", { - groupId, - qortalAddress: address, - }) + message: 'Would you like to perform a CANCEL_GROUP_INVITE transaction?', + publishFee: fee.fee + ' QORT', + }); + setIsLoadingCancelInvite(true); + await new Promise((res, rej) => { + window + .sendMessage('cancelInvitationToGroup', { + groupId, + qortalAddress: address, + }) .then((response) => { if (!response?.error) { setInfoSnack({ - type: "success", - message: "Successfully canceled invitation. It may take a couple of minutes for the changes to propagate", + type: 'success', + message: + 'Successfully canceled invitation. It may take a couple of minutes for the changes to propagate', }); setOpenSnack(true); handlePopoverClose(); @@ -94,7 +117,7 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) => return; } setInfoSnack({ - type: "error", + type: 'error', message: response?.error, }); setOpenSnack(true); @@ -102,24 +125,22 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) => }) .catch((error) => { setInfoSnack({ - type: "error", - message: error.message || "An error occurred", + type: 'error', + message: error.message || 'An error occurred', }); setOpenSnack(true); rej(error); }); - - }) + }); } catch (error) { - } finally { - setIsLoadingCancelInvite(false) + setIsLoadingCancelInvite(false); } - } + }; const rowRenderer = ({ index, key, parent, style }) => { const member = invites[index]; - + return ( anchorEl={popoverAnchor} onClose={handlePopoverClose} anchorOrigin={{ - vertical: "bottom", - horizontal: "center", + vertical: 'bottom', + horizontal: 'center', }} transformOrigin={{ - vertical: "top", - horizontal: "center", + vertical: 'top', + horizontal: 'center', }} - style={{ marginTop: "8px" }} + style={{ marginTop: '8px' }} > - - handleCancelInvitation(member?.invitee)}>Cancel Invitation + variant="contained" + onClick={() => handleCancelInvitation(member?.invitee)} + > + Cancel Invitation + - handlePopoverOpen(event, index)}> + handlePopoverOpen(event, index)} + > @@ -180,7 +212,16 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) => return (

Invitees list

-
+
{({ height, width }) => (
); -} +}; diff --git a/src/components/Group/ListOfJoinRequests.tsx b/src/components/Group/ListOfJoinRequests.tsx index 0f4bd81..d2bd5ad 100644 --- a/src/components/Group/ListOfJoinRequests.tsx +++ b/src/components/Group/ListOfJoinRequests.tsx @@ -1,16 +1,31 @@ -import React, { useContext, useEffect, useRef, useState } from 'react'; -import { Avatar, Box, Button, ListItem, ListItemAvatar, ListItemButton, ListItemText, Popover } from '@mui/material'; -import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized'; +import { useContext, useEffect, useRef, useState } from 'react'; +import { + Avatar, + Box, + ListItem, + ListItemAvatar, + ListItemButton, + ListItemText, + Popover, +} from '@mui/material'; +import { + AutoSizer, + CellMeasurer, + CellMeasurerCache, + List, +} from 'react-virtualized'; import { getNameInfo } from './Group'; import { getBaseApi, getFee } from '../../background'; import { LoadingButton } from '@mui/lab'; import { MyContext, getBaseApiReact } from '../../App'; export const getMemberInvites = async (groupNumber) => { - const response = await fetch(`${getBaseApiReact()}/groups/joinrequests/${groupNumber}?limit=0`); + const response = await fetch( + `${getBaseApiReact()}/groups/joinrequests/${groupNumber}?limit=0` + ); const groupData = await response.json(); return groupData; -} +}; const getNames = async (listOfMembers, includeNoNames) => { let members = []; @@ -19,24 +34,29 @@ const getNames = async (listOfMembers, includeNoNames) => { if (member.joiner) { const name = await getNameInfo(member.joiner); if (name) { - members.push({ ...member, name: name || "" }); - } else if(includeNoNames){ - members.push({ ...member, name: name || "" }); + members.push({ ...member, name: name || '' }); + } else if (includeNoNames) { + members.push({ ...member, name: name || '' }); } } } } return members; -} +}; const cache = new CellMeasurerCache({ fixedWidth: true, defaultHeight: 50, }); -export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }) => { +export const ListOfJoinRequests = ({ + groupId, + setInfoSnack, + setOpenSnack, + show, +}) => { const [invites, setInvites] = useState([]); - const {txList, setTxList} = useContext(MyContext) + const { txList, setTxList } = useContext(MyContext); 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 @@ -51,7 +71,7 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show } } catch (error) { console.error(error); } - } + }; useEffect(() => { if (groupId) { @@ -69,31 +89,33 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show } setOpenPopoverIndex(null); }; - const handleAcceptJoinRequest = async (address)=> { + const handleAcceptJoinRequest = async (address) => { try { - const fee = await getFee('GROUP_INVITE') + const fee = await getFee('GROUP_INVITE'); // TODO translate await show({ - message: "Would you like to perform a GROUP_INVITE transaction?" , - publishFee: fee.fee + ' QORT' - }) - setIsLoadingAccept(true) - await new Promise((res, rej)=> { - window.sendMessage("inviteToGroup", { - groupId, - qortalAddress: address, - inviteTime: 10800, - }) + message: 'Would you like to perform a GROUP_INVITE transaction?', + publishFee: fee.fee + ' QORT', + }); + setIsLoadingAccept(true); + await new Promise((res, rej) => { + window + .sendMessage('inviteToGroup', { + groupId, + qortalAddress: address, + inviteTime: 10800, + }) .then((response) => { if (!response?.error) { setIsLoadingAccept(false); setInfoSnack({ - type: "success", - message: "Successfully accepted join request. It may take a couple of minutes for the changes to propagate", + type: 'success', + message: + 'Successfully accepted join request. It may take a couple of minutes for the changes to propagate', }); setOpenSnack(true); handlePopoverClose(); res(response); - + setTxList((prev) => [ { ...response, @@ -106,12 +128,12 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show } }, ...prev, ]); - + return; } - + setInfoSnack({ - type: "error", + type: 'error', message: response?.error, }); setOpenSnack(true); @@ -119,25 +141,28 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show } }) .catch((error) => { setInfoSnack({ - type: "error", - message: error?.message || "An error occurred", + type: 'error', + message: error?.message || 'An error occurred', }); setOpenSnack(true); rej(error); }); - - }) + }); } catch (error) { - } finally { - setIsLoadingAccept(false) + setIsLoadingAccept(false); } - } + }; const rowRenderer = ({ index, key, parent, style }) => { const member = invites[index]; - const findJoinRequsetInTxList = txList?.find((tx)=> tx?.groupId === groupId && tx?.qortalAddress === member?.joiner && tx?.type === 'join-request-accept') - if(findJoinRequsetInTxList) return null + const findJoinRequsetInTxList = txList?.find( + (tx) => + tx?.groupId === groupId && + tx?.qortalAddress === member?.joiner && + tx?.type === 'join-request-accept' + ); + if (findJoinRequsetInTxList) return null; return ( - - handleAcceptJoinRequest(member?.joiner)}>Accept + variant="contained" + onClick={() => handleAcceptJoinRequest(member?.joiner)} + > + Accept + - handlePopoverOpen(event, index)}> + handlePopoverOpen(event, index)} + > @@ -198,7 +234,16 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show } return (

Join request list

-
+
{({ height, width }) => (
); -} +}; diff --git a/src/components/Group/ListOfMembers.tsx b/src/components/Group/ListOfMembers.tsx index 9910453..0f227f8 100644 --- a/src/components/Group/ListOfMembers.tsx +++ b/src/components/Group/ListOfMembers.tsx @@ -1,29 +1,29 @@ import { Avatar, Box, - Button, ListItem, ListItemAvatar, ListItemButton, ListItemText, Popover, Typography, -} from "@mui/material"; -import React, { useRef, useState } from "react"; +} from '@mui/material'; +import { useRef, useState } from 'react'; import { AutoSizer, CellMeasurer, CellMeasurerCache, List, -} from "react-virtualized"; -import { LoadingButton } from "@mui/lab"; -import { getBaseApi, getFee } from "../../background"; -import { getBaseApiReact } from "../../App"; +} from 'react-virtualized'; +import { LoadingButton } from '@mui/lab'; +import { getFee } from '../../background'; +import { getBaseApiReact } from '../../App'; const cache = new CellMeasurerCache({ fixedWidth: true, defaultHeight: 50, }); + const ListOfMembers = ({ members, groupId, @@ -40,7 +40,6 @@ const ListOfMembers = ({ const [isLoadingMakeAdmin, setIsLoadingMakeAdmin] = useState(false); const [isLoadingRemoveAdmin, setIsLoadingRemoveAdmin] = useState(false); - const listRef = useRef(); const handlePopoverOpen = (event, index) => { @@ -55,23 +54,25 @@ const ListOfMembers = ({ const handleKick = async (address) => { try { - const fee = await getFee("GROUP_KICK"); + const fee = await getFee('GROUP_KICK'); await show({ - message: "Would you like to perform a GROUP_KICK transaction?", - publishFee: fee.fee + " QORT", + message: 'Would you like to perform a GROUP_KICK transaction?', + publishFee: fee.fee + ' QORT', }); setIsLoadingKick(true); new Promise((res, rej) => { - window.sendMessage("kickFromGroup", { - groupId, - qortalAddress: address, - }) + window + .sendMessage('kickFromGroup', { + groupId, + qortalAddress: address, + }) .then((response) => { if (!response?.error) { setInfoSnack({ - type: "success", - message: "Successfully kicked member from group. It may take a couple of minutes for the changes to propagate", + type: 'success', + message: + 'Successfully kicked member from group. It may take a couple of minutes for the changes to propagate', }); setOpenSnack(true); handlePopoverClose(); @@ -79,7 +80,7 @@ const ListOfMembers = ({ return; } setInfoSnack({ - type: "error", + type: 'error', message: response?.error, }); setOpenSnack(true); @@ -87,38 +88,40 @@ const ListOfMembers = ({ }) .catch((error) => { setInfoSnack({ - type: "error", - message: error.message || "An error occurred", + type: 'error', + message: error.message || 'An error occurred', }); setOpenSnack(true); rej(error); }); - }); } catch (error) { + console.log(error); } finally { setIsLoadingKick(false); } }; const handleBan = async (address) => { try { - const fee = await getFee("GROUP_BAN"); + const fee = await getFee('GROUP_BAN'); // TODO translate await show({ - message: "Would you like to perform a GROUP_BAN transaction?", - publishFee: fee.fee + " QORT", + message: 'Would you like to perform a GROUP_BAN transaction?', + publishFee: fee.fee + ' QORT', }); setIsLoadingBan(true); await new Promise((res, rej) => { - window.sendMessage("banFromGroup", { - groupId, - qortalAddress: address, - rBanTime: 0, - }) + window + .sendMessage('banFromGroup', { + groupId, + qortalAddress: address, + rBanTime: 0, + }) .then((response) => { if (!response?.error) { setInfoSnack({ - type: "success", - message: "Successfully banned member from group. It may take a couple of minutes for the changes to propagate", + type: 'success', + message: + 'Successfully banned member from group. It may take a couple of minutes for the changes to propagate', }); setOpenSnack(true); handlePopoverClose(); @@ -126,7 +129,7 @@ const ListOfMembers = ({ return; } setInfoSnack({ - type: "error", + type: 'error', message: response?.error, }); setOpenSnack(true); @@ -134,13 +137,12 @@ const ListOfMembers = ({ }) .catch((error) => { setInfoSnack({ - type: "error", - message: error.message || "An error occurred", + type: 'error', + message: error.message || 'An error occurred', }); setOpenSnack(true); rej(error); }); - }); } catch (error) { } finally { @@ -150,22 +152,24 @@ const ListOfMembers = ({ const makeAdmin = async (address) => { try { - const fee = await getFee("ADD_GROUP_ADMIN"); + const fee = await getFee('ADD_GROUP_ADMIN'); await show({ - message: "Would you like to perform a ADD_GROUP_ADMIN transaction?", - publishFee: fee.fee + " QORT", + message: 'Would you like to perform a ADD_GROUP_ADMIN transaction?', + publishFee: fee.fee + ' QORT', }); setIsLoadingMakeAdmin(true); await new Promise((res, rej) => { - window.sendMessage("makeAdmin", { - groupId, - qortalAddress: address, - }) + window + .sendMessage('makeAdmin', { + groupId, + qortalAddress: address, + }) .then((response) => { if (!response?.error) { setInfoSnack({ - type: "success", - message: "Successfully made member an admin. It may take a couple of minutes for the changes to propagate", + type: 'success', + message: + 'Successfully made member an admin. It may take a couple of minutes for the changes to propagate', }); setOpenSnack(true); handlePopoverClose(); @@ -173,7 +177,7 @@ const ListOfMembers = ({ return; } setInfoSnack({ - type: "error", + type: 'error', message: response?.error, }); setOpenSnack(true); @@ -181,13 +185,12 @@ const ListOfMembers = ({ }) .catch((error) => { setInfoSnack({ - type: "error", - message: error.message || "An error occurred", + type: 'error', + message: error.message || 'An error occurred', }); setOpenSnack(true); rej(error); }); - }); } catch (error) { } finally { @@ -197,22 +200,24 @@ const ListOfMembers = ({ const removeAdmin = async (address) => { try { - const fee = await getFee("REMOVE_GROUP_ADMIN"); + const fee = await getFee('REMOVE_GROUP_ADMIN'); await show({ - message: "Would you like to perform a REMOVE_GROUP_ADMIN transaction?", - publishFee: fee.fee + " QORT", + message: 'Would you like to perform a REMOVE_GROUP_ADMIN transaction?', + publishFee: fee.fee + ' QORT', }); setIsLoadingRemoveAdmin(true); await new Promise((res, rej) => { - window.sendMessage("removeAdmin", { - groupId, - qortalAddress: address, - }) + window + .sendMessage('removeAdmin', { + groupId, + qortalAddress: address, + }) .then((response) => { if (!response?.error) { setInfoSnack({ - type: "success", - message: "Successfully removed member as an admin. It may take a couple of minutes for the changes to propagate", + type: 'success', + message: + 'Successfully removed member as an admin. It may take a couple of minutes for the changes to propagate', }); setOpenSnack(true); handlePopoverClose(); @@ -220,7 +225,7 @@ const ListOfMembers = ({ return; } setInfoSnack({ - type: "error", + type: 'error', message: response?.error, }); setOpenSnack(true); @@ -228,13 +233,12 @@ const ListOfMembers = ({ }) .catch((error) => { setInfoSnack({ - type: "error", - message: error.message || "An error occurred", + type: 'error', + message: error.message || 'An error occurred', }); setOpenSnack(true); rej(error); }); - }); } catch (error) { } finally { @@ -260,24 +264,24 @@ const ListOfMembers = ({ anchorEl={popoverAnchor} onClose={handlePopoverClose} anchorOrigin={{ - vertical: "bottom", - horizontal: "center", + vertical: 'bottom', + horizontal: 'center', }} transformOrigin={{ - vertical: "top", - horizontal: "center", + vertical: 'top', + horizontal: 'center', }} - style={{ marginTop: "8px" }} + style={{ marginTop: '8px' }} > {isOwner && ( @@ -336,21 +340,28 @@ const ListOfMembers = ({ {member?.isAdmin && ( - Admin - )} + + Admin + + )} -
)} @@ -363,11 +374,11 @@ const ListOfMembers = ({

Member list

diff --git a/src/components/Group/ListOfThreadPostsWatched.tsx b/src/components/Group/ListOfThreadPostsWatched.tsx index e0a9d00..1cf0d2a 100644 --- a/src/components/Group/ListOfThreadPostsWatched.tsx +++ b/src/components/Group/ListOfThreadPostsWatched.tsx @@ -42,7 +42,7 @@ export const ListOfThreadPostsWatched = () => { rej(response.error); }) .catch((error) => { - rej(error.message || 'An error occurred'); + rej(error.message || 'An error occurred'); // TODO translate }); }); } catch (error) { diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index a69dde4..05d30aa 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -105,7 +105,7 @@ export const ManageMembers = ({ rej(response.error); }) .catch((error) => { - rej(error.message || 'An error occurred'); + rej(error.message || 'An error occurred'); // TODO translate }); }); } catch (error) { diff --git a/src/components/Group/QMailMessages.tsx b/src/components/Group/QMailMessages.tsx index 857b0db..8673110 100644 --- a/src/components/Group/QMailMessages.tsx +++ b/src/components/Group/QMailMessages.tsx @@ -87,7 +87,7 @@ export const QMailMessages = ({ userName, userAddress }) => { rej(response.error); }) .catch((error) => { - rej(error.message || 'An error occurred'); + rej(error.message || 'An error occurred'); // TODO translate }); }); } catch (error) { diff --git a/src/components/Group/Settings.tsx b/src/components/Group/Settings.tsx index d3ff1f7..fd2f9c2 100644 --- a/src/components/Group/Settings.tsx +++ b/src/components/Group/Settings.tsx @@ -79,7 +79,7 @@ export const Settings = ({ address, open, setOpen }) => { if (response?.error) { console.error('Error adding user settings:', response.error); } else { - console.log('User settings added successfully'); + console.log('User settings added successfully'); // TODO translate } }) .catch((error) => { diff --git a/src/components/Group/ThingsToDoInitial.tsx b/src/components/Group/ThingsToDoInitial.tsx index 023cfde..6c315cd 100644 --- a/src/components/Group/ThingsToDoInitial.tsx +++ b/src/components/Group/ThingsToDoInitial.tsx @@ -4,10 +4,11 @@ import ListItem from '@mui/material/ListItem'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; -import { Box, Typography } from '@mui/material'; +import { Box, Typography, useTheme } from '@mui/material'; import { Spacer } from '../../common/Spacer'; import { QMailMessages } from './QMailMessages'; import { executeEvent } from '../../utils/events'; +import { useTranslation } from 'react-i18next'; export const ThingsToDoInitial = ({ myAddress, @@ -18,6 +19,8 @@ export const ThingsToDoInitial = ({ }) => { const [checked1, setChecked1] = React.useState(false); const [checked2, setChecked2] = React.useState(false); + const { t } = useTranslation(['core', 'tutorial']); + const theme = useTheme(); React.useEffect(() => { if (balance && +balance >= 6) { @@ -72,7 +75,11 @@ export const ThingsToDoInitial = ({ fontWeight: 600, }} > - {!isLoaded ? 'Loading...' : 'Getting Started'} + {!isLoaded + ? t('core:loading', { postProcess: 'capitalize' }) + : t('tutorial:initial.getting_started', { + postProcess: 'capitalize', + })} @@ -80,12 +87,12 @@ export const ThingsToDoInitial = ({ {isLoaded && ( @@ -114,7 +121,9 @@ export const ThingsToDoInitial = ({ fontWeight: 400, }, }} - primary={`Have at least 6 QORT in your wallet`} + primary={t('tutorial:initial.6_qort', { + postProcess: 'capitalize', + })} /> { +const getGroupInfo = async (groupId) => { const response = await fetch(`${getBaseApiReact()}/groups/` + groupId); const groupData = await response.json(); if (groupData) { - return groupData - } -} - export const getGroupNames = async (listOfGroups) => { - let groups = []; - if (listOfGroups && Array.isArray(listOfGroups)) { - for (const group of listOfGroups) { - - const groupInfo = await getGroupInfo(group.groupId); - if (groupInfo) { - groups.push({ ...group, ...groupInfo }); - - } - } - } - return groups; + return groupData; } - -export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => { - const {txList, setTxList, show} = useContext(MyContext) - const [invites, setInvites] = useState([]); - const [isLoading, setIsLoading] = useState(false); - - const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to - const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open - const listRef = useRef(); - - const getRequests = async () => { - try { - const response = await fetch(`${getBaseApiReact()}/groups/invites/${myAddress}/?limit=0`); - const inviteData = await response.json(); - - const resMoreData = await getGroupNames(inviteData) - setInvites(resMoreData); - } catch (error) { - console.error(error); +}; +export const getGroupNames = async (listOfGroups) => { + let groups = []; + if (listOfGroups && Array.isArray(listOfGroups)) { + for (const group of listOfGroups) { + const groupInfo = await getGroupInfo(group.groupId); + if (groupInfo) { + groups.push({ ...group, ...groupInfo }); } } - - useEffect(() => { - - getRequests(); - - }, []); - - const handlePopoverOpen = (event, index) => { - setPopoverAnchor(event.currentTarget); - setOpenPopoverIndex(index); - }; - - const handlePopoverClose = () => { - setPopoverAnchor(null); - setOpenPopoverIndex(null); - }; - - const handleJoinGroup = async (groupId, groupName)=> { - try { - - const fee = await getFee('JOIN_GROUP') - await show({ - message: "Would you like to perform an JOIN_GROUP transaction?" , - publishFee: fee.fee + ' QORT' - }) + } + return groups; +}; - setIsLoading(true); +export const UserListOfInvites = ({ + myAddress, + setInfoSnack, + setOpenSnack, +}) => { + const { txList, setTxList, show } = useContext(MyContext); + const [invites, setInvites] = useState([]); + const [isLoading, setIsLoading] = useState(false); - await new Promise((res, rej)=> { - window.sendMessage("joinGroup", { + const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to + const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open + const listRef = useRef(); + + const getRequests = async () => { + try { + const response = await fetch( + `${getBaseApiReact()}/groups/invites/${myAddress}/?limit=0` + ); + const inviteData = await response.json(); + + const resMoreData = await getGroupNames(inviteData); + setInvites(resMoreData); + } catch (error) { + console.error(error); + } + }; + + useEffect(() => { + getRequests(); + }, []); + + const handlePopoverOpen = (event, index) => { + setPopoverAnchor(event.currentTarget); + setOpenPopoverIndex(index); + }; + + const handlePopoverClose = () => { + setPopoverAnchor(null); + setOpenPopoverIndex(null); + }; + + const handleJoinGroup = async (groupId, groupName) => { + try { + const fee = await getFee('JOIN_GROUP'); // TODO translate + await show({ + message: 'Would you like to perform an JOIN_GROUP transaction?', + publishFee: fee.fee + ' QORT', + }); + + setIsLoading(true); + + await new Promise((res, rej) => { + window + .sendMessage('joinGroup', { groupId, }) - .then((response) => { - if (!response?.error) { - setTxList((prev) => [ - { - ...response, - type: 'joined-group', - label: `Joined Group ${groupName}: awaiting confirmation`, - labelDone: `Joined Group ${groupName}: success!`, - done: false, - groupId, - }, - ...prev, - ]); - res(response); - setInfoSnack({ - type: "success", - message: "Successfully requested to join group. It may take a couple of minutes for the changes to propagate", - }); - setOpenSnack(true); - handlePopoverClose(); - return; - } + .then((response) => { + if (!response?.error) { + setTxList((prev) => [ + { + ...response, + type: 'joined-group', + label: `Joined Group ${groupName}: awaiting confirmation`, + labelDone: `Joined Group ${groupName}: success!`, + done: false, + groupId, + }, + ...prev, + ]); + res(response); setInfoSnack({ - type: "error", - message: response?.error, + type: 'success', + message: + 'Successfully requested to join group. It may take a couple of minutes for the changes to propagate', }); setOpenSnack(true); - rej(response.error); - }) - .catch((error) => { - setInfoSnack({ - type: "error", - message: error.message || "An error occurred", - }); - setOpenSnack(true); - rej(error); + handlePopoverClose(); + return; + } + setInfoSnack({ + type: 'error', + message: response?.error, }); - - }) - - } catch (error) { - - } finally { - setIsLoading(false); - - } + setOpenSnack(true); + rej(response.error); + }) + .catch((error) => { + setInfoSnack({ + type: 'error', + message: error.message || 'An error occurred', + }); + setOpenSnack(true); + rej(error); + }); + }); + } catch (error) { + } finally { + setIsLoading(false); } - - const rowRenderer = ({ index, key, parent, style }) => { - const invite = invites[index]; - - return ( - - {({ measure }) => ( -
- - - { + const invite = invites[index]; + + return ( + + {({ measure }) => ( +
+ + + - Join {invite?.groupName} - Join {invite?.groupName} + handleJoinGroup(invite?.groupId, invite?.groupName)}>Join group - - - handlePopoverOpen(event, index)}> + variant="contained" + onClick={() => + handleJoinGroup(invite?.groupId, invite?.groupName) + } + > + Join group + + + + handlePopoverOpen(event, index)} + > {invite?.isOpen === false && ( - + + )} + {invite?.isOpen === true && ( + + )} + + + + +
)} - {invite?.isOpen === true && ( - - )} - - - -
-
- )} -
- ); - }; - - return ( - + ); + }; + + return ( + -

Invite list

-
+

Invite list

+
- - {({ height, width }) => ( - - )} - -
- - ); -} + + {({ height, width }) => ( + + )} + +
+
+ ); +}; diff --git a/src/components/Group/WalletsAppWrapper.tsx b/src/components/Group/WalletsAppWrapper.tsx index c569425..111e216 100644 --- a/src/components/Group/WalletsAppWrapper.tsx +++ b/src/components/Group/WalletsAppWrapper.tsx @@ -88,7 +88,7 @@ export const WalletsAppWrapper = () => { justifyContent: 'space-between', }} > - Q-Wallets + Q-Wallets // TODO translate { const socketRef = useRef(null); // WebSocket reference const timeoutIdRef = useRef(null); // Timeout ID reference const groupSocketTimeoutRef = useRef(null); // Group Socket Timeout reference - const initiateRef = useRef(null) + const initiateRef = useRef(null); const forceCloseWebSocket = () => { if (socketRef.current) { clearTimeout(timeoutIdRef.current); @@ -17,21 +21,20 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => { }; const logoutEventFunc = () => { - forceCloseWebSocket() + forceCloseWebSocket(); }; useEffect(() => { - subscribeToEvent("logout-event", logoutEventFunc); + subscribeToEvent('logout-event', logoutEventFunc); return () => { - unsubscribeFromEvent("logout-event", logoutEventFunc); + unsubscribeFromEvent('logout-event', logoutEventFunc); }; }, []); useEffect(() => { if (!myAddress) return; // Only proceed if myAddress is set - const pingHeads = () => { try { if (socketRef.current?.readyState === WebSocket.OPEN) { @@ -53,10 +56,9 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => { const currentAddress = myAddress; try { - if(!initiateRef.current) { - setIsLoadingGroups(true) - pauseAllQueues() - + if (!initiateRef.current) { + setIsLoadingGroups(true); + pauseAllQueues(); } const socketLink = `${getBaseApiReactSocket()}/websockets/chat/active/${currentAddress}?encoding=BASE64&haschatreference=false`; socketRef.current = new WebSocket(socketLink); @@ -71,34 +73,46 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => { clearTimeout(timeoutIdRef.current); groupSocketTimeoutRef.current = setTimeout(pingHeads, 45000); // Ping every 45 seconds } else { - if(!initiateRef.current) { - setIsLoadingGroups(false) - initiateRef.current = true - resumeAllQueues() - + if (!initiateRef.current) { + setIsLoadingGroups(false); + initiateRef.current = true; + resumeAllQueues(); } const data = JSON.parse(e.data); - const copyGroups = [...(data?.groups || [])] - const findIndex = copyGroups?.findIndex(item => item?.groupId === 0) - if(findIndex !== -1){ + const copyGroups = [...(data?.groups || [])]; + const findIndex = copyGroups?.findIndex( + (item) => item?.groupId === 0 + ); + if (findIndex !== -1) { copyGroups[findIndex] = { ...(copyGroups[findIndex] || {}), - groupId: "0" - } + groupId: '0', + }; } - const filteredGroups = copyGroups - const sortedGroups = filteredGroups.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); - const sortedDirects = (data?.direct || []).filter(item => - item?.name !== 'extension-proxy' && item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' - ).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); + const filteredGroups = copyGroups; + const sortedGroups = filteredGroups.sort( + (a, b) => (b.timestamp || 0) - (a.timestamp || 0) + ); + const sortedDirects = (data?.direct || []) + .filter( + (item) => + item?.name !== 'extension-proxy' && + item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' + ) + .sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); - window.sendMessage("handleActiveGroupDataFromSocket", { - groups: sortedGroups, - directs: sortedDirects, - }).catch((error) => { - console.error("Failed to handle active group data from socket:", error.message || "An error occurred"); + window + .sendMessage('handleActiveGroupDataFromSocket', { + groups: sortedGroups, + directs: sortedDirects, + }) + .catch((error) => { + // TODO translate + console.error( + 'Failed to handle active group data from socket:', + error.message || 'An error occurred' + ); }); - } } catch (error) { console.error('Error parsing onmessage data:', error); @@ -127,9 +141,7 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => { } }; - - initWebsocketMessageGroup(); // Initialize WebSocket on component mount - + initWebsocketMessageGroup(); // Initialize WebSocket on component mount return () => { forceCloseWebSocket(); // Clean up WebSocket on component unmount diff --git a/src/components/Group/useBlockUsers.tsx b/src/components/Group/useBlockUsers.tsx index eeb5361..178c358 100644 --- a/src/components/Group/useBlockUsers.tsx +++ b/src/components/Group/useBlockUsers.tsx @@ -1,218 +1,194 @@ -import React, { useCallback, useEffect, useRef } from "react"; -import { getBaseApiReact } from "../../App"; -import { truncate } from "lodash"; - - +import { useCallback, useEffect, useRef } from 'react'; export const useBlockedAddresses = () => { - const userBlockedRef = useRef({}) - const userNamesBlockedRef = useRef({}) - - const getAllBlockedUsers = useCallback(()=> { + const userBlockedRef = useRef({}); + const userNamesBlockedRef = useRef({}); + const getAllBlockedUsers = useCallback(() => { return { names: userNamesBlockedRef.current, - addresses: userBlockedRef.current - } - }, []) + addresses: userBlockedRef.current, + }; + }, []); - const isUserBlocked = useCallback((address, name)=> { + const isUserBlocked = useCallback((address, name) => { try { - if(!address) return false - if(userBlockedRef.current[address]) return true - return false - - + if (!address) return false; + if (userBlockedRef.current[address]) return true; + return false; } catch (error) { - //error + //error } - }, []) + }, []); - useEffect(()=> { - const fetchBlockedList = async ()=> { + useEffect(() => { + const fetchBlockedList = async () => { try { - const response = await new Promise((res, rej) => { - window.sendMessage("listActions", { - + const response = await new Promise((res, rej) => { + window + .sendMessage('listActions', { type: 'get', listName: `blockedAddresses`, - - }) - .then((response) => { - if (response.error) { - rej(response?.message); - return; - } else { - res(response); - } - }) - .catch((error) => { - console.error("Failed qortalRequest", error); - }); - }) - const blockedUsers = {} - response?.forEach((item)=> { - blockedUsers[item] = true - }) - userBlockedRef.current = blockedUsers + }) + .then((response) => { + if (response.error) { + rej(response?.message); + return; + } else { + res(response); + } + }) + .catch((error) => { + console.error('Failed qortalRequest', error); + }); + }); + const blockedUsers = {}; + response?.forEach((item) => { + blockedUsers[item] = true; + }); + userBlockedRef.current = blockedUsers; - const response2 = await new Promise((res, rej) => { - window.sendMessage("listActions", { - + const response2 = await new Promise((res, rej) => { + window + .sendMessage('listActions', { type: 'get', listName: `blockedNames`, - + }) + .then((response) => { + if (response.error) { + rej(response?.message); + return; + } else { + res(response); + } + }) + .catch((error) => { + console.error('Failed qortalRequest', error); + }); + }); + const blockedUsers2 = {}; + response2?.forEach((item) => { + blockedUsers2[item] = true; + }); + userNamesBlockedRef.current = blockedUsers2; + } catch (error) { + console.error(error); + } + }; + fetchBlockedList(); + }, []); + + const removeBlockFromList = useCallback(async (address, name) => { + if (name) { + await new Promise((res, rej) => { + window + .sendMessage('listActions', { + type: 'remove', + items: [name], + listName: 'blockedNames', }) .then((response) => { if (response.error) { rej(response?.message); return; } else { + const copyObject = { ...userNamesBlockedRef.current }; + delete copyObject[name]; + userNamesBlockedRef.current = copyObject; + res(response); } }) .catch((error) => { - console.error("Failed qortalRequest", error); + console.error('Failed qortalRequest', error); }); - }) - const blockedUsers2 = {} - response2?.forEach((item)=> { - blockedUsers2[item] = true - }) - userNamesBlockedRef.current = blockedUsers2 - - - } catch (error) { - console.error(error) - } - } - fetchBlockedList() - }, []) - - const removeBlockFromList = useCallback(async (address, name)=> { - if(name){ - await new Promise((res, rej) => { - window.sendMessage("listActions", { - - type: 'remove', - items: [name] , - listName: 'blockedNames' - - }) - .then((response) => { - if (response.error) { - rej(response?.message); - return; - } else { - - const copyObject = {...userNamesBlockedRef.current} - delete copyObject[name] - userNamesBlockedRef.current = copyObject - - - res(response); - } - }) - .catch((error) => { - console.error("Failed qortalRequest", error); - }); - }) + }); } - if(address){ + if (address) { await new Promise((res, rej) => { - window.sendMessage("listActions", { - + window + .sendMessage('listActions', { type: 'remove', items: [address], - listName: 'blockedAddresses' - - }) - .then((response) => { - if (response.error) { - rej(response?.message); - return; - } else { - - const copyObject = {...userBlockedRef.current} - delete copyObject[address] - userBlockedRef.current = copyObject - - - res(response); - } - }) - .catch((error) => { - console.error("Failed qortalRequest", error); - }); - }) - } - - - }, []) + listName: 'blockedAddresses', + }) + .then((response) => { + if (response.error) { + rej(response?.message); + return; + } else { + const copyObject = { ...userBlockedRef.current }; + delete copyObject[address]; + userBlockedRef.current = copyObject; - const addToBlockList = useCallback(async (address, name)=> { - if(name){ + res(response); + } + }) + .catch((error) => { + console.error('Failed qortalRequest', error); + }); + }); + } + }, []); + + const addToBlockList = useCallback(async (address, name) => { + if (name) { await new Promise((res, rej) => { - window.sendMessage("listActions", { - + window + .sendMessage('listActions', { type: 'add', items: [name], - listName: 'blockedNames' - - }) - .then((response) => { - if (response.error) { - rej(response?.message); - return; - } else { - const copyObject = {...userNamesBlockedRef.current} - copyObject[name] = true - userNamesBlockedRef.current = copyObject - - - res(response); - } - }) - .catch((error) => { - console.error("Failed qortalRequest", error); - }); - }) + listName: 'blockedNames', + }) + .then((response) => { + if (response.error) { + rej(response?.message); + return; + } else { + const copyObject = { ...userNamesBlockedRef.current }; + copyObject[name] = true; + userNamesBlockedRef.current = copyObject; + + res(response); + } + }) + .catch((error) => { + console.error('Failed qortalRequest', error); + }); + }); } - if(address){ + if (address) { await new Promise((res, rej) => { - window.sendMessage("listActions", { - + window + .sendMessage('listActions', { type: 'add', items: [address], - listName: 'blockedAddresses' - - }) - .then((response) => { - if (response.error) { - rej(response?.message); - return; - } else { - - const copyObject = {...userBlockedRef.current} - copyObject[address] = true - userBlockedRef.current = copyObject - - res(response); - } - }) - .catch((error) => { - console.error("Failed qortalRequest", error); - }); - }) + listName: 'blockedAddresses', + }) + .then((response) => { + if (response.error) { + rej(response?.message); + return; + } else { + const copyObject = { ...userBlockedRef.current }; + copyObject[address] = true; + userBlockedRef.current = copyObject; + + res(response); + } + }) + .catch((error) => { + console.error('Failed qortalRequest', error); + }); + }); } - - }, []) + }, []); return { isUserBlocked, addToBlockList, removeBlockFromList, - getAllBlockedUsers + getAllBlockedUsers, }; }; diff --git a/src/components/Group/useHandleUserInfo.tsx b/src/components/Group/useHandleUserInfo.tsx index a497259..622e737 100644 --- a/src/components/Group/useHandleUserInfo.tsx +++ b/src/components/Group/useHandleUserInfo.tsx @@ -1,32 +1,30 @@ -import React, { useCallback, useRef } from "react"; -import { getBaseApiReact } from "../../App"; - - +import { useCallback, useRef } from 'react'; +import { getBaseApiReact } from '../../App'; export const useHandleUserInfo = () => { - const userInfoRef = useRef({}) + const userInfoRef = useRef({}); - - const getIndividualUserInfo = useCallback(async (address)=> { + const getIndividualUserInfo = useCallback(async (address) => { try { - if(!address) return null - if(userInfoRef.current[address] !== undefined) return userInfoRef.current[address] + if (!address) return null; + if (userInfoRef.current[address] !== undefined) + return userInfoRef.current[address]; const url = `${getBaseApiReact()}/addresses/${address}`; - const response = await fetch(url); - if (!response.ok) { - throw new Error("network error"); - } - const data = await response.json(); - userInfoRef.current = { - ...userInfoRef.current, - [address]: data?.level - } - return data?.level + const response = await fetch(url); + if (!response.ok) { + throw new Error('network error'); + } + const data = await response.json(); + userInfoRef.current = { + ...userInfoRef.current, + [address]: data?.level, + }; + return data?.level; } catch (error) { - //error + //error } - }, []) + }, []); return { getIndividualUserInfo, diff --git a/src/components/Home/NewUsersCTA.tsx b/src/components/Home/NewUsersCTA.tsx index 4ad5c79..30dea8a 100644 --- a/src/components/Home/NewUsersCTA.tsx +++ b/src/components/Home/NewUsersCTA.tsx @@ -1,40 +1,40 @@ -import { Box, ButtonBase, Typography } from "@mui/material"; -import React from "react"; -import { Spacer } from "../../common/Spacer"; +import { Box, ButtonBase, Typography } from '@mui/material'; +import { Spacer } from '../../common/Spacer'; export const NewUsersCTA = ({ balance }) => { if (balance === undefined || +balance > 0) return null; return ( Are you a new user? - + {' '} + // TODO translate Please message us on Telegram or Discord if you need 4 QORT to start @@ -43,25 +43,25 @@ export const NewUsersCTA = ({ balance }) => { { if (window?.electronAPI?.openExternal) { window.electronAPI.openExternal( - "https://link.qortal.dev/telegram-invite" + 'https://link.qortal.dev/telegram-invite' ); } else { window.open( - "https://link.qortal.dev/telegram-invite", - "_blank" + 'https://link.qortal.dev/telegram-invite', + '_blank' ); } }} @@ -70,15 +70,15 @@ export const NewUsersCTA = ({ balance }) => { { if (window?.electronAPI?.openExternal) { window.electronAPI.openExternal( - "https://link.qortal.dev/discord-invite" + 'https://link.qortal.dev/discord-invite' ); } else { - window.open("https://link.qortal.dev/discord-invite", "_blank"); + window.open('https://link.qortal.dev/discord-invite', '_blank'); } }} > diff --git a/src/components/Home/QortPrice.tsx b/src/components/Home/QortPrice.tsx index f4586fb..2f118c1 100644 --- a/src/components/Home/QortPrice.tsx +++ b/src/components/Home/QortPrice.tsx @@ -1,8 +1,9 @@ -import React, { useCallback, useEffect, useState } from "react"; -import { getBaseApiReact } from "../../App"; -import { Box, Tooltip, Typography } from "@mui/material"; -import { BarSpinner } from "../../common/Spinners/BarSpinner/BarSpinner"; -import { formatDate } from "../../utils/time"; +import { useCallback, useEffect, useState } from 'react'; +import { getBaseApiReact } from '../../App'; +import { Box, Tooltip, Typography, useTheme } from '@mui/material'; +import { BarSpinner } from '../../common/Spinners/BarSpinner/BarSpinner'; +import { formatDate } from '../../utils/time'; +import { useTranslation } from 'react-i18next'; function getAverageLtcPerQort(trades) { let totalQort = 0; @@ -38,6 +39,8 @@ export const QortPrice = () => { const [supply, setSupply] = useState(null); const [lastBlock, setLastBlock] = useState(null); const [loading, setLoading] = useState(true); + const { t } = useTranslation(['core', 'tutorial']); + const theme = useTheme(); const getPrice = useCallback(async () => { try { @@ -101,64 +104,63 @@ export const QortPrice = () => { return () => clearInterval(interval); }, [getPrice]); - return ( + Based on the latest 20 trades } placement="bottom" arrow - sx={{ fontSize: "24" }} + sx={{ fontSize: '24' }} slotProps={{ tooltip: { sx: { - color: "#ffffff", - backgroundColor: "#444444", + color: theme.palette.text.primary, + backgroundColor: theme.palette.background.default, }, }, arrow: { sx: { - color: "#444444", + color: theme.palette.text.primary, }, }, }} > - Price + {t('core:price', { postProcess: 'capitalize' })} + {!ltcPerQort ? ( ) : ( {ltcPerQort} LTC/QORT @@ -168,28 +170,28 @@ export const QortPrice = () => { - Supply + {t('core:supply', { postProcess: 'capitalize' })} + {!supply ? ( ) : ( {supply} QORT @@ -197,60 +199,58 @@ export const QortPrice = () => { )} - {lastBlock?.timestamp && formatDate(lastBlock?.timestamp)} - - } - placement="bottom" - arrow - sx={{ fontSize: "24" }} - slotProps={{ - tooltip: { - sx: { - color: "#ffffff", - backgroundColor: "#444444", - }, + title={ + + {lastBlock?.timestamp && formatDate(lastBlock?.timestamp)} + + } + placement="bottom" + arrow + sx={{ fontSize: '24' }} + slotProps={{ + tooltip: { + sx: { + color: theme.palette.text.primary, + backgroundColor: theme.palette.background.default, }, - arrow: { - sx: { - color: "#444444", - }, + }, + arrow: { + sx: { + color: theme.palette.text.primary, }, - }} - > - - + - Last height + {t('core:last_height', { postProcess: 'capitalize' })} + {!lastBlock?.height ? ( ) : ( {lastBlock?.height} )} - - + ); diff --git a/src/components/Minting/Minting.tsx b/src/components/Minting/Minting.tsx index 84697c8..7187986 100644 --- a/src/components/Minting/Minting.tsx +++ b/src/components/Minting/Minting.tsx @@ -206,7 +206,6 @@ export const Minting = ({ window .sendMessage( 'ADMIN_ACTION', - { type: 'addmintingaccount', value: val, @@ -266,7 +265,7 @@ export const Minting = ({ rej({ message: response.error }); }) .catch((error) => { - rej({ message: error.message || 'An error occurred' }); + rej({ message: error.message || 'An error occurred' }); //TODO translate }); }); } catch (error) { diff --git a/src/components/QMailStatus.tsx b/src/components/QMailStatus.tsx index c760751..dd6f9be 100644 --- a/src/components/QMailStatus.tsx +++ b/src/components/QMailStatus.tsx @@ -1,13 +1,14 @@ import { useMemo } from 'react'; -import EmailIcon from '@mui/icons-material/Email'; import { useRecoilState } from 'recoil'; import { mailsAtom, qMailLastEnteredTimestampAtom } from '../atoms/global'; import { isLessThanOneWeekOld } from './Group/QMailMessages'; import { ButtonBase, Tooltip, useTheme } from '@mui/material'; import { executeEvent } from '../utils/events'; import { Mail } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; export const QMailStatus = () => { + const { t } = useTranslation(['core']); const theme = useTheme(); const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState( @@ -63,7 +64,9 @@ export const QMailStatus = () => { fontWeight: 700, }} > - Q-MAIL + {t('core:q_mail', { + postProcess: 'capitalize', + })} } placement="left" diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index c8196d8..f0fbc4f 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -1,6 +1,6 @@ -import React, { useContext, useEffect, useMemo, useState } from 'react'; +import { useContext, useEffect, useMemo, useState } from 'react'; import { useRecoilState, useSetRecoilState } from 'recoil'; -import isEqual from 'lodash/isEqual'; // Import deep comparison utility +import isEqual from 'lodash/isEqual'; // TODO Import deep comparison utility import { canSaveSettingToQdnAtom, hasSettingsChangedAtom, @@ -26,6 +26,7 @@ import { base64ToUint8Array, uint8ArrayToObject, } from '../../backgroundFunctions/encryption'; +import { useTranslation } from 'react-i18next'; export const handleImportClick = async () => { const fileInput = document.createElement('input'); @@ -77,6 +78,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { const [anchorEl, setAnchorEl] = useState(null); const { show } = useContext(MyContext); + const { t } = useTranslation(['core']); + const hasChanged = useMemo(() => { const newChanges = { sortablePinnedApps: pinnedApps.map((item) => { @@ -146,8 +149,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { const fee = await getFee('ARBITRARY'); await show({ - message: - 'Would you like to publish your settings to QDN (encrypted) ?', + message: t('core:save.publish_qnd', { postProcess: 'capitalize' }), publishFee: fee.fee + ' QORT', }); const response = await new Promise((res, rej) => { @@ -165,7 +167,10 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { rej(response.error); }) .catch((error) => { - rej(error.message || 'An error occurred'); + rej( + error.message || + t('core:result.error.generic', { postProcess: 'capitalize' }) + ); }); }); if (response?.identifier) { @@ -173,7 +178,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { setSettingsQdnLastUpdated(Date.now()); setInfoSnack({ type: 'success', - message: 'Sucessfully published to QDN', + message: t('core:result.success.publish_qdn', { + postProcess: 'capitalize', + }), }); setOpenSnack(true); setAnchorEl(null); @@ -182,7 +189,11 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { } catch (error) { setInfoSnack({ type: 'error', - message: error?.message || 'Unable to save to QDN', + message: + error?.message || + t('core:result.error.save_qdn', { + postProcess: 'capitalize', + }), }); setOpenSnack(true); } finally { @@ -214,7 +225,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { {isDesktop ? ( { )} + { {isUsingImportExportSettings && ( { fontSize: '14px', }} > - You are using the export/import way of saving settings. - + {t('core:save_options.settings', { + postProcess: 'capitalize', + })} + {' '} @@ -302,10 +320,10 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { {!isUsingImportExportSettings && ( @@ -323,8 +341,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { fontSize: '14px', }} > - You need a registered Qortal name to save your pinned apps to - QDN. + {t('core:save_options.register_name', { + postProcess: 'capitalize', + })} ) : ( @@ -343,10 +362,13 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { fontSize: '14px', }} > - You have unsaved changes to your pinned apps. Save them to - QDN. + {t('core:save_options.unsaved_changes', { + postProcess: 'capitalize', + })} + + { onClick={saveToQdn} variant="contained" > - Save to QDN + {t('core:save_options.save_qdn', { + postProcess: 'capitalize', + })} {!isNaN(settingsQdnLastUpdated) && @@ -375,8 +399,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { fontSize: '14px', }} > - Don't like your current local changes? Would you - like to reset to your saved QDN pinned apps? + {t('core:save_options.reset_qdn', { + postProcess: 'capitalize', + })} { }, }} > - Revert to QDN + {t('core:save_options.revert_qdn', { + postProcess: 'capitalize', + })} )} @@ -408,8 +435,10 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { fontSize: '14px', }} > - Don't like your current local changes? Would you - like to reset to the default pinned apps? + {' '} + {t('core:save_options.reset_pinned', { + postProcess: 'capitalize', + })} { onClick={revertChanges} variant="contained" > - Revert to default + {t('core:save_options.revert_default', { + postProcess: 'capitalize', + })} )} @@ -439,8 +470,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { fontSize: '14px', }} > - The app was unable to download your existing QDN-saved - pinned apps. Would you like to overwrite those changes? + {t('core:save_options.overwrite_changes', { + postProcess: 'capitalize', + })} { }, }} > - Overwrite to QDN + {t('core:save_options.overwrite_qdn', { + postProcess: 'capitalize', + })}
)} @@ -478,7 +512,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { fontSize: '14px', }} > - You currently do not have any changes to your pinned apps + {t('core:save_options.no_pinned_changes', { + postProcess: 'capitalize', + })}
)} @@ -533,8 +569,11 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { } }} > - Import + {t('core:import', { + postProcess: 'capitalize', + })} + { try { @@ -555,7 +594,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { } }} > - Export + {t('core:export', { + postProcess: 'capitalize', + })} diff --git a/src/components/Theme/ThemeSelector.tsx b/src/components/Theme/ThemeSelector.tsx index 238ca00..42b75c0 100644 --- a/src/components/Theme/ThemeSelector.tsx +++ b/src/components/Theme/ThemeSelector.tsx @@ -2,8 +2,10 @@ import { useThemeContext } from './ThemeContext'; import { IconButton, Tooltip } from '@mui/material'; import LightModeIcon from '@mui/icons-material/LightMode'; import DarkModeIcon from '@mui/icons-material/DarkMode'; +import { useTranslation } from 'react-i18next'; const ThemeSelector = () => { + const { t } = useTranslation(['core']); const { themeMode, toggleTheme } = useThemeContext(); return ( @@ -16,7 +18,17 @@ const ThemeSelector = () => { position: 'absolute', }} > - + {themeMode === 'dark' ? : } diff --git a/src/components/Tutorials/Tutorials.tsx b/src/components/Tutorials/Tutorials.tsx index 382774c..06274e7 100644 --- a/src/components/Tutorials/Tutorials.tsx +++ b/src/components/Tutorials/Tutorials.tsx @@ -13,11 +13,13 @@ import { } from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; import { VideoPlayer } from '../Embeds/VideoPlayer'; +import { useTranslation } from 'react-i18next'; export const Tutorials = () => { const { openTutorialModal, setOpenTutorialModal } = useContext(GlobalContext); const [multiNumber, setMultiNumber] = useState(0); const theme = useTheme(); + const { t } = useTranslation(['core', 'tutorial']); const handleClose = () => { setOpenTutorialModal(null); @@ -61,9 +63,7 @@ export const Tutorials = () => { })} - - {selectedTutorial?.title} {` Tutorial`} - + {selectedTutorial?.title} { @@ -138,7 +138,7 @@ export const Tutorials = () => { diff --git a/src/components/Tutorials/useHandleTutorials.tsx b/src/components/Tutorials/useHandleTutorials.tsx index 1df8626..5fde392 100644 --- a/src/components/Tutorials/useHandleTutorials.tsx +++ b/src/components/Tutorials/useHandleTutorials.tsx @@ -8,6 +8,7 @@ import navigationImg from './img/navigation.webp'; import overviewImg from './img/overview.webp'; import startedImg from './img/started.webp'; import obtainingImg from './img/obtaining-qort.jpg'; +import { useTranslation } from 'react-i18next'; const checkIfGatewayIsOnline = async () => { try { @@ -27,9 +28,11 @@ const checkIfGatewayIsOnline = async () => { return false; } }; + export const useHandleTutorials = () => { const [openTutorialModal, setOpenTutorialModal] = useState(null); const [shownTutorials, setShowTutorials] = useState(null); + const { t } = useTranslation(['core', 'tutorial']); useEffect(() => { try { @@ -104,7 +107,9 @@ export const useHandleTutorials = () => { setOpenTutorialModal({ multi: [ { - title: '1. Getting Started', + title: t('tutorial:1_getting_started', { + postProcess: 'capitalize', + }), resource: { name: 'a-test', service: 'VIDEO', @@ -113,7 +118,9 @@ export const useHandleTutorials = () => { }, }, { - title: '2. Overview', + title: t('tutorial:2_overview', { + postProcess: 'capitalize', + }), resource: { name: 'a-test', service: 'VIDEO', @@ -122,7 +129,9 @@ export const useHandleTutorials = () => { }, }, { - title: '3. Qortal Groups', + title: t('tutorial:3_groups', { + postProcess: 'capitalize', + }), resource: { name: 'a-test', service: 'VIDEO', @@ -131,7 +140,9 @@ export const useHandleTutorials = () => { }, }, { - title: '4. Obtaining Qort', + title: t('tutorial:4_obtain_qort', { + postProcess: 'capitalize', + }), resource: { name: 'a-test', service: 'VIDEO', @@ -151,7 +162,9 @@ export const useHandleTutorials = () => { setOpenTutorialModal({ multi: [ { - title: '1. Apps Dashboard', + title: t('tutorial:apps.dashboard', { + postProcess: 'capitalize', + }), resource: { name: 'a-test', service: 'VIDEO', @@ -160,7 +173,9 @@ export const useHandleTutorials = () => { }, }, { - title: '2. Apps Navigation', + title: t('tutorial:apps.navigation', { + postProcess: 'capitalize', + }), resource: { name: 'a-test', service: 'VIDEO', diff --git a/src/main.tsx b/src/main.tsx index 69e8901..3d64137 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -6,6 +6,7 @@ import { MessageQueueProvider } from './MessageQueueContext.tsx'; import { RecoilRoot } from 'recoil'; import { ThemeProvider } from './components/Theme/ThemeContext.tsx'; import { CssBaseline } from '@mui/material'; +import '../i18n'; ReactDOM.createRoot(document.getElementById('root')!).render( <> diff --git a/src/styles/App-styles.ts b/src/styles/App-styles.ts index 9b385bc..980657b 100644 --- a/src/styles/App-styles.ts +++ b/src/styles/App-styles.ts @@ -130,10 +130,10 @@ export const CustomButton = styled(Box)(({ theme }) => ({ transition: 'all 0.2s', width: 'fit-content', '&:hover': { - backgroundColor: theme.palette.background.default, - color: '#fff', + backgroundColor: theme.palette.background.paper, + color: theme.palette.text.secondary, 'svg path': { - fill: '#fff', + fill: theme.palette.background.paper, }, }, }));