diff --git a/src/components/settings-modal/account-settings/account-settings.module.css b/src/components/settings-modal/account-settings/account-settings.module.css
new file mode 100644
index 00000000..e69de29b
diff --git a/src/components/settings-modal/account-settings/account-settings.tsx b/src/components/settings-modal/account-settings/account-settings.tsx
new file mode 100644
index 00000000..40337d86
--- /dev/null
+++ b/src/components/settings-modal/account-settings/account-settings.tsx
@@ -0,0 +1,183 @@
+import { useEffect, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { createAccount, deleteAccount, exportAccount, importAccount, setAccount, setActiveAccount, useAccount, useAccounts } from '@plebbit/plebbit-react-hooks';
+import stringify from 'json-stringify-pretty-compact';
+import styles from './account-settings.module.css';
+
+const AccountSettings = () => {
+ const { t } = useTranslation();
+ const account = useAccount();
+ const { accounts } = useAccounts();
+ const [text, setText] = useState('');
+ const [switchToLastAccount, setSwitchToLastAccount] = useState(false);
+
+ const accountJson = useMemo(
+ () => stringify({ account: { ...account, plebbit: undefined, karma: undefined, plebbitReactOptions: undefined, unreadNotificationCount: undefined } }),
+ [account],
+ );
+
+ useEffect(() => {
+ setText(accountJson);
+ }, [accountJson]);
+
+ useEffect(() => {
+ if (switchToLastAccount && accounts.length > 0) {
+ const lastAccount = accounts[accounts.length - 1];
+ setActiveAccount(lastAccount.name);
+ setSwitchToLastAccount(false);
+ }
+ }, [accounts, switchToLastAccount]);
+
+ const _createAccount = async () => {
+ try {
+ await createAccount();
+ } catch (error) {
+ if (error instanceof Error) {
+ alert(error.message);
+ console.log(error);
+ } else {
+ console.error('An unknown error occurred:', error);
+ }
+ }
+ setSwitchToLastAccount(true);
+ };
+
+ const _deleteAccount = (accountName: string) => {
+ if (!accountName) {
+ return;
+ } else if (window.confirm(t('delete_confirm', { value: accountName, interpolation: { escapeValue: false } }))) {
+ if (window.confirm(t('double_confirm'))) {
+ deleteAccount(accountName);
+ }
+ } else {
+ return;
+ }
+ };
+
+ const saveAccount = async () => {
+ try {
+ const newAccount = JSON.parse(text).account;
+ // force keeping the same id, makes it easier to copy paste
+ await setAccount({ ...newAccount, id: account?.id });
+ alert(`Saved ${newAccount.name}`);
+ } catch (error) {
+ if (error instanceof Error) {
+ alert(error.message);
+ console.log(error);
+ } else {
+ console.error('An unknown error occurred:', error);
+ }
+ }
+ };
+
+ const _exportAccount = async () => {
+ try {
+ const accountString = await exportAccount();
+ const accountObject = JSON.parse(accountString);
+ const formattedAccountJson = JSON.stringify(accountObject, null, 2);
+
+ // Create a Blob from the JSON string
+ const blob = new Blob([formattedAccountJson], { type: 'application/json' });
+
+ // Create a URL for the Blob
+ const fileUrl = URL.createObjectURL(blob);
+
+ // Create a temporary download link
+ const link = document.createElement('a');
+ link.href = fileUrl;
+ link.download = `${account.name}.json`;
+
+ // Append the link, trigger the download, then remove the link
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ // Release the Blob URL
+ URL.revokeObjectURL(fileUrl);
+ } catch (error) {
+ if (error instanceof Error) {
+ alert(error.message);
+ console.log(error);
+ } else {
+ console.error('An unknown error occurred:', error);
+ }
+ }
+ };
+
+ const _importAccount = async () => {
+ // Create a file input element
+ const fileInput = document.createElement('input');
+ fileInput.type = 'file';
+ fileInput.accept = '.json';
+
+ // Handle file selection
+ fileInput.onchange = async (event) => {
+ try {
+ const files = (event.target as HTMLInputElement).files;
+ if (!files || files.length === 0) {
+ throw new Error('No file selected.');
+ }
+ const file = files[0];
+
+ // Read the file content
+ const reader = new FileReader();
+ reader.onload = async (e) => {
+ const fileContent = e.target!.result; // Non-null assertion
+ if (typeof fileContent !== 'string') {
+ throw new Error('File content is not a string.');
+ }
+ const newAccount = JSON.parse(fileContent);
+ await importAccount(fileContent);
+ setSwitchToLastAccount(true);
+ alert(`Imported ${newAccount.account?.name}`);
+ };
+ reader.readAsText(file);
+ } catch (error) {
+ if (error instanceof Error) {
+ alert(error.message);
+ console.log(error);
+ } else {
+ console.error('An unknown error occurred:', error);
+ }
+ }
+ };
+
+ // Trigger file selection dialog
+ fileInput.click();
+ };
+
+ const accountsOptions = accounts.map((account) => (
+
+ ));
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default AccountSettings;
diff --git a/src/components/settings-modal/account-settings/index.ts b/src/components/settings-modal/account-settings/index.ts
new file mode 100644
index 00000000..289fc6ee
--- /dev/null
+++ b/src/components/settings-modal/account-settings/index.ts
@@ -0,0 +1 @@
+export { default } from './account-settings';
diff --git a/src/components/settings-modal/crypto-address-setting/crypto-address-setting.module.css b/src/components/settings-modal/crypto-address-setting/crypto-address-setting.module.css
new file mode 100644
index 00000000..676d5be6
--- /dev/null
+++ b/src/components/settings-modal/crypto-address-setting/crypto-address-setting.module.css
@@ -0,0 +1,11 @@
+.red {
+ color: red;
+}
+
+.yellow {
+ color: yellow;
+}
+
+.green {
+ color: green;
+}
\ No newline at end of file
diff --git a/src/components/settings-modal/crypto-address-setting/crypto-address-setting.tsx b/src/components/settings-modal/crypto-address-setting/crypto-address-setting.tsx
new file mode 100644
index 00000000..453ef1dc
--- /dev/null
+++ b/src/components/settings-modal/crypto-address-setting/crypto-address-setting.tsx
@@ -0,0 +1,161 @@
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useAccount, setAccount, useResolvedAuthorAddress } from '@plebbit/plebbit-react-hooks';
+import styles from './crypto-address-setting.module.css';
+
+const CryptoAddressSetting = () => {
+ const { t } = useTranslation();
+ const account = useAccount();
+
+ const [cryptoState, setCryptoState] = useState({
+ cryptoAddress: '',
+ checkingCryptoAddress: false,
+ showResolvingMessage: false,
+ resolveString: t('crypto_address_verification'),
+ resolveClass: '',
+ });
+
+ const [savedCryptoAddress, setSavedCryptoAddress] = useState(false);
+
+ const author = { ...account?.author, address: cryptoState.cryptoAddress };
+ const { resolvedAddress, state, error, chainProvider } = useResolvedAuthorAddress({ author, cache: false });
+
+ const checkCryptoAddress = () => {
+ if (!cryptoState.cryptoAddress || !cryptoState.cryptoAddress.includes('.')) {
+ alert(t('enter_crypto_address'));
+ return;
+ }
+
+ let resolveString = '';
+ let resolveClass = '';
+
+ if (state === 'failed') {
+ resolveString = error instanceof Error ? `failed to resolve crypto address, error: ${error.message}` : 'cannot resolve crypto address, unknown error';
+ resolveClass = styles.red;
+ } else if (state === 'resolving') {
+ resolveString = `resolving from ${chainProvider?.urls}`;
+ resolveClass = styles.yellow;
+ } else if (resolvedAddress && resolvedAddress === account?.signer?.address) {
+ resolveString = t('crypto_address_yours');
+ resolveClass = styles.green;
+ } else if (resolvedAddress && resolvedAddress !== account?.signer?.address) {
+ resolveString = t('crypto_address_not_yours');
+ resolveClass = styles.red;
+ } else {
+ resolveString = t('crypto_address_verification');
+ resolveClass = '';
+ }
+
+ setCryptoState((prevState) => ({
+ ...prevState,
+ showResolvingMessage: true,
+ resolveString,
+ resolveClass,
+ }));
+ };
+
+ const saveCryptoAddress = async () => {
+ if (!cryptoState.cryptoAddress || !cryptoState.cryptoAddress.includes('.')) {
+ alert(t('enter_crypto_address'));
+ return;
+ } else if (resolvedAddress && resolvedAddress !== account?.signer?.address) {
+ alert(t('crypto_address_not_yours'));
+ return;
+ } else if (cryptoState.cryptoAddress && !resolvedAddress) {
+ alert(t('crypto_address_not_resolved'));
+ return;
+ } else if (resolvedAddress && resolvedAddress === account?.signer?.address) {
+ try {
+ await setAccount({ ...account, author: { ...account?.author, address: cryptoState.cryptoAddress } });
+ setSavedCryptoAddress(true);
+
+ setTimeout(() => {
+ setSavedCryptoAddress(false);
+ }, 2000);
+
+ setCryptoState((prevState) => ({
+ ...prevState,
+ savedCryptoAddress: true,
+ cryptoAddress: '',
+ checkingCryptoAddress: false,
+ }));
+ } catch (error) {
+ if (error instanceof Error) {
+ alert(error.message);
+ console.log(error);
+ } else {
+ console.error('An unknown error occurred:', error);
+ }
+ }
+ setSavedCryptoAddress(true);
+ setCryptoState((prevState) => ({
+ ...prevState,
+ checkingCryptoAddress: false,
+ showResolvingMessage: false,
+ resolveString: t('crypto_address_verification'),
+ resolveClass: '',
+ }));
+ }
+ };
+
+ const [showCryptoAddressInfo, setShowCryptoAddressInfo] = useState(false);
+
+ return (
+
+
+
setCryptoState((prevState) => ({ ...prevState, cryptoAddress: e.target.value }))}
+ />
+
+
+ {showCryptoAddressInfo && (
+
+ steps to set a .eth user address:
+
+
+ -
+ go to{' '}
+
+ app.ens.domains
+ {' '}
+ and search the address
+
+ - once you own the address, go to its page, click on "records", then "edit records"
+ - add a new text record with name "plebbit-author-address" and value: {account?.signer?.address}
+
+ steps to set a .sol user address:
+
+
+ -
+ go to{' '}
+
+ sns.id
+ {' '}
+ and search the address
+
+ - once you own the address, go to your profile, click the address menu "...", then "create subdomain"
+ - enter subdomain "plebbit-author-address" and create
+ - go to subdomain, "content", change content to: {account?.signer?.address}
+
+
+ )}
+ {savedCryptoAddress &&
{t('saved')}}
+
+
+ {' '}
+ {cryptoState.resolveString}
+
+
+ );
+};
+
+export default CryptoAddressSetting;
diff --git a/src/components/settings-modal/crypto-address-setting/index.ts b/src/components/settings-modal/crypto-address-setting/index.ts
new file mode 100644
index 00000000..2c4a8540
--- /dev/null
+++ b/src/components/settings-modal/crypto-address-setting/index.ts
@@ -0,0 +1 @@
+export { default } from './crypto-address-setting';
diff --git a/src/components/settings-modal/settings-modal.tsx b/src/components/settings-modal/settings-modal.tsx
index 42970fa9..03c21627 100644
--- a/src/components/settings-modal/settings-modal.tsx
+++ b/src/components/settings-modal/settings-modal.tsx
@@ -1,11 +1,11 @@
-import { useEffect, useMemo, useState } from 'react';
+import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import { createAccount, deleteAccount, exportAccount, importAccount, setAccount, setActiveAccount, useAccount, useAccounts } from '@plebbit/plebbit-react-hooks';
-import stringify from 'json-stringify-pretty-compact';
import styles from './settings-modal.module.css';
import useTheme from '../../hooks/use-theme';
import packageJson from '../../../package.json';
+import AccountSettings from './account-settings';
+import CryptoAddressSetting from './crypto-address-setting';
const commitRef = process.env.REACT_APP_COMMIT_REF;
const isElectron = window.isElectron === true;
@@ -104,179 +104,11 @@ const InterfaceLanguage = () => {
);
};
-const AccountSettings = () => {
- const { t } = useTranslation();
- const account = useAccount();
- const { accounts } = useAccounts();
- const [text, setText] = useState('');
- const [switchToLastAccount, setSwitchToLastAccount] = useState(false);
-
- const accountJson = useMemo(
- () => stringify({ account: { ...account, plebbit: undefined, karma: undefined, plebbitReactOptions: undefined, unreadNotificationCount: undefined } }),
- [account],
- );
-
- useEffect(() => {
- setText(accountJson);
- }, [accountJson]);
-
- useEffect(() => {
- if (switchToLastAccount && accounts.length > 0) {
- const lastAccount = accounts[accounts.length - 1];
- setActiveAccount(lastAccount.name);
- setSwitchToLastAccount(false);
- }
- }, [accounts, switchToLastAccount]);
-
- const _createAccount = async () => {
- try {
- await createAccount();
- } catch (error) {
- if (error instanceof Error) {
- alert(error.message);
- console.log(error);
- } else {
- console.error('An unknown error occurred:', error);
- }
- }
- setSwitchToLastAccount(true);
- };
-
- const _deleteAccount = (accountName: string) => {
- if (!accountName) {
- return;
- } else if (window.confirm(t('delete_confirm', { value: accountName, interpolation: { escapeValue: false } }))) {
- if (window.confirm(t('double_confirm'))) {
- deleteAccount(accountName);
- }
- } else {
- return;
- }
- };
-
- const saveAccount = async () => {
- try {
- const newAccount = JSON.parse(text).account;
- // force keeping the same id, makes it easier to copy paste
- await setAccount({ ...newAccount, id: account?.id });
- alert(`Saved ${newAccount.name}`);
- } catch (error) {
- if (error instanceof Error) {
- alert(error.message);
- console.log(error);
- } else {
- console.error('An unknown error occurred:', error);
- }
- }
- };
-
- const _exportAccount = async () => {
- try {
- const accountString = await exportAccount();
- const accountObject = JSON.parse(accountString);
- const formattedAccountJson = JSON.stringify(accountObject, null, 2);
-
- // Create a Blob from the JSON string
- const blob = new Blob([formattedAccountJson], { type: 'application/json' });
-
- // Create a URL for the Blob
- const fileUrl = URL.createObjectURL(blob);
-
- // Create a temporary download link
- const link = document.createElement('a');
- link.href = fileUrl;
- link.download = `${account.name}.json`;
-
- // Append the link, trigger the download, then remove the link
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
-
- // Release the Blob URL
- URL.revokeObjectURL(fileUrl);
- } catch (error) {
- if (error instanceof Error) {
- alert(error.message);
- console.log(error);
- } else {
- console.error('An unknown error occurred:', error);
- }
- }
- };
-
- const _importAccount = async () => {
- // Create a file input element
- const fileInput = document.createElement('input');
- fileInput.type = 'file';
- fileInput.accept = '.json';
-
- // Handle file selection
- fileInput.onchange = async (event) => {
- try {
- const files = (event.target as HTMLInputElement).files;
- if (!files || files.length === 0) {
- throw new Error('No file selected.');
- }
- const file = files[0];
-
- // Read the file content
- const reader = new FileReader();
- reader.onload = async (e) => {
- const fileContent = e.target!.result; // Non-null assertion
- if (typeof fileContent !== 'string') {
- throw new Error('File content is not a string.');
- }
- const newAccount = JSON.parse(fileContent);
- await importAccount(fileContent);
- setSwitchToLastAccount(true);
- alert(`Imported ${newAccount.account?.name}`);
- };
- reader.readAsText(file);
- } catch (error) {
- if (error instanceof Error) {
- alert(error.message);
- console.log(error);
- } else {
- console.error('An unknown error occurred:', error);
- }
- }
- };
-
- // Trigger file selection dialog
- fileInput.click();
- };
-
- const accountsOptions = accounts.map((account) => (
-
- ));
-
+const CryptoWalletSettings = () => {
return (
-
-
-
-
-
-
-
+ <>
+
+ >
);
};
@@ -297,6 +129,8 @@ const SettingsModal = () => {
};
const [showAccountSettings, setShowAccountSettings] = useState(false);
+ const [showCryptoAddressSetting, setShowCryptoAddressSetting] = useState(false);
+ const [showCryptoWalletSettings, setShowCryptoWalletSettings] = useState(false);
const [showPlebbitOptionsSettings, setShowPlebbitOptionsSettings] = useState(false);
return (
@@ -333,6 +167,20 @@ const SettingsModal = () => {
{showAccountSettings && }
+
+
+
+ {showCryptoAddressSetting && }
+
+
+
+ {showCryptoWalletSettings && }