Merge branch 'develop' into feature/add-theme

This commit is contained in:
nico.benaz 2025-04-24 19:21:05 +02:00 committed by GitHub
commit 3d17303d84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
74 changed files with 3373 additions and 2018 deletions

View File

@ -18,4 +18,10 @@ Leveraging a redundant set of publicly accessible nodes provided by crowetic, Qo
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/<locale>` folder.
See [guidelines](./docs/i18n_languages.md).

84
docs/contribution.md Normal file
View File

@ -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
```

10
docs/i18n_languages.md Normal file
View File

@ -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' })}`

49
i18n.js Normal file
View File

@ -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;

161
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}
}

View File

@ -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": "добро пожаловать в"
}

View File

@ -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": "добро пожаловать"
}

View File

@ -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"
}
}

View File

@ -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<any>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingSendCoin, setIsLoadingSendCoin] = useState<boolean>(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')}
</span>
}
placement="left"
@ -1589,9 +1593,10 @@ function App() {
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
SETTINGS
{t('core:settings')}
</span>
}
placement="left"
@ -1628,9 +1633,10 @@ function App() {
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
USER LOOKUP
{t('core:user_lookup')}
</span>
}
placement="left"
@ -1667,9 +1673,10 @@ function App() {
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
WALLETS
{t('core:wallet_other')}
</span>
}
placement="left"
@ -1703,9 +1710,10 @@ function App() {
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
YOUR ACCOUNT
{t('auth:account.your')}
</span>
}
placement="left"
@ -1813,9 +1821,10 @@ function App() {
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
MINTING STATUS
{t('core:minting_status')}
</span>
}
placement="left"
@ -1857,9 +1866,10 @@ function App() {
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
TUTORIAL
{t('core:tutorial')}
</span>
}
placement="left"
@ -1894,8 +1904,14 @@ function App() {
>
<Tooltip
title={
<span style={{ fontSize: '14px', fontWeight: 700 }}>
BACKUP WALLET
<span
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
{t('core:backup_wallet')}
</span>
}
placement="left"
@ -2083,6 +2099,7 @@ function App() {
{messageQortalRequest?.text1}
</TextP>
</Box>
{messageQortalRequest?.text2 && (
<>
<Spacer height="10px" />
@ -2124,6 +2141,7 @@ function App() {
>
{messageQortalRequest?.text3}
</TextP>
<Spacer height="15px" />
</Box>
</>
@ -2158,11 +2176,11 @@ function App() {
<TextP
sx={{
textAlign: 'center',
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 700,
lineHeight: 1.2,
maxWidth: '90%',
textAlign: 'center',
}}
>
{messageQortalRequest?.highlightedText}
@ -2379,17 +2397,7 @@ function App() {
>
{sendqortState?.amount} QORT
</TextP>
{/* <Spacer height="29px" />
<CustomLabel htmlFor="standard-adornment-password">
Confirm Wallet Password
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={paymentPassword}
onChange={(e) => setPaymentPassword(e.target.value)}
/> */}
<Spacer height="29px" />
<Box
sx={{
@ -2529,7 +2537,7 @@ function App() {
setExtstate('create-wallet');
}}
>
Create account
{t('auth:create_account', { postProcess: 'capitalize' })}
</CustomButton>
</>
)}
@ -2624,7 +2632,7 @@ function App() {
fontWeight: 600,
}}
>
Authenticate
{t('auth:authenticate', { postProcess: 'capitalize' })}
</TextP>
</Box>
@ -2632,7 +2640,7 @@ function App() {
<>
<CustomLabel htmlFor="standard-adornment-password">
Wallet Password
{t('auth:wallet.password', { postProcess: 'capitalize' })}
</CustomLabel>
<Spacer height="5px" />
@ -2656,7 +2664,8 @@ function App() {
fontSize: '12px',
}}
>
{'Using node: '} {currentNode?.url}
{t('auth:node.using', { postProcess: 'capitalize' })}:{' '}
{currentNode?.url}
</Typography>
</>
) : (
@ -2667,7 +2676,7 @@ function App() {
fontSize: '12px',
}}
>
{'Using public node'}
{t('auth:node.using_public', { postProcess: 'capitalize' })}
</Typography>
</>
)}
@ -2675,7 +2684,7 @@ function App() {
<Spacer height="20px" />
<CustomButton onClick={authenticateWallet}>
Authenticate
{t('auth:authenticate', { postProcess: 'capitalize' })}
</CustomButton>
<ErrorText>{walletToBeDecryptedError}</ErrorText>
@ -2703,7 +2712,9 @@ function App() {
onClick={returnToMain}
/>
</Box>
<Spacer height="10px" />
<div
className="image-container"
style={{
@ -2731,7 +2742,7 @@ function App() {
fontWeight: 600,
}}
>
Download Account
{t('auth:download_account', { postProcess: 'capitalize' })}
</TextP>
</Box>
@ -2740,9 +2751,13 @@ function App() {
{!walletToBeDownloaded && (
<>
<CustomLabel htmlFor="standard-adornment-password">
Confirm Wallet Password
{t('auth:wallet.password_confirmation', {
postProcess: 'capitalize',
})}
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={walletToBeDownloadedPassword}
@ -2750,9 +2765,13 @@ function App() {
setWalletToBeDownloadedPassword(e.target.value)
}
/>
<Spacer height="20px" />
<CustomButton onClick={confirmPasswordToDownload}>
Confirm password
{t('auth:password_confirmation', {
postProcess: 'capitalize',
})}
</CustomButton>
<ErrorText>{walletToBeDownloadedError}</ErrorText>
</>
@ -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',
})}
</CustomButton>
</>
)}
@ -3024,7 +3047,7 @@ function App() {
<Spacer height="17px" />
<CustomButton onClick={createAccountFunc}>
Create Account
{t('auth:create_account', { postProcess: 'capitalize' })}
</CustomButton>
</Box>
<ErrorText>{walletToBeDownloadedError}</ErrorText>

View File

@ -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) => (
<Tooltip {...props} classes={{ popper: className }} />
))(({ 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 = ({
>
<img src={Logo1Dark} className="base-image" />
</div>
<Spacer height="30px" />
<TextP
sx={{
textAlign: 'center',
@ -463,7 +480,7 @@ export const NotAuthenticated = ({
fontSize: '18px',
}}
>
WELCOME TO
{t('auth:welcome', { postProcess: 'capitalize' })}
<TextSpan
sx={{
fontSize: '18px',
@ -499,18 +516,15 @@ export const NotAuthenticated = ({
transaction you make is linked to your ID, and this is where you
manage all your QORT and other tradeable cryptocurrencies on
Qortal.
</Typography>
</Typography>{' '}
// TODO translate
</React.Fragment>
}
>
<CustomButton onClick={() => setExtstate('wallets')}>
{/* <input {...getInputProps()} /> */}
Accounts
{t('auth:account.account_many', { postProcess: 'capitalize' })}
</CustomButton>
</HtmlTooltip>
{/* <Tooltip title="Authenticate by importing your Qortal JSON file" arrow>
<img src={Info} />
</Tooltip> */}
</Box>
<Spacer height="6px" />
@ -534,7 +548,8 @@ export const NotAuthenticated = ({
}}
>
New users start here!
</Typography>
</Typography>{' '}
// TODO translate
<Spacer height="10px" />
<Typography
color="inherit"
@ -546,7 +561,8 @@ export const NotAuthenticated = ({
to start using Qortal. Once you have made your account, you can
start doing things like obtaining some QORT, buying a name and
avatar, publishing videos and blogs, and much more.
</Typography>
</Typography>{' '}
// TODO translate
</React.Fragment>
}
>
@ -565,10 +581,11 @@ export const NotAuthenticated = ({
},
}}
>
Create account
{t('auth:create_account', { postProcess: 'capitalize' })}
</CustomButton>
</HtmlTooltip>
</Box>
<Spacer height="15px" />
<Typography
@ -577,8 +594,10 @@ export const NotAuthenticated = ({
visibility: !useLocalNode && 'hidden',
}}
>
{'Using node: '} {currentNode?.url}
{t('auth:node.using', { postProcess: 'capitalize' })}:{' '}
{currentNode?.url}
</Typography>
<>
<Spacer height="15px" />
<Box
@ -603,7 +622,7 @@ export const NotAuthenticated = ({
textDecoration: 'underline',
}}
>
For advanced users
{t('auth:advanced_users', { postProcess: 'capitalize' })}
</Typography>
<Box
sx={{
@ -628,7 +647,7 @@ export const NotAuthenticated = ({
},
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track':
{
backgroundColor: 'white', // Change track color when checked
backgroundColor: theme.palette.background.default,
},
}}
checked={useLocalNode}
@ -659,7 +678,11 @@ export const NotAuthenticated = ({
disabled={false}
/>
}
label={`Use ${isLocal ? 'Local' : 'Custom'} Node`}
label={
isLocal
? t('auth:node.use_local', { postProcess: 'capitalize' })
: t('auth:node.use_custom', { postProcess: 'capitalize' })
}
/>
</Box>
{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' })}
</Button>
<Typography
sx={{
fontSize: '12px',
visibility: importedApiKey ? 'visible' : 'hidden',
}}
>{`api key : ${importedApiKey}`}</Typography>
>
{t('auth:apikey.key', { postProcess: 'capitalize' })}: $
{importedApiKey}
</Typography>
</>
)}
<Button
@ -688,19 +716,21 @@ export const NotAuthenticated = ({
variant="contained"
component="label"
>
Choose custom node
{t('auth:node.choose', { postProcess: 'capitalize' })}
</Button>
</>
<Typography
sx={{
color: 'white',
color: theme.palette.text.primary,
fontSize: '12px',
}}
>
Build version: {manifestData?.version}
{t('auth:build_version', { postProcess: 'capitalize' })}:
{manifestData?.version}
</Typography>
</Box>
</>
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}
@ -714,7 +744,10 @@ export const NotAuthenticated = ({
aria-describedby="alert-dialog-description"
fullWidth
>
<DialogTitle id="alert-dialog-title">{'Custom nodes'}</DialogTitle>
<DialogTitle id="alert-dialog-title">
{' '}
{t('auth:node.custom_many', { postProcess: 'capitalize' })}:
</DialogTitle>
<DialogContent>
<Box
sx={{
@ -742,7 +775,7 @@ export const NotAuthenticated = ({
>
<Typography
sx={{
color: 'white',
color: theme.palette.text.primary,
fontSize: '14px',
}}
>
@ -783,7 +816,7 @@ export const NotAuthenticated = ({
}}
variant="contained"
>
Choose
{t('core:choose', { postProcess: 'capitalize' })}
</Button>
</Box>
</Box>
@ -799,7 +832,7 @@ export const NotAuthenticated = ({
>
<Typography
sx={{
color: 'white',
color: theme.palette.text.primary,
fontSize: '14px',
}}
>
@ -842,8 +875,9 @@ export const NotAuthenticated = ({
}}
variant="contained"
>
Choose
{t('core:choose', { postProcess: 'capitalize' })}
</Button>
<Button
size="small"
onClick={() => {
@ -854,20 +888,20 @@ export const NotAuthenticated = ({
}}
variant="contained"
>
Edit
{t('core:edit', { postProcess: 'capitalize' })}
</Button>
<Button
size="small"
onClick={() => {
const nodesToSave = [
...(customNodes || []),
].filter((item) => item?.url !== node?.url);
saveCustomNodes(nodesToSave, true);
}}
variant="contained"
>
Remove
{t('core:remove', { postProcess: 'capitalize' })}
</Button>
</Box>
</Box>
@ -902,7 +936,14 @@ export const NotAuthenticated = ({
)}
</Box>
</DialogContent>
<DialogActions>
{mode === 'list' && (
<Button variant="contained" onClick={addCustomNode}>
{t('core:add', { postProcess: 'capitalize' })}
</Button>
)}
{mode === 'list' && (
<>
<Button
@ -912,15 +953,10 @@ export const NotAuthenticated = ({
}}
autoFocus
>
Close
{t('core:close', { postProcess: 'capitalize' })}
</Button>
</>
)}
{mode === 'list' && (
<Button variant="contained" onClick={addCustomNode}>
Add
</Button>
)}
{mode === 'add-node' && (
<>
@ -931,7 +967,7 @@ export const NotAuthenticated = ({
setCustomNodeToSaveIndex(null);
}}
>
Return to list
{t('auth:return_to_list', { postProcess: 'capitalize' })}
</Button>
<Button
@ -940,7 +976,7 @@ export const NotAuthenticated = ({
onClick={() => saveCustomNodes(customNodes)}
autoFocus
>
Save
{t('core:save', { postProcess: 'capitalize' })}
</Button>
</>
)}
@ -954,7 +990,9 @@ export const NotAuthenticated = ({
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{'Enter apikey'}</DialogTitle>
<DialogTitle id="alert-dialog-title">
{t('auth:apikey.enter', { postProcess: 'capitalize' })}
</DialogTitle>
<DialogContent>
<Box
sx={{
@ -972,7 +1010,7 @@ export const NotAuthenticated = ({
variant="contained"
component="label"
>
Alternative: File select
{t('auth:apikey.alternative', { postProcess: 'capitalize' })}
<input
type="file"
accept=".txt"
@ -982,17 +1020,8 @@ export const NotAuthenticated = ({
</Button>
</Box>
</DialogContent>
<DialogActions>
<Button
variant="contained"
onClick={() => {
setEnteredApiKey('');
setShowSelectApiKey(false);
}}
>
Close
</Button>
<DialogActions>
<Button
variant="contained"
disabled={!enteredApiKey}
@ -1036,7 +1065,17 @@ export const NotAuthenticated = ({
}}
autoFocus
>
Save
{t('core:save', { postProcess: 'capitalize' })}
</Button>
<Button
variant="contained"
onClick={() => {
setEnteredApiKey('');
setShowSelectApiKey(false);
}}
>
{t('core:close', { postProcess: 'capitalize' })}
</Button>
</DialogActions>
</Dialog>

View File

@ -1,3 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import {
AppCircle,
AppCircleContainer,

View File

@ -1,3 +1,4 @@
import React from 'react';
import {
AppCircle,
AppCircleContainer,

View File

@ -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',
}}
>
<h3>Core Information</h3>
<h3>{t('core:core.information', { postProcess: 'capitalize' })}</h3>
<h4 className="lineHeight">
Core Version:{' '}
{t('core:core.version', { postProcess: 'capitalize' })}:{' '}
<span style={{ color: '#03a9f4' }}>{buildVersion}</span>
</h4>
<h4 className="lineHeight">{message}</h4>
<h4 className="lineHeight">
Block Height:{' '}
{t('core:core.block_height', { postProcess: 'capitalize' })}:{' '}
<span style={{ color: '#03a9f4' }}>{height || ''}</span>
</h4>
<h4 className="lineHeight">
Connected Peers:{' '}
{t('core:core.peers', { postProcess: 'capitalize' })}:{' '}
<span style={{ color: '#03a9f4' }}>
{numberOfConnections || ''}
</span>
</h4>
<h4 className="lineHeight">
Using public node:{' '}
{t('auth:node.using_public', { postProcess: 'capitalize' })}:{' '}
<span style={{ color: '#03a9f4' }}>
{isUsingGateway?.toString()}
</span>

View File

@ -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']);
return (
<Box
sx={{
display: "flex",
gap: "20px",
flexWrap: "wrap",
display: 'flex',
gap: '20px',
flexWrap: 'wrap',
}}
>
<ButtonBase
sx={{
"&:hover": { backgroundColor: "secondary.main" },
transition: "all 0.1s ease-in-out",
padding: "5px",
borderRadius: "5px",
gap: "5px",
'&:hover': { backgroundColor: theme.palette.background.paper },
borderRadius: '5px',
gap: '5px',
padding: '5px',
transition: 'all 0.1s ease-in-out',
}}
onClick={async () => {
executeEvent("addTab", {
data: { service: "APP", name: "q-trade" },
executeEvent('addTab', {
data: { service: 'APP', name: 'q-trade' },
});
executeEvent("open-apps-mode", {});
executeEvent('open-apps-mode', {});
}}
>
<img
style={{
borderRadius: "50%",
height: '30px'
borderRadius: '50%',
height: '30px',
}}
src={qTradeLogo}
/>
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
}}
>
Trade QORT
{t('tutorial:initial.trade_qort', { postProcess: 'capitalize' })}
</Typography>
</ButtonBase>
<ButtonBase
sx={{
"&:hover": { backgroundColor: "secondary.main" },
transition: "all 0.1s ease-in-out",
padding: "5px",
borderRadius: "5px",
gap: "5px",
'&:hover': { backgroundColor: theme.palette.background.paper },
borderRadius: '5px',
gap: '5px',
padding: '5px',
transition: 'all 0.1s ease-in-out',
}}
onClick={() => {
setDesktopViewMode('apps')
setDesktopViewMode('apps');
}}
>
<AppsIcon
sx={{
color: "white",
color: theme.palette.text.primary,
}}
/>
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
}}
>
See Apps
{t('tutorial:initial.see_apps', { postProcess: 'capitalize' })}
</Typography>
</ButtonBase>
<ButtonBase
sx={{
"&:hover": { backgroundColor: "secondary.main" },
transition: "all 0.1s ease-in-out",
padding: "5px",
borderRadius: "5px",
gap: "5px",
'&:hover': { backgroundColor: theme.palette.background.paper },
borderRadius: '5px',
gap: '5px',
padding: '5px',
transition: 'all 0.1s ease-in-out',
}}
onClick={async () => {
executeEvent("openGroupMessage", {
from: "0" ,
executeEvent('openGroupMessage', {
from: '0',
});
}}
>
<ChatIcon
sx={{
color: "white",
color: theme.palette.text.primary,
}}
/>
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
}}
>
General Chat
{t('tutorial:initial.general_chat', { postProcess: 'capitalize' })}
</Typography>
</ButtonBase>
<ButtonBase
sx={{
"&:hover": { backgroundColor: "secondary.main" },
transition: "all 0.1s ease-in-out",
padding: "5px",
borderRadius: "5px",
gap: "5px",
'&:hover': { backgroundColor: theme.palette.background.paper },
transition: 'all 0.1s ease-in-out',
padding: '5px',
borderRadius: '5px',
gap: '5px',
}}
onClick={async () => {
executeEvent("openWalletsApp", {
});
executeEvent('openWalletsApp', {});
}}
>
<AccountBalanceWalletIcon
sx={{
color: "white",
color: theme.palette.text.primary,
}}
/>
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
}}
>
Wallets
{t('core:wallet_other', { postProcess: 'capitalize' })}
</Typography>
</ButtonBase>
</Box>

View File

@ -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')}
</span>
}
placement="left"

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -1,20 +1,19 @@
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",
display: 'flex',
fontFamily: 'Mulish',
fontSize: '19px',
fontWeight: 400,
letterSpacing: 0,
color: theme.palette.text.primary,
width: '100%'
width: '100%',
}));
export const DisplayHtml = ({ html, textColor }: any) => {
@ -29,6 +28,7 @@ export const DisplayHtml = ({ html, textColor }: any) => {
}, [html]);
if (!cleanContent) return null;
return (
<CrowdfundInlineContent>
<div
@ -36,7 +36,7 @@ export const DisplayHtml = ({ html, textColor }: any) => {
style={{
color: textColor || 'white',
fontWeight: 400,
fontSize: '16px'
fontSize: '16px',
}}
dangerouslySetInnerHTML={{ __html: cleanContent }}
/>

View File

@ -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}
</ThreadSingleTitle>
<Spacer height="10px" />
{filterMode === 'Recently active' && (
<div
style={{
@ -799,7 +795,7 @@ export const GroupMail = ({
sx={{
color: 'white',
fontSize: '12px',
}}
}} // TODO translate
>
Last page
</Typography>

View File

@ -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,7 +667,7 @@ export const GroupNameP = styled(Typography)`
`;
export const AllThreadP = styled(Typography)`
color: #FFF;
color: #fff;
font-size: 20px;
font-style: normal;
font-weight: 400;
@ -668,9 +687,10 @@ height: 76px;
align-items: center;
transition: 0.2s all;
&:hover {
background: rgba(255, 255, 255, 0.20)
background: rgba(255, 255, 255, 0.2);
}
`;
export const SingleTheadMessageParent = styled(Box)`
border-radius: 35px 4px 4px 35px;
background: #434448;
@ -680,22 +700,20 @@ 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;
color: #fff;
font-family: Roboto;
font-size: 16px;
font-style: normal;
@ -705,8 +723,9 @@ white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
`;
export const ThreadInfoColumnbyP = styled('span')`
color: rgba(255, 255, 255, 0.80);
color: rgba(255, 255, 255, 0.8);
font-family: Roboto;
font-size: 16px;
font-style: normal;
@ -715,15 +734,16 @@ line-height: normal;
`;
export const ThreadInfoColumnTime = styled(Typography)`
color: rgba(255, 255, 255, 0.80);
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;
color: #fff;
font-family: Roboto;
font-size: 23px;
font-style: normal;
@ -732,17 +752,19 @@ line-height: normal;
white-space: wrap;
text-overflow: ellipsis;
overflow: hidden;
`
`;
export const ThreadSingleLastMessageP = styled(Typography)`
color: #FFF;
color: #fff;
font-family: Roboto;
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: normal;
`
`;
export const ThreadSingleLastMessageSpanP = styled('span')`
color: #FFF;
color: #fff;
font-family: Roboto;
font-size: 12px;
font-style: normal;
@ -754,24 +776,22 @@ export const GroupContainer = styled(Box)`
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)',
},
}));

View File

@ -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) {

View File

@ -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 {
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 <blockquote {...attributes}>{children}</blockquote>
return <blockquote {...attributes}>{children}</blockquote>;
case 'heading-2':
return (
<h2
@ -22,7 +28,7 @@ export const renderElement = ({
>
{children}
</h2>
)
);
case 'heading-3':
return (
<h3
@ -32,21 +38,21 @@ export const renderElement = ({
>
{children}
</h3>
)
);
case 'code-block':
return (
<pre {...attributes} className="code-block">
<code>{children}</code>
</pre>
)
);
case 'code-line':
return <div {...attributes}>{children}</div>
return <div {...attributes}>{children}</div>;
case 'link':
return (
<a href={element.url} {...attributes}>
{children}
</a>
)
);
default:
return (
<p
@ -56,24 +62,23 @@ export const renderElement = ({
>
{children}
</p>
)
);
}
}
};
export const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => {
let el = children
let el = children;
if (leaf.bold) {
el = <strong>{el}</strong>
el = <strong>{el}</strong>;
}
if (leaf.italic) {
el = <em>{el}</em>
el = <em>{el}</em>;
}
if (leaf.underline) {
el = <u>{el}</u>
el = <u>{el}</u>;
}
if (leaf.link) {
@ -81,39 +86,35 @@ export const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => {
<a href={leaf.link} {...attributes}>
{el}
</a>
)
);
}
return <span {...attributes}>{el}</span>
}
return <span {...attributes}>{el}</span>;
};
interface ReadOnlySlateProps {
content: any
mode?: string
content: any;
mode?: string;
}
const ReadOnlySlate: React.FC<ReadOnlySlateProps> = ({ 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)
setLoad(true);
await new Promise<void>((res) => {
setTimeout(() => {
res()
res();
}, 250);
})
setLoad(false)
}, [])
});
setLoad(false);
}, []);
useEffect(() => {
performUpdate();
}, [value]);
performUpdate()
}, [value])
if(load) return null
if (load) return null;
return (
<Slate editor={editor} value={value} onChange={() => {}}>
@ -123,7 +124,7 @@ const ReadOnlySlate: React.FC<ReadOnlySlateProps> = ({ content, mode }) => {
renderLeaf={renderLeaf}
/>
</Slate>
)
}
);
};
export default ReadOnlySlate;

View File

@ -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<boolean>(false);
let cleanHTML = "";
let cleanHTML = '';
if (message?.htmlContent) {
cleanHTML = DOMPurify.sanitize(message.htmlContent);
}
@ -32,37 +30,49 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
return (
<SingleTheadMessageParent
sx={{
height: "auto",
alignItems: "flex-start",
cursor: "default",
borderRadius: '35px 4px 4px 4px'
height: 'auto',
alignItems: 'flex-start',
cursor: 'default',
borderRadius: '35px 4px 4px 4px',
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
width: '100%'
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
width: '100%',
}}
>
<Box
sx={{
display: "flex",
alignItems: "flex-start",
gap: "10px",
display: 'flex',
alignItems: 'flex-start',
gap: '10px',
}}
>
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
<Avatar sx={{
<WrapperUserAction
disabled={myName === message?.name}
address={undefined}
name={message?.name}
>
<Avatar
sx={{
height: '50px',
width: '50px'
}} src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.name}/qortal_avatar?async=true`} alt={message?.name}>{message?.name?.charAt(0)}</Avatar>
width: '50px',
}}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.name}/qortal_avatar?async=true`}
alt={message?.name}
>
{message?.name?.charAt(0)}
</Avatar>
</WrapperUserAction>
<ThreadInfoColumn>
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
<WrapperUserAction
disabled={myName === message?.name}
address={undefined}
name={message?.name}
>
<ThreadInfoColumnNameP>{message?.name}</ThreadInfoColumnNameP>
</WrapperUserAction>
<ThreadInfoColumnTime>
@ -71,37 +81,40 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
</ThreadInfoColumn>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}}
>
{message?.attachments?.length > 0 && (
<Box
sx={{
width: "100%",
marginTop: "10px",
width: '100%',
marginTop: '10px',
}}
>
{message?.attachments
.map((file: any, index: number) => {
const isFirst = index === 0
{message?.attachments.map((file: any, index: number) => {
const isFirst = index === 0;
return (
<Box
sx={{
display: expandAttachments ? "flex" : !expandAttachments && isFirst ? 'flex' : 'none',
alignItems: "center",
justifyContent: "flex-start",
width: "100%",
display: expandAttachments
? 'flex'
: !expandAttachments && isFirst
? 'flex'
: 'none',
alignItems: 'center',
justifyContent: 'flex-start',
width: '100%',
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "5px",
cursor: "pointer",
width: "auto",
display: 'flex',
alignItems: 'center',
gap: '5px',
cursor: 'pointer',
width: 'auto',
}}
>
{/* <FileElement
@ -128,75 +141,84 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
{message?.attachments?.length > 1 && isFirst && (
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "5px",
display: 'flex',
alignItems: 'center',
gap: '5px',
}}
onClick={() => {
setExpandAttachments(prev => !prev);
setExpandAttachments((prev) => !prev);
}}
>
<MoreImg
sx={{
marginLeft: "5px",
marginLeft: '5px',
transform: expandAttachments
? "rotate(180deg)"
: "unset",
? 'rotate(180deg)'
: 'unset',
}}
src={MoreSVG}
/>
<MoreP>
{expandAttachments ? 'hide' : `(${message?.attachments?.length - 1} more)`}
{expandAttachments
? 'hide'
: `(${message?.attachments?.length - 1} more)`}
</MoreP>
</Box>
)}
</Box>
</Box>
);
})
}
})}
</Box>
)}
</div>
</Box>
<Spacer height="20px" />
{message?.reply?.textContentV2 && (
<>
<Box sx={{
<Box
sx={{
width: '100%',
opacity: 0.7,
borderRadius: '5px',
border: '1px solid gray',
boxSizing: 'border-box',
padding: '5px'
}}>
<Box
sx={{
display: "flex",
alignItems: "flex-start",
gap: "10px",
padding: '5px',
}}
>
<Avatar sx={{
<Box
sx={{
display: 'flex',
alignItems: 'flex-start',
gap: '10px',
}}
>
<Avatar
sx={{
height: '30px',
width: '30px'
}} src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.reply?.name}/qortal_avatar?async=true`} alt={message?.reply?.name}>{message?.reply?.name?.charAt(0)}</Avatar>
width: '30px',
}}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.reply?.name}/qortal_avatar?async=true`}
alt={message?.reply?.name}
>
{message?.reply?.name?.charAt(0)}
</Avatar>
<ThreadInfoColumn>
<ThreadInfoColumnNameP sx={{
fontSize: '14px'
}}>{message?.reply?.name}</ThreadInfoColumnNameP>
<ThreadInfoColumnNameP
sx={{
fontSize: '14px',
}}
>
{message?.reply?.name}
</ThreadInfoColumnNameP>
</ThreadInfoColumn>
</Box>
<MessageDisplay htmlContent={message?.reply?.textContentV2} />
</Box>
<Spacer height="20px" />
</>
)}
{message?.textContent && (
@ -208,22 +230,18 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
{message?.htmlContent && (
<div dangerouslySetInnerHTML={{ __html: cleanHTML }} />
)}
<Box sx={{
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'flex-end'
}}>
<IconButton
onClick={() => openNewPostWithQuote(message)}
justifyContent: 'flex-end',
}}
>
<IconButton onClick={() => openNewPostWithQuote(message)}>
<FormatQuoteIcon />
</IconButton>
</Box>
</Box>
</SingleTheadMessageParent>
);
};

View File

@ -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 (
<ReactQuill

View File

@ -467,6 +467,7 @@ export const Thread = ({
}
setMessages(fullArrayMsg);
} catch (error) {
console.log(error);
} finally {
}
},
@ -700,7 +701,7 @@ export const Thread = ({
);
}}
disabled={!hasPreviousPage}
variant="contained"
variant="contained" // TODO translate
>
Previous
</Button>

View File

@ -2218,7 +2218,8 @@ export const Group = ({
}}
>
No group selected
</Typography>
</Typography>{' '}
// TODO translate
</Box>
)}

View File

@ -69,7 +69,7 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
<Typography
sx={{
fontSize: '1rem',
}}
}} // TODO translate
>
Group Invites{' '}
{groupsWithJoinRequests?.length > 0 &&

View File

@ -82,6 +82,7 @@ export const GroupJoinRequests = ({
);
setGroupsWithJoinRequests(res);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
@ -138,7 +139,7 @@ export const GroupJoinRequests = ({
<Typography
sx={{
fontSize: '1rem',
}}
}} // TODO translate
>
Join Requests{' '}
{filteredJoinRequests?.filter((group) => group?.data?.length > 0)

View File

@ -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,72 +36,103 @@ export const GroupMenu = ({ setGroupSection, groupSection, setOpenManageMembers,
return (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
width: '100%',
display: 'flex',
justifyContent: 'center',
marginTop: '14px',
marginBottom: '14px'
marginBottom: '14px',
}}
>
<Button
aria-controls={open ? "home-menu" : undefined}
aria-controls={open ? 'home-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
variant="contained"
sx={{
backgroundColor: "var(--bg-primary)",
width: "148px",
borderRadius: "5px",
fontSize: "12px",
backgroundColor: 'var(--bg-primary)',
width: '148px',
borderRadius: '5px',
fontSize: '12px',
fontWeight: 600,
color: "#fff",
textTransform: "none",
color: '#fff',
textTransform: 'none',
padding: '5px',
height: '25px'
height: '25px',
}}
>
<Box
sx={{
display: "flex",
gap: "6px",
alignItems: "center",
justifyContent: "space-between",
width: '100%'
display: 'flex',
gap: '6px',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
}}
>
<Box
sx={{
display: "flex",
gap: "6px",
alignItems: "center",
display: 'flex',
gap: '6px',
alignItems: 'center',
}}
>
{groupSection === "announcement" &&(
<> <NotificationIcon2 color={hasUnreadAnnouncements || hasUnreadChat ? 'var(--danger)' : 'white'} /> {" Announcements"}</>
{groupSection === 'announcement' && (
<>
{' '}
<NotificationIcon2
color={
hasUnreadAnnouncements || hasUnreadChat
? 'var(--danger)'
: 'white'
}
/>{' '}
{' Announcements'}
</>
)}
{groupSection === "chat" &&(
<> <ChatIcon color={hasUnreadAnnouncements || hasUnreadChat ? 'var(--danger)' : 'white'} /> {" Group Chats"}</>
{groupSection === 'chat' && (
<>
{' '}
<ChatIcon
color={
hasUnreadAnnouncements || hasUnreadChat
? 'var(--danger)'
: 'white'
}
/>{' '}
{' Group Chats'}
</>
)}
{groupSection === "forum" &&(
<> <ThreadsIcon color={hasUnreadAnnouncements || hasUnreadChat ? 'var(--danger)' : 'white'} /> {" Threads"}</>
{groupSection === 'forum' && (
<>
{' '}
<ThreadsIcon
color={
hasUnreadAnnouncements || hasUnreadChat
? 'var(--danger)'
: 'white'
}
/>{' '}
{' Threads'}
</>
)}
</Box>
<ArrowDownIcon color="white" />
</Box>
</Button>
<Menu
id="home-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
"aria-labelledby": "basic-button",
'aria-labelledby': 'basic-button',
}}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
@ -108,93 +144,109 @@ export const GroupMenu = ({ setGroupSection, groupSection, setOpenManageMembers,
backgroundColor: 'var(--bg-primary)',
color: '#fff',
width: '148px',
borderRadius: '5px'
borderRadius: '5px',
},
},
}}
sx={{
marginTop: '10px'
marginTop: '10px',
}}
>
<MenuItem
onClick={() => {
goToChat()
goToChat();
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<ChatIcon color={hasUnreadChat ? 'var(--danger)' : "#fff"} />
<ListItemIcon
sx={{
minWidth: '24px !important',
}}
>
<ChatIcon color={hasUnreadChat ? 'var(--danger)' : '#fff'} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
<ListItemText
sx={{
'& .MuiTypography-root': {
fontSize: '12px',
fontWeight: 600,
color: hasUnreadChat ? "var(--danger)" :"#fff"
color: hasUnreadChat ? 'var(--danger)' : '#fff',
},
}} primary="Chat" />
}}
primary="Chat"
/>
</MenuItem>
<MenuItem
onClick={() => {
goToAnnouncements()
goToAnnouncements();
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<NotificationIcon2 color={hasUnreadAnnouncements ? 'var(--danger)' : "#fff" } />
<ListItemIcon
sx={{
minWidth: '24px !important',
}}
>
<NotificationIcon2
color={hasUnreadAnnouncements ? 'var(--danger)' : '#fff'}
/>
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
<ListItemText
sx={{
'& .MuiTypography-root': {
fontSize: '12px',
fontWeight: 600,
color: hasUnreadAnnouncements ? "var(--danger)" :"#fff"
color: hasUnreadAnnouncements ? 'var(--danger)' : '#fff',
},
}} primary="Announcements" />
}}
primary="Announcements"
/>
</MenuItem>
<MenuItem
onClick={() => {
setGroupSection("forum");
setGroupSection('forum');
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<ThreadsIcon color={"#fff"} />
<ListItemIcon
sx={{
minWidth: '24px !important',
}}
>
<ThreadsIcon color={'#fff'} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
<ListItemText
sx={{
'& .MuiTypography-root': {
fontSize: '12px',
fontWeight: 600,
},
}} primary="Threads" />
}}
primary="Threads"
/>
</MenuItem>
<MenuItem
onClick={() => {
setOpenManageMembers(true)
setOpenManageMembers(true);
handleClose();
}}
>
<ListItemIcon sx={{
minWidth: '24px !important'
}}>
<MembersIcon sx={{ color: "#fff" }} />
<ListItemIcon
sx={{
minWidth: '24px !important',
}}
>
<MembersIcon sx={{ color: '#fff' }} />
</ListItemIcon>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "12px",
<ListItemText
sx={{
'& .MuiTypography-root': {
fontSize: '12px',
fontWeight: 600,
},
}} primary="Members" />
}}
primary="Members"
/>
</MenuItem>
</Menu>
</Box>

View File

@ -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 (
<Box
sx={{
display: "flex",
width: "100%",
flexDirection: "column",
height: "100%",
overflow: "auto",
alignItems: "center",
}}
>
<Spacer height="20px" />
<Typography
sx={{
color: "rgba(255, 255, 255, 1)",
fontWeight: 400,
fontSize: userInfo?.name?.length > 15 ? "16px" : "20px",
padding: '10px'
}}
>
Welcome
{userInfo?.name ? (
<span
style={{
fontStyle: "italic",
}}
>{`, ${userInfo?.name}`}</span>
) : null}
</Typography>
<Spacer height="26px" />
{/* <Box
sx={{
display: "flex",
width: "100%",
justifyContent: "flex-start",
}}
>
<Button
variant="outlined"
startIcon={<RefreshIcon />}
onClick={refreshHomeDataFunc}
sx={{
color: "white",
}}
>
Refresh home data
</Button>
</Box> */}
{!isLoadingGroups && (
<Box
sx={{
display: "flex",
gap: "15px",
flexWrap: "wrap",
justifyContent: "center",
}}
>
<ThingsToDoInitial
balance={balance}
myAddress={myAddress}
name={userInfo?.name}
hasGroups={groups?.length !== 0}
/>
<ListOfThreadPostsWatched />
<GroupJoinRequests
setGroupSection={setGroupSection}
setSelectedGroup={setSelectedGroup}
getTimestampEnterChat={getTimestampEnterChat}
setOpenManageMembers={setOpenManageMembers}
myAddress={myAddress}
groups={groups}
setMobileViewMode={setMobileViewMode}
setDesktopViewMode={setDesktopViewMode}
/>
<GroupInvites
setOpenAddGroup={setOpenAddGroup}
myAddress={myAddress}
groups={groups}
setMobileViewMode={setMobileViewMode}
/>
</Box>
)}
<Spacer height="180px" />
</Box>
);
};

View File

@ -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);
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])
if (userInfo !== null) return true;
return false;
}, [userInfo]);
const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(() => {
if(isLoaded && checked1 && checked2) return true
return false
}, [checked1, isLoaded, checked2])
if (isLoaded && checked1 && checked2) return true;
return false;
}, [checked1, isLoaded, checked2]);
return (
<Box
sx={{
display: desktopViewMode === "home" ? "flex" : "none",
width: "100%",
flexDirection: "column",
height: "100%",
overflow: "auto",
alignItems: "center",
alignItems: 'center',
display: desktopViewMode === 'home' ? 'flex' : 'none',
flexDirection: 'column',
height: '100%',
overflow: 'auto',
width: '100%',
}}
>
<Spacer height="20px" />
<Box
sx={{
display: "flex",
width: "100%",
flexDirection: "column",
height: "100%",
alignItems: "flex-start",
maxWidth: "1036px",
alignItems: 'flex-start',
display: 'flex',
flexDirection: 'column',
height: '100%',
maxWidth: '1036px',
width: '100%',
}}
>
<Typography
sx={{
color: "rgba(255, 255, 255, 1)",
color: theme.palette.text.primary,
fontWeight: 400,
fontSize: userInfo?.name?.length > 15 ? "16px" : "20px",
padding: "10px",
fontSize: userInfo?.name?.length > 15 ? '16px' : '20px',
padding: '10px',
}}
>
Welcome
{t('core:welcome', { postProcess: 'capitalize' })}
{userInfo?.name ? (
<span
style={{
fontStyle: "italic",
fontStyle: 'italic',
}}
>{`, ${userInfo?.name}`}</span>
) : null}
</Typography>
<Spacer height="30px" />
{!isLoadingGroups && (
<Box
sx={{
display: "flex",
gap: "20px",
flexWrap: "wrap",
width: "100%",
justifyContent: "center",
display: 'flex',
flexWrap: 'wrap',
gap: '20px',
justifyContent: 'center',
width: '100%',
}}
>
<Box
sx={{
display: "flex",
gap: "20px",
flexWrap: "wrap",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
gap: '20px',
}}
>
<Box
sx={{
width: "330px",
display: "flex",
alignItems: "center",
justifyContent: "center",
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
width: '330px',
}}
>
<ThingsToDoInitial
@ -125,12 +129,12 @@ export const HomeDesktop = ({
name={userInfo?.name}
userInfo={userInfo}
hasGroups={
groups?.filter((item) => item?.groupId !== "0").length !== 0
groups?.filter((item) => item?.groupId !== '0').length !== 0
}
/>
</Box>
{desktopViewMode === "home" && (
{desktopViewMode === 'home' && (
<>
{/* <Box sx={{
width: '330px',
@ -144,10 +148,10 @@ export const HomeDesktop = ({
<>
<Box
sx={{
width: "330px",
display: "flex",
alignItems: "center",
justifyContent: "center",
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
width: '330px',
}}
>
<GroupJoinRequests
@ -161,12 +165,13 @@ export const HomeDesktop = ({
setDesktopViewMode={setDesktopViewMode}
/>
</Box>
<Box
sx={{
width: "330px",
display: "flex",
alignItems: "center",
justifyContent: "center",
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
width: '330px',
}}
>
<GroupInvites
@ -178,7 +183,6 @@ export const HomeDesktop = ({
</Box>
</>
)}
</>
)}
</Box>
@ -189,58 +193,55 @@ export const HomeDesktop = ({
{!isLoadingGroups && (
<>
<Spacer height="60px" />
<Divider
color="secondary"
sx={{
width: "100%",
width: '100%',
}}
>
<Box
sx={{
display: "flex",
gap: "10px",
alignItems: "center",
alignItems: 'center',
display: 'flex',
gap: '10px',
}}
>
<ExploreIcon
sx={{
color: "white",
ccolor: theme.palette.text.primary,
}}
/>{" "}
/>{' '}
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
}}
>
Explore
</Typography>{" "}
{t('tutorial:initial.explore', { postProcess: 'capitalize' })}
</Typography>{' '}
</Box>
</Divider>
{!hasDoneNameAndBalanceAndIsLoaded && (
<Spacer height="40px" />
)}
{!hasDoneNameAndBalanceAndIsLoaded && <Spacer height="40px" />}
<Box
sx={{
display: "flex",
gap: "20px",
flexWrap: "wrap",
width: "100%",
justifyContent: "center",
display: 'flex',
flexWrap: 'wrap',
gap: '20px',
justifyContent: 'center',
width: '100%',
}}
>
{hasDoneNameAndBalanceAndIsLoaded && (
<ListOfGroupPromotions />
)}
{hasDoneNameAndBalanceAndIsLoaded && <ListOfGroupPromotions />}
<Explore setDesktopViewMode={setDesktopViewMode} />
</Box>
<NewUsersCTA balance={balance} />
</>
)}
</Box>
<Spacer height="26px" />
{/* <Box

View File

@ -1,13 +1,6 @@
import { LoadingButton } from '@mui/lab';
import {
Box,
Button,
Input,
MenuItem,
Select,
SelectChangeEvent,
} from '@mui/material';
import React, { useState } from 'react';
import { Box, Input, MenuItem, Select, SelectChangeEvent } from '@mui/material';
import { useState } from 'react';
import { Spacer } from '../../common/Spacer';
import { Label } from './AddGroup';
import { getFee } from '../../background';
@ -34,6 +27,7 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
})
.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`,

View File

@ -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 = [];
@ -21,13 +36,13 @@ const getNames = async (listOfMembers, includeNoNames) => {
if (name) {
members.push({ ...member, name });
} else if (includeNoNames) {
members.push({ ...member, name: name || "" });
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) {
@ -69,14 +84,16 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
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)
message: 'Would you like to perform a CANCEL_GROUP_BAN transaction?',
publishFee: fee.fee + ' QORT',
});
setIsLoadingUnban(true);
new Promise((res, rej) => {
window.sendMessage("cancelBan", {
window
.sendMessage('cancelBan', {
groupId,
qortalAddress: address,
})
@ -85,15 +102,16 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
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,20 +119,18 @@ 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];
@ -135,36 +151,47 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
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' }}
>
<Box
sx={{
width: "325px",
height: "250px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "10px",
padding: "10px",
width: '325px',
height: '250px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
padding: '10px',
}}
>
<LoadingButton loading={isLoadingUnban}
<LoadingButton
loading={isLoadingUnban}
loadingPosition="start"
variant="contained" onClick={()=> handleCancelBan(member?.offender)}>Cancel Ban</LoadingButton>
variant="contained"
onClick={() => handleCancelBan(member?.offender)}
>
Cancel Ban
</LoadingButton>
</Box>
</Popover>
<ListItemButton onClick={(event) => handlePopoverOpen(event, index)}>
<ListItemButton
onClick={(event) => handlePopoverOpen(event, index)}
>
<ListItemAvatar>
<Avatar
alt={member?.name}
src={member?.name ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true` : ''}
src={
member?.name
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true`
: ''
}
/>
</ListItemAvatar>
<ListItemText primary={member?.name || member?.offender} />
@ -179,7 +206,16 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
return (
<div>
<p>Ban list</p>
<div style={{ position: 'relative', height: '500px', width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<div
style={{
position: 'relative',
height: '500px',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 1,
}}
>
<AutoSizer>
{({ height, width }) => (
<List
@ -196,4 +232,4 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
</div>
</div>
);
}
};

View File

@ -230,7 +230,7 @@ export const ListOfGroupPromotions = () => {
.catch((error) => {
rej(error.message || 'An error occurred');
});
});
}); // TODO translate
setInfoSnack({
type: 'success',
message:

View File

@ -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 = [];
@ -21,20 +36,25 @@ const getNames = async (listOfMembers, includeNoNames) => {
if (name) {
members.push({ ...member, name });
} else if (includeNoNames) {
members.push({ ...member, name: name || "" });
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) {
@ -70,22 +90,25 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
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)
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", {
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,20 +125,18 @@ 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];
@ -136,36 +157,47 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
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' }}
>
<Box
sx={{
width: "325px",
height: "250px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "10px",
padding: "10px",
width: '325px',
height: '250px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
padding: '10px',
}}
>
<LoadingButton loading={isLoadingCancelInvite}
<LoadingButton
loading={isLoadingCancelInvite}
loadingPosition="start"
variant="contained" onClick={()=> handleCancelInvitation(member?.invitee)}>Cancel Invitation</LoadingButton>
variant="contained"
onClick={() => handleCancelInvitation(member?.invitee)}
>
Cancel Invitation
</LoadingButton>
</Box>
</Popover>
<ListItemButton onClick={(event) => handlePopoverOpen(event, index)}>
<ListItemButton
onClick={(event) => handlePopoverOpen(event, index)}
>
<ListItemAvatar>
<Avatar
alt={member?.name}
src={member?.name ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true` : ''}
src={
member?.name
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true`
: ''
}
/>
</ListItemAvatar>
<ListItemText primary={member?.name || member?.invitee} />
@ -180,7 +212,16 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
return (
<div>
<p>Invitees list</p>
<div style={{ position: 'relative', height: '500px', width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<div
style={{
position: 'relative',
height: '500px',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 1,
}}
>
<AutoSizer>
{({ height, width }) => (
<List
@ -197,4 +238,4 @@ export const ListOfInvites = ({ groupId, setInfoSnack, setOpenSnack, show }) =>
</div>
</div>
);
}
};

View File

@ -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 || "" });
members.push({ ...member, name: name || '' });
} else if (includeNoNames) {
members.push({ ...member, name: name || "" });
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) {
@ -71,14 +91,15 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
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)
message: 'Would you like to perform a GROUP_INVITE transaction?',
publishFee: fee.fee + ' QORT',
});
setIsLoadingAccept(true);
await new Promise((res, rej) => {
window.sendMessage("inviteToGroup", {
window
.sendMessage('inviteToGroup', {
groupId,
qortalAddress: address,
inviteTime: 10800,
@ -87,8 +108,9 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
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();
@ -111,7 +133,7 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
}
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 (
<CellMeasurer
key={key}
@ -154,36 +179,47 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
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' }}
>
<Box
sx={{
width: "325px",
height: "250px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "10px",
padding: "10px",
width: '325px',
height: '250px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
padding: '10px',
}}
>
<LoadingButton loading={isLoadingAccept}
<LoadingButton
loading={isLoadingAccept}
loadingPosition="start"
variant="contained" onClick={()=> handleAcceptJoinRequest(member?.joiner)}>Accept</LoadingButton>
variant="contained"
onClick={() => handleAcceptJoinRequest(member?.joiner)}
>
Accept
</LoadingButton>
</Box>
</Popover>
<ListItemButton onClick={(event) => handlePopoverOpen(event, index)}>
<ListItemButton
onClick={(event) => handlePopoverOpen(event, index)}
>
<ListItemAvatar>
<Avatar
alt={member?.name}
src={member?.name ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true` : ''}
src={
member?.name
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true`
: ''
}
/>
</ListItemAvatar>
<ListItemText primary={member?.name || member?.joiner} />
@ -198,7 +234,16 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
return (
<div>
<p>Join request list</p>
<div style={{ position: 'relative', height: '500px', width: '100%', display: 'flex', flexDirection: 'column', flexShrink: 1 }}>
<div
style={{
position: 'relative',
height: '500px',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 1,
}}
>
<AutoSizer>
{({ height, width }) => (
<List
@ -215,4 +260,4 @@ export const ListOfJoinRequests = ({ groupId, setInfoSnack, setOpenSnack, show }
</div>
</div>
);
}
};

View File

@ -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", {
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,29 +88,30 @@ 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", {
window
.sendMessage('banFromGroup', {
groupId,
qortalAddress: address,
rBanTime: 0,
@ -117,8 +119,9 @@ const ListOfMembers = ({
.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", {
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", {
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' }}
>
<Box
sx={{
width: "325px",
height: "250px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "10px",
padding: "10px",
width: '325px',
height: '250px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
padding: '10px',
}}
>
{isOwner && (
@ -336,21 +340,28 @@ const ListOfMembers = ({
<ListItemAvatar>
<Avatar
alt={member?.name || member?.member}
src={member?.name ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true` : ''}
src={
member?.name
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${member?.name}/qortal_avatar?async=true`
: ''
}
/>
</ListItemAvatar>
<ListItemText
id={""}
id={''}
primary={member?.name || member?.member}
/>
{member?.isAdmin && (
<Typography sx={{
<Typography
sx={{
color: 'white',
marginLeft: 'auto'
}}>Admin</Typography>
marginLeft: 'auto',
}}
>
Admin
</Typography>
)}
</ListItemButton>
</ListItem>
</div>
)}
@ -363,11 +374,11 @@ const ListOfMembers = ({
<p>Member list</p>
<div
style={{
position: "relative",
height: "500px",
width: "100%",
display: "flex",
flexDirection: "column",
position: 'relative',
height: '500px',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexShrink: 1,
}}
>

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) => {

View File

@ -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',
})}
</Typography>
<Spacer height="10px" />
@ -80,12 +87,12 @@ export const ThingsToDoInitial = ({
<Box
sx={{
width: '322px',
bgcolor: theme.palette.background.paper,
borderRadius: '19px',
display: 'flex',
flexDirection: 'column',
bgcolor: 'background.paper',
padding: '20px',
borderRadius: '19px',
width: '322px',
}}
>
{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',
})}
/>
<ListItemIcon
sx={{
@ -183,7 +192,9 @@ export const ThingsToDoInitial = ({
fontWeight: 400,
},
}}
primary={`Register a name`}
primary={t('tutorial:initial.register_name', {
postProcess: 'capitalize',
})}
/>
<ListItemIcon
sx={{

View File

@ -1,45 +1,57 @@
import { Box, Button, ListItem, ListItemButton, ListItemText, Popover, Typography } from '@mui/material';
import React, { useContext, useEffect, useRef, useState } from 'react'
import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';
import {
Box,
ListItem,
ListItemButton,
ListItemText,
Popover,
Typography,
} from '@mui/material';
import { useContext, useEffect, useRef, useState } from 'react';
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List,
} from 'react-virtualized';
import { MyContext, getBaseApiReact } from '../../App';
import { LoadingButton } from '@mui/lab';
import { getBaseApi, getFee } from '../../background';
import { getFee } from '../../background';
import LockIcon from '@mui/icons-material/Lock';
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
import { Spacer } from "../../common/Spacer";
import { Spacer } from '../../common/Spacer';
const cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 50,
});
const getGroupInfo = async (groupId) => {
const response = await fetch(`${getBaseApiReact()}/groups/` + groupId);
const groupData = await response.json();
if (groupData) {
return 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;
}
};
export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => {
const {txList, setTxList, show} = useContext(MyContext)
export const UserListOfInvites = ({
myAddress,
setInfoSnack,
setOpenSnack,
}) => {
const { txList, setTxList, show } = useContext(MyContext);
const [invites, setInvites] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
@ -49,20 +61,20 @@ export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => {
const getRequests = async () => {
try {
const response = await fetch(`${getBaseApiReact()}/groups/invites/${myAddress}/?limit=0`);
const response = await fetch(
`${getBaseApiReact()}/groups/invites/${myAddress}/?limit=0`
);
const inviteData = await response.json();
const resMoreData = await getGroupNames(inviteData)
const resMoreData = await getGroupNames(inviteData);
setInvites(resMoreData);
} catch (error) {
console.error(error);
}
}
};
useEffect(() => {
getRequests();
}, []);
const handlePopoverOpen = (event, index) => {
@ -77,17 +89,17 @@ export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => {
const handleJoinGroup = async (groupId, groupName) => {
try {
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'
})
message: 'Would you like to perform an JOIN_GROUP transaction?',
publishFee: fee.fee + ' QORT',
});
setIsLoading(true);
await new Promise((res, rej) => {
window.sendMessage("joinGroup", {
window
.sendMessage('joinGroup', {
groupId,
})
.then((response) => {
@ -105,15 +117,16 @@ export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => {
]);
res(response);
setInfoSnack({
type: "success",
message: "Successfully requested to join group. It may take a couple of minutes for the changes to propagate",
type: 'success',
message:
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate',
});
setOpenSnack(true);
handlePopoverClose();
return;
}
setInfoSnack({
type: "error",
type: 'error',
message: response?.error,
});
setOpenSnack(true);
@ -121,22 +134,18 @@ export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => {
})
.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 {
setIsLoading(false);
}
}
};
const rowRenderer = ({ index, key, parent, style }) => {
const invite = invites[index];
@ -157,46 +166,61 @@ export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => {
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' }}
>
<Box
sx={{
width: "325px",
height: "250px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "10px",
padding: "10px",
width: '325px',
height: '250px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
padding: '10px',
}}
>
<Typography>Join {invite?.groupName}</Typography>
<LoadingButton
loading={isLoading}
loadingPosition="start"
variant="contained" onClick={()=> handleJoinGroup(invite?.groupId, invite?.groupName)}>Join group</LoadingButton>
variant="contained"
onClick={() =>
handleJoinGroup(invite?.groupId, invite?.groupName)
}
>
Join group
</LoadingButton>
</Box>
</Popover>
<ListItemButton onClick={(event) => handlePopoverOpen(event, index)}>
<ListItemButton
onClick={(event) => handlePopoverOpen(event, index)}
>
{invite?.isOpen === false && (
<LockIcon sx={{
color: 'var(--green)'
}} />
<LockIcon
sx={{
color: 'var(--green)',
}}
/>
)}
{invite?.isOpen === true && (
<NoEncryptionGmailerrorredIcon sx={{
color: 'var(--danger)'
}} />
<NoEncryptionGmailerrorredIcon
sx={{
color: 'var(--danger)',
}}
/>
)}
<Spacer width="15px" />
<ListItemText primary={invite?.groupName} secondary={invite?.description} />
<ListItemText
primary={invite?.groupName}
secondary={invite?.description}
/>
</ListItemButton>
</ListItem>
</div>
@ -206,18 +230,20 @@ export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => {
};
return (
<Box sx={{
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1
}}>
flexGrow: 1,
}}
>
<p>Invite list</p>
<div
style={{
position: "relative",
width: "100%",
display: "flex",
flexDirection: "column",
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
}}
>
@ -237,4 +263,4 @@ export const UserListOfInvites = ({myAddress, setInfoSnack, setOpenSnack}) => {
</div>
</Box>
);
}
};

View File

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

View File

@ -1,12 +1,16 @@
import React, { useEffect, useRef } from 'react';
import { getBaseApiReactSocket, pauseAllQueues, resumeAllQueues } from '../../App';
import { useEffect, useRef } from 'react';
import {
getBaseApiReactSocket,
pauseAllQueues,
resumeAllQueues,
} from '../../App';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
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) {
@ -54,9 +57,8 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
try {
if (!initiateRef.current) {
setIsLoadingGroups(true)
pauseAllQueues()
setIsLoadingGroups(true);
pauseAllQueues();
}
const socketLink = `${getBaseApiReactSocket()}/websockets/chat/active/${currentAddress}?encoding=BASE64&haschatreference=false`;
socketRef.current = new WebSocket(socketLink);
@ -72,33 +74,45 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
groupSocketTimeoutRef.current = setTimeout(pingHeads, 45000); // Ping every 45 seconds
} else {
if (!initiateRef.current) {
setIsLoadingGroups(false)
initiateRef.current = true
resumeAllQueues()
setIsLoadingGroups(false);
initiateRef.current = true;
resumeAllQueues();
}
const data = JSON.parse(e.data);
const copyGroups = [...(data?.groups || [])]
const findIndex = copyGroups?.findIndex(item => item?.groupId === 0)
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", {
window
.sendMessage('handleActiveGroupDataFromSocket', {
groups: sortedGroups,
directs: sortedDirects,
}).catch((error) => {
console.error("Failed to handle active group data from socket:", error.message || "An error occurred");
})
.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,10 +141,8 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
}
};
initWebsocketMessageGroup(); // Initialize WebSocket on component mount
return () => {
forceCloseWebSocket(); // Clean up WebSocket on component unmount
};

View File

@ -1,42 +1,34 @@
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 userBlockedRef = useRef({});
const userNamesBlockedRef = useRef({});
const getAllBlockedUsers = useCallback(() => {
return {
names: userNamesBlockedRef.current,
addresses: userBlockedRef.current
}
}, [])
addresses: userBlockedRef.current,
};
}, []);
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
}
}, [])
}, []);
useEffect(() => {
const fetchBlockedList = async () => {
try {
const response = await new Promise((res, rej) => {
window.sendMessage("listActions", {
window
.sendMessage('listActions', {
type: 'get',
listName: `blockedAddresses`,
})
.then((response) => {
if (response.error) {
@ -47,21 +39,20 @@ export const useBlockedAddresses = () => {
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
console.error('Failed qortalRequest', error);
});
})
const blockedUsers = {}
});
const blockedUsers = {};
response?.forEach((item) => {
blockedUsers[item] = true
})
userBlockedRef.current = blockedUsers
blockedUsers[item] = true;
});
userBlockedRef.current = blockedUsers;
const response2 = await new Promise((res, rej) => {
window.sendMessage("listActions", {
window
.sendMessage('listActions', {
type: 'get',
listName: `blockedNames`,
})
.then((response) => {
if (response.error) {
@ -72,147 +63,132 @@ export const useBlockedAddresses = () => {
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
console.error('Failed qortalRequest', error);
});
})
const blockedUsers2 = {}
});
const blockedUsers2 = {};
response2?.forEach((item) => {
blockedUsers2[item] = true
})
userNamesBlockedRef.current = blockedUsers2
blockedUsers2[item] = true;
});
userNamesBlockedRef.current = blockedUsers2;
} catch (error) {
console.error(error)
console.error(error);
}
}
fetchBlockedList()
}, [])
};
fetchBlockedList();
}, []);
const removeBlockFromList = useCallback(async (address, name) => {
if (name) {
await new Promise((res, rej) => {
window.sendMessage("listActions", {
window
.sendMessage('listActions', {
type: 'remove',
items: [name],
listName: 'blockedNames'
listName: 'blockedNames',
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userNamesBlockedRef.current}
delete copyObject[name]
userNamesBlockedRef.current = copyObject
const copyObject = { ...userNamesBlockedRef.current };
delete copyObject[name];
userNamesBlockedRef.current = copyObject;
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
console.error('Failed qortalRequest', error);
});
});
})
}
if (address) {
await new Promise((res, rej) => {
window.sendMessage("listActions", {
window
.sendMessage('listActions', {
type: 'remove',
items: [address],
listName: 'blockedAddresses'
listName: 'blockedAddresses',
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userBlockedRef.current}
delete copyObject[address]
userBlockedRef.current = copyObject
const copyObject = { ...userBlockedRef.current };
delete copyObject[address];
userBlockedRef.current = copyObject;
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", 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'
listName: 'blockedNames',
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userNamesBlockedRef.current}
copyObject[name] = true
userNamesBlockedRef.current = copyObject
const copyObject = { ...userNamesBlockedRef.current };
copyObject[name] = true;
userNamesBlockedRef.current = copyObject;
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
console.error('Failed qortalRequest', error);
});
});
})
}
if (address) {
await new Promise((res, rej) => {
window.sendMessage("listActions", {
window
.sendMessage('listActions', {
type: 'add',
items: [address],
listName: 'blockedAddresses'
listName: 'blockedAddresses',
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userBlockedRef.current}
copyObject[address] = true
userBlockedRef.current = copyObject
const copyObject = { ...userBlockedRef.current };
copyObject[address] = true;
userBlockedRef.current = copyObject;
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
console.error('Failed qortalRequest', error);
});
});
})
}
}, [])
}, []);
return {
isUserBlocked,
addToBlockList,
removeBlockFromList,
getAllBlockedUsers
getAllBlockedUsers,
};
};

View File

@ -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) => {
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");
throw new Error('network error');
}
const data = await response.json();
userInfoRef.current = {
...userInfoRef.current,
[address]: data?.level
}
return data?.level
[address]: data?.level,
};
return data?.level;
} catch (error) {
//error
}
}, [])
}, []);
return {
getIndividualUserInfo,

View File

@ -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 (
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Spacer height="40px" />
<Box
sx={{
width: "320px",
justifyContent: "center",
flexDirection: "column",
alignItems: "center",
padding: "15px",
outline: "1px solid gray",
borderRadius: "4px",
width: '320px',
justifyContent: 'center',
flexDirection: 'column',
alignItems: 'center',
padding: '15px',
outline: '1px solid gray',
borderRadius: '4px',
}}
>
<Typography
sx={{
textAlign: "center",
fontSize: "1.2rem",
fontWeight: "bold",
textAlign: 'center',
fontSize: '1.2rem',
fontWeight: 'bold',
}}
>
Are you a new user?
</Typography>
</Typography>{' '}
// TODO translate
<Spacer height="20px" />
<Typography>
Please message us on Telegram or Discord if you need 4 QORT to start
@ -43,25 +43,25 @@ export const NewUsersCTA = ({ balance }) => {
<Spacer height="20px" />
<Box
sx={{
width: "100%",
display: "flex",
gap: "10px",
justifyContent: "center",
width: '100%',
display: 'flex',
gap: '10px',
justifyContent: 'center',
}}
>
<ButtonBase
sx={{
textDecoration: "underline",
textDecoration: 'underline',
}}
onClick={() => {
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 }) => {
</ButtonBase>
<ButtonBase
sx={{
textDecoration: "underline",
textDecoration: 'underline',
}}
onClick={() => {
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');
}
}}
>

View File

@ -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 (
<Box
sx={{
display: "flex",
gap: "20px",
flexWrap: "wrap",
flexDirection: "column",
width: "322px",
display: 'flex',
gap: '20px',
flexWrap: 'wrap',
flexDirection: 'column',
width: '322px',
}}
>
<Tooltip
title={
<span style={{ color: "white", fontSize: "14px", fontWeight: 700 }}>
<span style={{ color: 'white', fontSize: '14px', fontWeight: 700 }}>
Based on the latest 20 trades
</span>
}
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,
},
},
}}
>
<Box
sx={{
width: "322px",
display: "flex",
flexDirection: "row",
gap: "10px",
justifyContent: "space-between",
display: 'flex',
flexDirection: 'row',
gap: '10px',
justifyContent: 'space-between',
width: '322px',
}}
>
<Typography
sx={{
fontSize: "1rem",
fontWeight: "bold",
fontSize: '1rem',
fontWeight: 'bold',
}}
>
Price
{t('core:price', { postProcess: 'capitalize' })}
</Typography>
{!ltcPerQort ? (
<BarSpinner width="16px" color="white" />
) : (
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
}}
>
{ltcPerQort} LTC/QORT
@ -168,28 +170,28 @@ export const QortPrice = () => {
</Tooltip>
<Box
sx={{
width: "322px",
display: "flex",
flexDirection: "row",
gap: "10px",
justifyContent: "space-between",
display: 'flex',
flexDirection: 'row',
gap: '10px',
justifyContent: 'space-between',
width: '322px',
}}
>
<Typography
sx={{
fontSize: "1rem",
fontWeight: "bold",
fontSize: '1rem',
fontWeight: 'bold',
}}
>
Supply
{t('core:supply', { postProcess: 'capitalize' })}
</Typography>
{!supply ? (
<BarSpinner width="16px" color="white" />
) : (
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
}}
>
{supply} QORT
@ -198,58 +200,56 @@ export const QortPrice = () => {
</Box>
<Tooltip
title={
<span style={{ color: "white", fontSize: "14px", fontWeight: 700 }}>
<span style={{ color: 'white', fontSize: '14px', fontWeight: 700 }}>
{lastBlock?.timestamp && formatDate(lastBlock?.timestamp)}
</span>
}
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,
},
},
}}
>
<Box
sx={{
width: "322px",
display: "flex",
flexDirection: "row",
gap: "10px",
justifyContent: "space-between",
display: 'flex',
flexDirection: 'row',
gap: '10px',
justifyContent: 'space-between',
width: '322px',
}}
>
<Typography
sx={{
fontSize: "1rem",
fontWeight: "bold",
fontSize: '1rem',
fontWeight: 'bold',
}}
>
Last height
{t('core:last_height', { postProcess: 'capitalize' })}
</Typography>
{!lastBlock?.height ? (
<BarSpinner width="16px" color="white" />
) : (
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
}}
>
{lastBlock?.height}
</Typography>
)}
</Box>
</Tooltip>
</Box>

View File

@ -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) {

View File

@ -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',
})}
</span>
}
placement="left"

View File

@ -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 ? (
<IconWrapper
disableWidth={disableWidth}
label="Save"
label={t('core:save_options.save', {
postProcess: 'capitalize',
})}
selected={false}
>
<SaveIcon
@ -225,6 +238,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
<SaveIcon color={hasChanged && !isLoading ? '#5EB049' : undefined} />
)}
</ButtonBase>
<Popover
open={!!anchorEl}
anchorEl={anchorEl}
@ -247,19 +261,19 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
{isUsingImportExportSettings && (
<Box
sx={{
padding: '15px',
display: 'flex',
flexDirection: 'column',
gap: 1,
padding: '15px',
width: '100%',
}}
>
<Box
sx={{
width: '100%',
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: '100%',
}}
>
<Typography
@ -267,8 +281,10 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
fontSize: '14px',
}}
>
You are using the export/import way of saving settings.
</Typography>
{t('core:save_options.settings', {
postProcess: 'capitalize',
})}
</Typography>{' '}
<Spacer height="40px" />
<Button
size="small"
@ -294,7 +310,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
},
}}
>
Use QDN saving
{t('core:save_options.qdn', {
postProcess: 'capitalize',
})}
</Button>
</Box>
</Box>
@ -302,10 +320,10 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
{!isUsingImportExportSettings && (
<Box
sx={{
padding: '15px',
display: 'flex',
flexDirection: 'column',
gap: 1,
padding: '15px',
width: '100%',
}}
>
@ -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',
})}
</Typography>
</Box>
) : (
@ -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',
})}
</Typography>
<Spacer height="10px" />
<LoadingButton
sx={{
backgroundColor: 'var(--green)',
@ -364,7 +386,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
onClick={saveToQdn}
variant="contained"
>
Save to QDN
{t('core:save_options.save_qdn', {
postProcess: 'capitalize',
})}
</LoadingButton>
<Spacer height="20px" />
{!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',
})}
</Typography>
<Spacer height="10px" />
<LoadingButton
@ -396,7 +421,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
},
}}
>
Revert to QDN
{t('core:save_options.revert_qdn', {
postProcess: 'capitalize',
})}
</LoadingButton>
</>
)}
@ -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',
})}
</Typography>
<Spacer height="10px" />
<LoadingButton
@ -417,7 +446,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
onClick={revertChanges}
variant="contained"
>
Revert to default
{t('core:save_options.revert_default', {
postProcess: 'capitalize',
})}
</LoadingButton>
</>
)}
@ -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',
})}
</Typography>
<Spacer height="10px" />
<LoadingButton
@ -460,7 +492,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
},
}}
>
Overwrite to QDN
{t('core:save_options.overwrite_qdn', {
postProcess: 'capitalize',
})}
</LoadingButton>
</Box>
)}
@ -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',
})}
</Typography>
</Box>
)}
@ -533,8 +569,11 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
}
}}
>
Import
{t('core:import', {
postProcess: 'capitalize',
})}
</ButtonBase>
<ButtonBase
onClick={async () => {
try {
@ -555,7 +594,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
}
}}
>
Export
{t('core:export', {
postProcess: 'capitalize',
})}
</ButtonBase>
</Box>
</Box>

View File

@ -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',
}}
>
<Tooltip title={themeMode === 'dark' ? 'Light mode' : 'Dark mode'}>
<Tooltip
title={
themeMode === 'dark'
? t('core:theme.light', {
postProcess: 'capitalize',
})
: t('core:theme.light', {
postProcess: 'capitalize',
})
}
>
<IconButton onClick={toggleTheme}>
{themeMode === 'dark' ? <LightModeIcon /> : <DarkModeIcon />}
</IconButton>

View File

@ -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 = () => {
})}
</Tabs>
<DialogTitle sx={{ m: 0, p: 2 }}>
{selectedTutorial?.title} {` Tutorial`}
</DialogTitle>
<DialogTitle sx={{ m: 0, p: 2 }}>{selectedTutorial?.title}</DialogTitle>
<IconButton
aria-label="close"
@ -91,7 +91,7 @@ export const Tutorials = () => {
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={handleClose}>
Close
{t('core:close', { postProcess: 'capitalize' })}
</Button>
</DialogActions>
</Dialog>
@ -138,7 +138,7 @@ export const Tutorials = () => {
<DialogActions>
<Button variant="contained" onClick={handleClose}>
Close
{t('core:close', { postProcess: 'capitalize' })}
</Button>
</DialogActions>
</Dialog>

View File

@ -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<any>(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',

View File

@ -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(
<>

View File

@ -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,
},
},
}));