Qortal-Hub/src/App.tsx
2025-04-24 21:33:04 +02:00

3727 lines
111 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
createContext,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useDropzone } from 'react-dropzone';
import {
Box,
Button,
ButtonBase,
Checkbox,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
FormControlLabel,
Tooltip,
Typography,
useTheme,
} from '@mui/material';
import { JsonView, allExpanded, darkStyles } from 'react-json-view-lite';
import 'react-json-view-lite/dist/index.css';
import { decryptStoredWallet } from './utils/decryptWallet';
import { CountdownCircleTimer } from 'react-countdown-circle-timer';
import Logo1Dark from './assets/svgs/Logo1Dark.svg';
import RefreshIcon from '@mui/icons-material/Refresh';
import DownloadIcon from '@mui/icons-material/Download';
import Copy from './assets/svgs/Copy.svg';
import ltcLogo from './assets/ltc.png';
import PersonSearchIcon from '@mui/icons-material/PersonSearch';
import qortLogo from './assets/qort.png';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { Return } from './assets/Icons/Return.tsx';
import WarningIcon from '@mui/icons-material/Warning';
import Success from './assets/svgs/Success.svg';
import './utils/seedPhrase/RandomSentenceGenerator';
import EngineeringIcon from '@mui/icons-material/Engineering';
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
import PriorityHighIcon from '@mui/icons-material/PriorityHigh';
import {
createAccount,
saveFileToDisk,
saveSeedPhraseToDisk,
} from './utils/generateWallet/generateWallet';
import { kdf } from './deps/kdf';
import { generateSaveWalletData } from './utils/generateWallet/storeWallet';
import { crypto, walletVersion } from './constants/decryptWallet';
import PhraseWallet from './utils/generateWallet/phrase-wallet';
import {
AddressBox,
AppContainer,
AuthenticatedContainer,
AuthenticatedContainerInnerLeft,
AuthenticatedContainerInnerRight,
CustomButton,
CustomButtonAccept,
CustomLabel,
TextItalic,
TextP,
TextSpan,
} from './styles/App-styles.ts';
import { Spacer } from './common/Spacer';
import { Loader } from './components/Loader';
import { PasswordField, ErrorText } from './components';
import { Group, requestQueueMemberNames } from './components/Group/Group';
import { TaskManager } from './components/TaskManager/TaskManager.tsx';
import { useModal } from './common/useModal';
import { CustomizedSnackbars } from './components/Snackbar/Snackbar';
import SettingsIcon from '@mui/icons-material/Settings';
import LogoutIcon from '@mui/icons-material/Logout';
import HelpIcon from '@mui/icons-material/Help';
import {
cleanUrl,
getProtocol,
getWallets,
groupApi,
groupApiSocket,
storeWallets,
} from './background';
import {
executeEvent,
subscribeToEvent,
unsubscribeFromEvent,
} from './utils/events';
import {
requestQueueCommentCount,
requestQueuePublishedAccouncements,
} from './components/Chat/GroupAnnouncements';
import { requestQueueGroupJoinRequests } from './components/Group/GroupJoinRequests';
import { DrawerComponent } from './components/Drawer/Drawer';
import { AddressQRCode } from './components/AddressQRCode';
import { Settings } from './components/Group/Settings';
import { MainAvatar } from './components/MainAvatar';
import { useRetrieveDataLocalStorage } from './useRetrieveDataLocalStorage';
import { useQortalGetSaveSettings } from './useQortalGetSaveSettings';
import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil';
import {
canSaveSettingToQdnAtom,
enabledDevModeAtom,
fullScreenAtom,
groupsPropertiesAtom,
hasSettingsChangedAtom,
isDisabledEditorEnterAtom,
isUsingImportExportSettingsAtom,
lastPaymentSeenTimestampAtom,
mailsAtom,
oldPinnedAppsAtom,
qMailLastEnteredTimestampAtom,
settingsLocalLastUpdatedAtom,
settingsQDNLastUpdatedAtom,
sortablePinnedAppsAtom,
} from './atoms/global';
import { useAppFullScreen } from './useAppFullscreen';
import { NotAuthenticated } from './ExtStates/NotAuthenticated';
import { handleGetFileFromIndexedDB } from './utils/indexedDB';
import { Wallets } from './Wallets';
import { useFetchResources } from './common/useFetchResources';
import { Tutorials } from './components/Tutorials/Tutorials';
import { useHandleTutorials } from './components/Tutorials/useHandleTutorials';
import { useHandleUserInfo } from './components/Group/useHandleUserInfo';
import { Minting } from './components/Minting/Minting';
import { isRunningGateway } from './qortalRequests';
import { QMailStatus } from './components/QMailStatus';
import { GlobalActions } from './components/GlobalActions/GlobalActions';
import { useBlockedAddresses } from './components/Group/useBlockUsers';
import { WalletIcon } from './assets/Icons/WalletIcon';
import { UserLookup } from './components/UserLookup.tsx/UserLookup';
import { RegisterName } from './components/RegisterName';
import { BuyQortInformation } from './components/BuyQortInformation';
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';
import LanguageSelector from './components/Language/LanguageSelector.tsx';
type extStates =
| 'not-authenticated'
| 'authenticated'
| 'send-qort'
| 'web-app-request-connection'
| 'web-app-request-payment'
| 'web-app-request-authentication'
| 'download-wallet'
| 'create-wallet'
| 'transfer-success-regular'
| 'transfer-success-request'
| 'wallet-dropped'
| 'web-app-request-buy-order'
| 'buy-order-submitted'
| 'wallets'
| 'group';
interface MyContextInterface {
txList: any[];
memberGroups: any[];
setTxList: (val) => void;
setMemberGroups: (val) => void;
isShow: boolean;
onCancel: () => void;
onOk: () => void;
show: () => void;
message: any;
}
const defaultValues: MyContextInterface = {
txList: [],
memberGroups: [],
setTxList: () => {},
setMemberGroups: () => {},
isShow: false,
onCancel: () => {},
onOk: () => {},
show: () => {},
message: {
publishFee: '',
message: '',
},
};
export const allQueues = {
requestQueueCommentCount: requestQueueCommentCount,
requestQueuePublishedAccouncements: requestQueuePublishedAccouncements,
requestQueueMemberNames: requestQueueMemberNames,
requestQueueGroupJoinRequests: requestQueueGroupJoinRequests,
};
const controlAllQueues = (action) => {
Object.keys(allQueues).forEach((key) => {
const val = allQueues[key];
try {
if (typeof val[action] === 'function') {
val[action]();
}
} catch (error) {
console.error(error);
}
});
};
export const clearAllQueues = () => {
Object.keys(allQueues).forEach((key) => {
const val = allQueues[key];
try {
val.clear();
} catch (error) {
console.error(error);
}
});
};
export const pauseAllQueues = () => {
controlAllQueues('pause');
window.sendMessage('pauseAllQueues', {}).catch((error) => {
console.error(
'Failed to pause all queues:',
error.message || 'An error occurred'
);
});
};
export const resumeAllQueues = () => {
controlAllQueues('resume');
window.sendMessage('resumeAllQueues', {}).catch((error) => {
console.error(
'Failed to resume all queues:',
error.message || 'An error occurred'
);
});
};
const defaultValuesGlobal = {
openTutorialModal: null,
setOpenTutorialModal: () => {},
};
export const MyContext = createContext<MyContextInterface>(defaultValues);
export const GlobalContext =
createContext<MyContextInterface>(defaultValuesGlobal);
export let globalApiKey: string | null = null;
export const getBaseApiReact = (customApi?: string) => {
if (customApi) {
return customApi;
}
if (globalApiKey?.url) {
return globalApiKey?.url;
} else {
return groupApi;
}
};
export const getArbitraryEndpointReact = () => {
if (globalApiKey) {
return `/arbitrary/resources/searchsimple`;
} else {
return `/arbitrary/resources/searchsimple`;
}
};
export const getBaseApiReactSocket = (customApi?: string) => {
if (customApi) {
return customApi;
}
if (globalApiKey?.url) {
return `${
getProtocol(globalApiKey?.url) === 'http' ? 'ws://' : 'wss://'
}${cleanUrl(globalApiKey?.url)}`;
} else {
return groupApiSocket;
}
};
export const isMainWindow = true;
function App() {
const [extState, setExtstate] = useState<extStates>('not-authenticated');
const [desktopViewMode, setDesktopViewMode] = useState('home');
const [backupjson, setBackupjson] = useState<any>(null);
const [rawWallet, setRawWallet] = useState<any>(null);
const [ltcBalanceLoading, setLtcBalanceLoading] = useState<boolean>(false);
const [qortBalanceLoading, setQortBalanceLoading] = useState<boolean>(false);
const [decryptedWallet, setdecryptedWallet] = useState<any>(null);
const [requestConnection, setRequestConnection] = useState<any>(null);
const [requestBuyOrder, setRequestBuyOrder] = useState<any>(null);
const [authenticatedMode, setAuthenticatedMode] = useState('qort');
const [requestAuthentication, setRequestAuthentication] = useState<any>(null);
const [userInfo, setUserInfo] = useState<any>(null);
const [balance, setBalance] = useState<any>(null);
const [ltcBalance, setLtcBalance] = useState<any>(null);
const [paymentTo, setPaymentTo] = useState<string>('');
const [paymentAmount, setPaymentAmount] = useState<number>(0);
const [paymentPassword, setPaymentPassword] = useState<string>('');
const [sendPaymentError, setSendPaymentError] = useState<string>('');
const [sendPaymentSuccess, setSendPaymentSuccess] = useState<string>('');
const [countdown, setCountdown] = useState<null | number>(null);
const [walletToBeDownloaded, setWalletToBeDownloaded] = useState<any>(null);
const [walletToBeDownloadedPassword, setWalletToBeDownloadedPassword] =
useState<string>('');
const [isMain, setIsMain] = useState<boolean>(true);
const isMainRef = useRef(false);
const [authenticatePassword, setAuthenticatePassword] = useState<string>('');
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 [
walletToBeDownloadedPasswordConfirm,
setWalletToBeDownloadedPasswordConfirm,
] = useState<string>('');
const [walletToBeDownloadedError, setWalletToBeDownloadedError] =
useState<string>('');
const [walletToBeDecryptedError, setWalletToBeDecryptedError] =
useState<string>('');
const [txList, setTxList] = useState([]);
const [memberGroups, setMemberGroups] = useState([]);
const [isFocused, setIsFocused] = useState(true);
const [hasSettingsChanged, setHasSettingsChanged] = useRecoilState(
hasSettingsChangedAtom
);
const balanceSetIntervalRef = useRef(null);
const { downloadResource } = useFetchResources();
const holdRefExtState = useRef<extStates>('not-authenticated');
const isFocusedRef = useRef<boolean>(true);
const {
showTutorial,
openTutorialModal,
shownTutorialsInitiated,
setOpenTutorialModal,
hasSeenGettingStarted,
} = useHandleTutorials();
const { isShow, onCancel, onOk, show, message } = useModal();
const {
isShow: isShowUnsavedChanges,
onCancel: onCancelUnsavedChanges,
onOk: onOkUnsavedChanges,
show: showUnsavedChanges,
message: messageUnsavedChanges,
} = useModal();
const {
isShow: isShowInfo,
onCancel: onCancelInfo,
onOk: onOkInfo,
show: showInfo,
message: messageInfo,
} = useModal();
const {
onCancel: onCancelQortalRequest,
onOk: onOkQortalRequest,
show: showQortalRequest,
isShow: isShowQortalRequest,
message: messageQortalRequest,
} = useModal();
const {
onCancel: onCancelQortalRequestExtension,
onOk: onOkQortalRequestExtension,
show: showQortalRequestExtension,
isShow: isShowQortalRequestExtension,
message: messageQortalRequestExtension,
} = useModal();
const [isRunningPublicNode, setIsRunningPublicNode] = useState(false);
const [infoSnack, setInfoSnack] = useState(null);
const [openSnack, setOpenSnack] = useState(false);
const [hasLocalNode, setHasLocalNode] = useState(false);
const [isOpenDrawerProfile, setIsOpenDrawerProfile] = useState(false);
const [isOpenDrawerLookup, setIsOpenDrawerLookup] = useState(false);
const [apiKey, setApiKey] = useState('');
const [isOpenSendQort, setIsOpenSendQort] = useState(false);
const [isOpenSendQortSuccess, setIsOpenSendQortSuccess] = useState(false);
const [rootHeight, setRootHeight] = useState('100%');
const {
isUserBlocked,
addToBlockList,
removeBlockFromList,
getAllBlockedUsers,
} = useBlockedAddresses();
const [currentNode, setCurrentNode] = useState({
url: 'http://127.0.0.1:12391',
});
const [useLocalNode, setUseLocalNode] = useState(false);
const [confirmRequestRead, setConfirmRequestRead] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [showSeed, setShowSeed] = useState(false);
const [creationStep, setCreationStep] = useState(1);
const { getIndividualUserInfo } = useHandleUserInfo();
const qortalRequestCheckbox1Ref = useRef(null);
useRetrieveDataLocalStorage(userInfo?.address);
useQortalGetSaveSettings(userInfo?.name, extState === 'authenticated');
const [fullScreen, setFullScreen] = useRecoilState(fullScreenAtom);
const [isEnabledDevMode, setIsEnabledDevMode] =
useRecoilState(enabledDevModeAtom);
const setIsDisabledEditorEnter = useSetRecoilState(isDisabledEditorEnterAtom);
const [isOpenMinting, setIsOpenMinting] = useState(false);
const { toggleFullScreen } = useAppFullScreen(setFullScreen);
const generatorRef = useRef(null);
const exportSeedphrase = () => {
const seedPhrase = generatorRef.current.parsedString;
saveSeedPhraseToDisk(seedPhrase);
};
const passwordRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (extState === 'wallet-dropped' && passwordRef.current) {
passwordRef.current.focus();
}
}, [extState]);
useEffect(() => {
const isDevModeFromStorage = localStorage.getItem('isEnabledDevMode');
if (isDevModeFromStorage) {
setIsEnabledDevMode(JSON.parse(isDevModeFromStorage));
}
}, []);
useEffect(() => {
isRunningGateway()
.then((res) => {
setIsRunningPublicNode(res);
})
.catch((error) => {
console.error(error);
});
}, [extState]);
useEffect(() => {
if (!shownTutorialsInitiated) return;
if (extState === 'not-authenticated') {
showTutorial('create-account');
} else if (extState === 'create-wallet' && walletToBeDownloaded) {
showTutorial('important-information');
} else if (extState === 'authenticated') {
showTutorial('getting-started');
}
}, [extState, walletToBeDownloaded, shownTutorialsInitiated]);
useEffect(() => {
// Attach a global event listener for double-click
const handleDoubleClick = () => {
toggleFullScreen();
};
// Add the event listener to the root HTML document
document.documentElement.addEventListener('dblclick', handleDoubleClick);
// Clean up the event listener on unmount
return () => {
document.documentElement.removeEventListener(
'dblclick',
handleDoubleClick
);
};
}, [toggleFullScreen]);
//resets for recoil
const resetAtomSortablePinnedAppsAtom = useResetRecoilState(
sortablePinnedAppsAtom
);
const resetAtomIsUsingImportExportSettingsAtom = useResetRecoilState(
isUsingImportExportSettingsAtom
);
const resetAtomCanSaveSettingToQdnAtom = useResetRecoilState(
canSaveSettingToQdnAtom
);
const resetAtomSettingsQDNLastUpdatedAtom = useResetRecoilState(
settingsQDNLastUpdatedAtom
);
const resetAtomSettingsLocalLastUpdatedAtom = useResetRecoilState(
settingsLocalLastUpdatedAtom
);
const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom);
const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(
qMailLastEnteredTimestampAtom
);
const resetAtomMailsAtom = useResetRecoilState(mailsAtom);
const resetGroupPropertiesAtom = useResetRecoilState(groupsPropertiesAtom);
const resetLastPaymentSeenTimestampAtom = useResetRecoilState(
lastPaymentSeenTimestampAtom
);
const resetAllRecoil = () => {
resetAtomSortablePinnedAppsAtom();
resetAtomCanSaveSettingToQdnAtom();
resetAtomSettingsQDNLastUpdatedAtom();
resetAtomSettingsLocalLastUpdatedAtom();
resetAtomOldPinnedAppsAtom();
resetAtomIsUsingImportExportSettingsAtom();
resetAtomQMailLastEnteredTimestampAtom();
resetAtomMailsAtom();
resetGroupPropertiesAtom();
resetLastPaymentSeenTimestampAtom();
};
const handleSetGlobalApikey = (key) => {
globalApiKey = key;
};
useEffect(() => {
try {
setIsLoading(true);
window
.sendMessage('getApiKey')
.then((response) => {
if (response) {
handleSetGlobalApikey(response);
setApiKey(response);
}
})
.catch((error) => {
console.error(
'Failed to get API key:',
error?.message || 'An error occurred'
); // TODO translate
})
.finally(() => {
window
.sendMessage('getWalletInfo')
.then((response) => {
if (response && response?.walletInfo) {
setRawWallet(response?.walletInfo);
if (
holdRefExtState.current === 'web-app-request-payment' ||
holdRefExtState.current === 'web-app-request-connection' ||
holdRefExtState.current === 'web-app-request-buy-order'
)
return;
if (response?.hasKeyPair) {
setExtstate('authenticated');
} else {
setExtstate('wallet-dropped');
}
}
})
.catch((error) => {
console.error('Failed to get wallet info:', error);
});
});
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
if (extState) {
holdRefExtState.current = extState;
}
}, [extState]);
useEffect(() => {
try {
const val = localStorage.getItem('settings-disable-editor-enter');
if (val) {
const parsedVal = JSON.parse(val);
if (parsedVal === false || parsedVal === true) {
setIsDisabledEditorEnter(parsedVal);
}
}
} catch (error) {
console.log(error);
}
}, []);
useEffect(() => {
isFocusedRef.current = isFocused;
}, [isFocused]);
const address = useMemo(() => {
if (!rawWallet?.address0) return '';
return rawWallet.address0;
}, [rawWallet]);
const { getRootProps, getInputProps } = useDropzone({
accept: {
'application/json': ['.json'], // Only accept JSON files
},
maxFiles: 1,
onDrop: async (acceptedFiles) => {
const file: any = acceptedFiles[0];
const fileContents = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onabort = () => reject('File reading was aborted');
reader.onerror = () => reject('File reading has failed');
reader.onload = () => {
// Resolve the promise with the reader result when reading completes
resolve(reader.result);
};
// Read the file as text
reader.readAsText(file);
});
let error: any = null;
let pf: any;
try {
if (typeof fileContents !== 'string') return;
pf = JSON.parse(fileContents);
} catch (e) {
console.log(error);
}
try {
const requiredFields = [
'address0',
'salt',
'iv',
'version',
'encryptedSeed',
'mac',
'kdfThreads',
];
for (const field of requiredFields) {
if (!(field in pf)) throw new Error(field + ' not found in JSON');
}
setRawWallet(pf);
// setExtstate("authenticated");
setExtstate('wallet-dropped');
setdecryptedWallet(null);
} catch (e) {
console.log(e);
}
},
});
const saveWalletFunc = async (password: string) => {
let wallet = structuredClone(rawWallet);
const res = await decryptStoredWallet(password, wallet);
const wallet2 = new PhraseWallet(res, wallet?.version || walletVersion);
wallet = await wallet2.generateSaveWalletData(
password,
crypto.kdfThreads,
() => {}
);
setWalletToBeDownloaded({
wallet,
qortAddress: rawWallet.address0,
});
return {
wallet,
qortAddress: rawWallet.address0,
};
};
const balanceSetInterval = () => {
try {
if (balanceSetIntervalRef?.current) {
clearInterval(balanceSetIntervalRef?.current);
}
let isCalling = false;
balanceSetIntervalRef.current = setInterval(async () => {
if (isCalling) return;
isCalling = true;
window
.sendMessage('balance')
.then((response) => {
if (!response?.error && !isNaN(+response)) {
setBalance(response);
}
isCalling = false;
})
.catch((error) => {
console.error('Failed to get balance:', error);
isCalling = false;
});
}, 40000);
} catch (error) {
console.error(error);
}
};
const getBalanceFunc = () => {
setQortBalanceLoading(true);
window
.sendMessage('balance')
.then((response) => {
if (!response?.error && !isNaN(+response)) {
setBalance(response);
}
setQortBalanceLoading(false);
})
.catch((error) => {
console.error('Failed to get balance:', error);
setQortBalanceLoading(false);
})
.finally(() => {
balanceSetInterval();
});
};
const getLtcBalanceFunc = () => {
setLtcBalanceLoading(true);
window
.sendMessage('ltcBalance')
.then((response) => {
if (!response?.error && !isNaN(+response)) {
setLtcBalance(response);
}
setLtcBalanceLoading(false);
})
.catch((error) => {
console.error('Failed to get LTC balance:', error);
setLtcBalanceLoading(false);
});
};
const clearAllStates = () => {
setRequestConnection(null);
setRequestAuthentication(null);
};
const qortalRequestPermissonFromExtension = async (message, event) => {
if (message.action === 'QORTAL_REQUEST_PERMISSION') {
try {
if (message?.payload?.checkbox1) {
qortalRequestCheckbox1Ref.current =
message?.payload?.checkbox1?.value || false;
}
setConfirmRequestRead(false);
await showQortalRequestExtension(message?.payload);
if (qortalRequestCheckbox1Ref.current) {
event.source.postMessage(
{
action: 'QORTAL_REQUEST_PERMISSION_RESPONSE',
requestId: message?.requestId,
result: {
accepted: true,
checkbox1: qortalRequestCheckbox1Ref.current,
},
},
event.origin
);
return;
}
event.source.postMessage(
{
action: 'QORTAL_REQUEST_PERMISSION_RESPONSE',
requestId: message?.requestId,
result: {
accepted: true,
},
},
event.origin
);
} catch (error) {
event.source.postMessage(
{
action: 'QORTAL_REQUEST_PERMISSION_RESPONSE',
requestId: message?.requestId,
result: {
accepted: false,
},
},
event.origin
);
}
}
};
useEffect(() => {
// Handler function for incoming messages
const messageHandler = (event) => {
if (event.origin !== window.location.origin) {
return;
}
const message = event.data;
if (message?.action === 'CHECK_FOCUS') {
event.source.postMessage(
{ action: 'CHECK_FOCUS_RESPONSE', isFocused: isFocusedRef.current },
event.origin
);
} else if (message.action === 'NOTIFICATION_OPEN_DIRECT') {
executeEvent('openDirectMessage', {
from: message.payload.from,
});
} else if (message.action === 'NOTIFICATION_OPEN_GROUP') {
executeEvent('openGroupMessage', {
from: message.payload.from,
});
} else if (message.action === 'NOTIFICATION_OPEN_ANNOUNCEMENT_GROUP') {
executeEvent('openGroupAnnouncement', {
from: message.payload.from,
});
} else if (message.action === 'NOTIFICATION_OPEN_THREAD_NEW_POST') {
executeEvent('openThreadNewPost', {
data: message.payload.data,
});
} else if (
message.action === 'QORTAL_REQUEST_PERMISSION' &&
message?.isFromExtension
) {
qortalRequestPermissonFromExtension(message, event);
} else if (message?.action === 'getFileFromIndexedDB') {
handleGetFileFromIndexedDB(event);
}
};
// Attach the event listener
window.addEventListener('message', messageHandler);
// Clean up the event listener on component unmount
return () => {
window.removeEventListener('message', messageHandler);
};
}, []);
//param = isDecline
const confirmPayment = (isDecline: boolean) => {
// REMOVED FOR MOBILE APP
};
const confirmBuyOrder = (isDecline: boolean) => {
// REMOVED FOR MOBILE APP
};
const responseToConnectionRequest = (
isOkay: boolean,
hostname: string,
interactionId: string
) => {
// REMOVED FOR MOBILE APP
};
const getUserInfo = useCallback(async (useTimer?: boolean) => {
try {
if (useTimer) {
await new Promise((res) => {
setTimeout(() => {
res(null);
}, 10000);
});
}
window
.sendMessage('userInfo')
.then((response) => {
if (response && !response.error) {
setUserInfo(response);
}
})
.catch((error) => {
console.error('Failed to get user info:', error);
});
getBalanceFunc();
} catch (error) {
console.log(error);
}
}, []);
useEffect(() => {
if (!address) return;
getUserInfo();
}, [address]);
useEffect(() => {
return () => {
console.log('exit');
};
}, []);
useEffect(() => {
if (
authenticatedMode === 'ltc' &&
!ltcBalanceLoading &&
ltcBalance === null
) {
getLtcBalanceFunc();
}
}, [authenticatedMode]);
const confirmPasswordToDownload = async () => {
try {
setWalletToBeDownloadedError('');
if (!walletToBeDownloadedPassword) {
setSendPaymentError('Please enter your password');
return;
}
setIsLoading(true);
await new Promise<void>((res) => {
setTimeout(() => {
res();
}, 250);
});
const res = await saveWalletFunc(walletToBeDownloadedPassword);
} catch (error: any) {
setWalletToBeDownloadedError(error?.message);
} finally {
setIsLoading(false);
}
};
const saveFileToDiskFunc = async () => {
try {
await saveFileToDisk(
walletToBeDownloaded.wallet,
walletToBeDownloaded.qortAddress
);
} catch (error: any) {
setWalletToBeDownloadedError(error?.message);
}
};
const saveWalletToLocalStorage = async (newWallet) => {
try {
getWallets()
.then((res) => {
if (res && Array.isArray(res)) {
const wallets = [...res, newWallet];
storeWallets(wallets);
} else {
storeWallets([newWallet]);
}
setIsLoading(false);
})
.catch((error) => {
console.error(error);
setIsLoading(false);
});
} catch (error) {
console.error(error);
}
};
const createAccountFunc = async () => {
try {
if (!walletToBeDownloadedPassword) {
setWalletToBeDownloadedError('Please enter a password');
return;
}
if (!walletToBeDownloadedPasswordConfirm) {
setWalletToBeDownloadedError('Please confirm your password');
return;
}
if (
walletToBeDownloadedPasswordConfirm !== walletToBeDownloadedPassword
) {
setWalletToBeDownloadedError('Password fields do not match!');
return;
}
setIsLoading(true);
await new Promise<void>((res) => {
setTimeout(() => {
res();
}, 250);
});
const res = await createAccount(generatorRef.current.parsedString);
const wallet = await res.generateSaveWalletData(
walletToBeDownloadedPassword,
crypto.kdfThreads,
() => {}
);
window
.sendMessage('decryptWallet', {
password: walletToBeDownloadedPassword,
wallet,
})
.then((response) => {
if (response && !response.error) {
setRawWallet(wallet);
saveWalletToLocalStorage(wallet);
setWalletToBeDownloaded({
wallet,
qortAddress: wallet.address0,
});
window
.sendMessage('userInfo')
.then((response2) => {
setIsLoading(false);
if (response2 && !response2.error) {
setUserInfo(response2);
}
})
.catch((error) => {
setIsLoading(false);
console.error('Failed to get user info:', error);
});
getBalanceFunc();
} else if (response?.error) {
setIsLoading(false);
setWalletToBeDecryptedError(response.error);
}
})
.catch((error) => {
setIsLoading(false);
console.error('Failed to decrypt wallet:', error);
});
} catch (error: any) {
setWalletToBeDownloadedError(error?.message);
setIsLoading(false);
}
};
const logoutFunc = async () => {
try {
if (hasSettingsChanged) {
await showUnsavedChanges({
message:
'Your settings have changed. If you logout you will lose your changes. Click on the save button in the header to keep your changed settings.',
}); // TODO translate
} else if (extState === 'authenticated') {
await showUnsavedChanges({
message: 'Are you sure you would like to logout?',
});
}
window
.sendMessage('logout', {})
.then((response) => {
if (response) {
executeEvent('logout-event', {});
resetAllStates();
}
})
.catch((error) => {
console.error(
'Failed to log out:',
error.message || 'An error occurred'
);
});
} catch (error) {
console.log(error);
}
};
const returnToMain = () => {
setPaymentTo('');
setPaymentAmount(0);
setPaymentPassword('');
setSendPaymentError('');
setSendPaymentSuccess('');
setCountdown(null);
setWalletToBeDownloaded(null);
setWalletToBeDownloadedPassword('');
setShowSeed(false);
setCreationStep(1);
setExtstate('authenticated');
setIsOpenSendQort(false);
setIsOpenSendQortSuccess(false);
};
const resetAllStates = () => {
setExtstate('not-authenticated');
setAuthenticatedMode('qort');
setBackupjson(null);
setRawWallet(null);
setdecryptedWallet(null);
setRequestConnection(null);
setRequestBuyOrder(null);
setRequestAuthentication(null);
setUserInfo(null);
setBalance(null);
setLtcBalance(null);
setPaymentTo('');
setPaymentAmount(0);
setPaymentPassword('');
setSendPaymentError('');
setSendPaymentSuccess('');
setCountdown(null);
setWalletToBeDownloaded(null);
setWalletToBeDownloadedPassword('');
setShowSeed(false);
setCreationStep(1);
setWalletToBeDownloadedPasswordConfirm('');
setWalletToBeDownloadedError('');
setSendqortState(null);
setHasLocalNode(false);
setTxList([]);
setMemberGroups([]);
resetAllRecoil();
if (balanceSetIntervalRef?.current) {
clearInterval(balanceSetIntervalRef?.current);
}
};
function roundUpToDecimals(number, decimals = 8) {
const factor = Math.pow(10, decimals); // Create a factor based on the number of decimals
return Math.ceil(+number * factor) / factor;
}
const authenticateWallet = async () => {
try {
setIsLoading(true);
setWalletToBeDecryptedError('');
await new Promise<void>((res) => {
setTimeout(() => {
res();
}, 250);
});
window
.sendMessage(
'decryptWallet',
{
password: authenticatePassword,
wallet: rawWallet,
},
120000
)
.then((response) => {
if (response && !response.error) {
setAuthenticatePassword('');
setExtstate('authenticated');
setWalletToBeDecryptedError('');
window
.sendMessage('userInfo')
.then((response) => {
setIsLoading(false);
if (response && !response.error) {
setUserInfo(response);
}
})
.catch((error) => {
setIsLoading(false);
console.error('Failed to get user info:', error);
});
getBalanceFunc();
window
.sendMessage('getWalletInfo')
.then((response) => {
if (response && response.walletInfo) {
setRawWallet(response.walletInfo);
}
})
.catch((error) => {
console.error('Failed to get wallet info:', error);
});
} else if (response?.error) {
setIsLoading(false);
setWalletToBeDecryptedError(response.error);
}
})
.catch((error) => {
setIsLoading(false);
console.error('Failed to decrypt wallet:', error);
});
} catch (error) {
setWalletToBeDecryptedError('Unable to authenticate. Wrong password');
}
};
useEffect(() => {
if (!isMainWindow) return;
// Handler for when the window gains focus
const handleFocus = () => {
setIsFocused(true);
};
// Handler for when the window loses focus
const handleBlur = () => {
setIsFocused(false);
};
// Attach the event listeners
window.addEventListener('focus', handleFocus);
window.addEventListener('blur', handleBlur);
// Optionally, listen for visibility changes
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
setIsFocused(true);
} else {
setIsFocused(false);
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
// Cleanup the event listeners on component unmount
return () => {
window.removeEventListener('focus', handleFocus);
window.removeEventListener('blur', handleBlur);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
const openGlobalSnackBarFunc = (e) => {
const message = e.detail?.message;
const type = e.detail?.type;
setOpenSnack(true);
setInfoSnack({
type,
message,
});
};
useEffect(() => {
subscribeToEvent('openGlobalSnackBar', openGlobalSnackBarFunc);
return () => {
unsubscribeFromEvent('openGlobalSnackBar', openGlobalSnackBarFunc);
};
}, []);
const openPaymentInternal = (e) => {
const directAddress = e.detail?.address;
const name = e.detail?.name;
setIsOpenSendQort(true);
setPaymentTo(name || directAddress);
};
useEffect(() => {
subscribeToEvent('openPaymentInternal', openPaymentInternal);
return () => {
unsubscribeFromEvent('openPaymentInternal', openPaymentInternal);
};
}, []);
const renderProfileLeft = () => {
return (
<AuthenticatedContainerInnerLeft
sx={{
overflowY: 'auto',
padding: '0px 20px',
minWidth: '225px',
}}
>
<Spacer height="20px" />
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'flex-start',
}}
>
{authenticatedMode === 'qort' && (
<Tooltip
title={
<span style={{ fontSize: '14px', fontWeight: 700 }}>
LITECOIN WALLET
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<img
onClick={() => {
setAuthenticatedMode('ltc');
}}
src={ltcLogo}
style={{
cursor: 'pointer',
width: '20px',
height: 'auto',
}}
/>
</Tooltip>
)}
{authenticatedMode === 'ltc' && (
<Tooltip
title={
<span style={{ fontSize: '14px', fontWeight: 700 }}>
QORTAL WALLET
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<img
onClick={() => {
setAuthenticatedMode('qort');
}}
src={qortLogo}
style={{
cursor: 'pointer',
width: '20px',
height: 'auto',
}}
/>
</Tooltip>
)}
</Box>
<Spacer height="48px" />
{authenticatedMode === 'ltc' ? (
<>
<img src={ltcLogo} />
<Spacer height="32px" />
<CopyToClipboard text={rawWallet?.ltcAddress}>
<AddressBox>
{rawWallet?.ltcAddress?.slice(0, 6)}...
{rawWallet?.ltcAddress?.slice(-4)} <img src={Copy} />
</AddressBox>
</CopyToClipboard>
<Spacer height="10px" />
{ltcBalanceLoading && (
<CircularProgress color="success" size={16} />
)}
{!isNaN(+ltcBalance) && !ltcBalanceLoading && (
<Box
sx={{
gap: '10px',
display: 'flex',
alignItems: 'center',
}}
>
<TextP
sx={{
fontSize: '20px',
fontWeight: 700,
lineHeight: '24px',
textAlign: 'center',
}}
>
{ltcBalance} LTC
</TextP>
<RefreshIcon
onClick={getLtcBalanceFunc}
sx={{
fontSize: '16px',
cursor: 'pointer',
}}
/>
</Box>
)}
<AddressQRCode targetAddress={rawWallet?.ltcAddress} />
</>
) : (
<>
<MainAvatar
setOpenSnack={setOpenSnack}
setInfoSnack={setInfoSnack}
myName={userInfo?.name}
balance={balance}
/>
<Spacer height="32px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '24px',
fontSize: '20px',
}}
>
{userInfo?.name}
</TextP>
<Spacer height="10px" />
<CopyToClipboard text={rawWallet?.address0}>
<AddressBox>
{rawWallet?.address0?.slice(0, 6)}...
{rawWallet?.address0?.slice(-4)} <img src={Copy} />
</AddressBox>
</CopyToClipboard>
<Spacer height="10px" />
{qortBalanceLoading && (
<CircularProgress color="success" size={16} />
)}
{!qortBalanceLoading && balance >= 0 && (
<Box
sx={{
gap: '10px',
display: 'flex',
alignItems: 'center',
}}
>
<TextP
sx={{
fontSize: '20px',
fontWeight: 700,
lineHeight: '24px',
textAlign: 'center',
}}
>
{balance?.toFixed(2)} QORT
</TextP>
<RefreshIcon
onClick={getBalanceFunc}
sx={{
fontSize: '16px',
cursor: 'pointer',
}}
/>
</Box>
)}
<Spacer height="35px" />
{userInfo && !userInfo?.name && (
<TextP
sx={{
color: 'red',
cursor: 'pointer',
fontSize: '16px',
fontWeight: 500,
lineHeight: 1.2,
marginTop: '10px',
textAlign: 'center',
textDecoration: 'underline',
}}
onClick={() => {
executeEvent('openRegisterName', {});
}}
>
REGISTER NAME
</TextP>
)}
<Spacer height="20px" />
<CustomButton
onClick={() => {
setIsOpenSendQort(true);
// setExtstate("send-qort");
setIsOpenDrawerProfile(false);
}}
>
Transfer QORT
</CustomButton>
<AddressQRCode targetAddress={rawWallet?.address0} />
</>
)}
<TextP
sx={{
cursor: 'pointer',
fontSize: '12px',
fontWeight: 500,
lineHeight: '24px',
marginTop: '10px',
textAlign: 'center',
textDecoration: 'underline',
}}
onClick={async () => {
executeEvent('addTab', {
data: { service: 'APP', name: 'q-trade' },
});
executeEvent('open-apps-mode', {});
}}
>
Get QORT at Q-Trade
</TextP>
</AuthenticatedContainerInnerLeft>
);
};
const renderProfile = () => {
return (
<AuthenticatedContainer
sx={{
backgroundColor: theme.palette.background.default,
display: 'flex',
justifyContent: 'flex-end',
width: 'auto',
}}
>
{desktopViewMode !== 'apps' &&
desktopViewMode !== 'dev' &&
desktopViewMode !== 'chat' && <>{renderProfileLeft()}</>}
<AuthenticatedContainerInnerRight
sx={{
height: '100%',
justifyContent: 'space-between',
}}
>
<Box
sx={{
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
width: '100%',
}}
>
<Spacer height="20px" />
<ButtonBase
onClick={() => {
logoutFunc();
setIsOpenDrawerProfile(false);
}}
>
<Tooltip
title={
<span
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
{t('core:action.logout')}
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<LogoutIcon />
</Tooltip>
</ButtonBase>
<Spacer height="20px" />
<ButtonBase
onClick={() => {
setIsSettingsOpen(true);
}}
>
<Tooltip
title={
<span
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
{t('core:settings')}
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<SettingsIcon />
</Tooltip>
</ButtonBase>
<Spacer height="20px" />
<ButtonBase
onClick={() => {
setIsOpenDrawerLookup(true);
}}
>
<Tooltip
title={
<span
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
{t('core:user_lookup')}
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<PersonSearchIcon />
</Tooltip>
</ButtonBase>
<Spacer height="20px" />
<ButtonBase
onClick={() => {
executeEvent('openWalletsApp', {});
}}
>
<Tooltip
title={
<span
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
{t('core:wallet_other')}
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<AccountBalanceWalletIcon />
</Tooltip>
</ButtonBase>
{desktopViewMode !== 'home' && (
<>
<Spacer height="20px" />
<Tooltip
title={
<span
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
{t('auth:account.your')}
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<ButtonBase
onClick={() => {
setIsOpenDrawerProfile(true);
}}
>
<WalletIcon width="25" />
</ButtonBase>
</Tooltip>
</>
)}
<Spacer height="20px" />
<QMailStatus />
<Spacer height="20px" />
{extState === 'authenticated' && (
<GeneralNotifications address={userInfo?.address} />
)}
</Box>
<Box
sx={{
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
width: '100%',
}}
>
{extState === 'authenticated' && isMainWindow && (
<MyContext.Provider
value={{
txList,
setTxList,
memberGroups,
setMemberGroups,
isShow,
onCancel,
onOk,
show,
userInfo,
message,
rootHeight,
showInfo,
openSnackGlobal: openSnack,
setOpenSnackGlobal: setOpenSnack,
infoSnackCustom: infoSnack,
setInfoSnackCustom: setInfoSnack,
downloadResource,
getIndividualUserInfo,
isUserBlocked,
addToBlockList,
removeBlockFromList,
getAllBlockedUsers,
isRunningPublicNode,
}}
>
<TaskManager getUserInfo={getUserInfo} />
<GlobalActions memberGroups={memberGroups} />
</MyContext.Provider>
)}
<Spacer height="20px" />
<ButtonBase
onClick={async () => {
try {
const res = await isRunningGateway();
if (res)
throw new Error(
'Cannot view minting details on the gateway'
);
setIsOpenMinting(true);
} catch (error) {
setOpenSnack(true);
setInfoSnack({
type: 'error',
message: error?.message,
});
}
}}
>
<Tooltip
title={
<span
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
{t('core:minting_status')}
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<EngineeringIcon />
</Tooltip>
</ButtonBase>
<Spacer height="20px" />
{(desktopViewMode === 'apps' || desktopViewMode === 'home') && (
<ButtonBase
onClick={() => {
if (desktopViewMode === 'apps') {
showTutorial('qapps', true);
} else {
showTutorial('getting-started', true);
}
}}
>
<Tooltip
title={
<span
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
{t('core:tutorial')}
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<HelpIcon />
</Tooltip>
</ButtonBase>
)}
<Spacer height="20px" />
<ButtonBase
onClick={() => {
setExtstate('download-wallet');
setIsOpenDrawerProfile(false);
}}
>
<Tooltip
title={
<span
style={{
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
{t('core:action.backup_wallet')}
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<DownloadIcon />
</Tooltip>
</ButtonBase>
<Spacer height="40px" />
</Box>
</AuthenticatedContainerInnerRight>
</AuthenticatedContainer>
);
};
return (
<AppContainer
sx={{
height: '100vh',
// backgroundImage: desktopViewMode === "apps" && 'url("appsBg.svg")',
// backgroundSize: desktopViewMode === "apps" && "cover",
// backgroundPosition: desktopViewMode === "apps" && "center",
// backgroundRepeat: desktopViewMode === "apps" && "no-repeat",
}}
>
<PdfViewer />
<GlobalContext.Provider
value={{
showTutorial,
openTutorialModal,
setOpenTutorialModal,
downloadResource,
hasSeenGettingStarted,
}}
>
<Tutorials />
{extState === 'not-authenticated' && (
<NotAuthenticated
getRootProps={getRootProps}
getInputProps={getInputProps}
setExtstate={setExtstate}
apiKey={apiKey}
globalApiKey={globalApiKey}
setApiKey={setApiKey}
handleSetGlobalApikey={handleSetGlobalApikey}
currentNode={currentNode}
setCurrentNode={setCurrentNode}
setUseLocalNode={setUseLocalNode}
useLocalNode={useLocalNode}
/>
)}
{/* {extState !== "not-authenticated" && (
<button onClick={logoutFunc}>logout</button>
)} */}
{extState === 'authenticated' && isMainWindow && (
<MyContext.Provider
value={{
txList,
setTxList,
memberGroups,
setMemberGroups,
isShow,
onCancel,
onOk,
show,
userInfo,
message,
rootHeight,
showInfo,
openSnackGlobal: openSnack,
setOpenSnackGlobal: setOpenSnack,
infoSnackCustom: infoSnack,
setInfoSnackCustom: setInfoSnack,
downloadResource,
getIndividualUserInfo,
isUserBlocked,
addToBlockList,
removeBlockFromList,
getAllBlockedUsers,
isRunningPublicNode,
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
height: '100vh',
width: '100vw',
}}
>
<Group
balance={balance}
desktopViewMode={desktopViewMode}
isFocused={isFocused}
isMain={isMain}
isOpenDrawerProfile={isOpenDrawerProfile}
logoutFunc={logoutFunc}
myAddress={address}
setDesktopViewMode={setDesktopViewMode}
setIsOpenDrawerProfile={setIsOpenDrawerProfile}
userInfo={userInfo}
/>
{renderProfile()}
</Box>
</MyContext.Provider>
)}
{isOpenSendQort && isMainWindow && (
<Box
sx={{
alignItems: 'center',
background: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
height: '100%',
position: 'fixed',
width: '100%',
zIndex: 10000,
}}
>
<Spacer height="22px" />
<Box
sx={{
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'flex-start',
maxWidth: '700px',
paddingLeft: '22px',
width: '100%',
}}
>
<Return
style={{
cursor: 'pointer',
height: '24px',
}}
onClick={returnToMain}
/>
</Box>
<Spacer height="35px" />
<QortPayment
balance={balance}
show={show}
onSuccess={() => {
setIsOpenSendQort(false);
setIsOpenSendQortSuccess(true);
}}
defaultPaymentTo={paymentTo}
/>
</Box>
)}
{isShowQortalRequest && !isMainWindow && (
<>
<Spacer height="120px" />
<Box
sx={{
display: 'flex',
justifyContent: 'center',
width: '100%',
}}
>
<TextP
sx={{
lineHeight: 1.2,
maxWidth: '90%',
textAlign: 'center',
}}
>
{messageQortalRequest?.text1}
</TextP>
</Box>
{messageQortalRequest?.text2 && (
<>
<Spacer height="10px" />
<Box
sx={{
display: 'flex',
justifyContent: 'flex-start',
width: '90%',
}}
>
<TextP
sx={{
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 'normal',
}}
>
{messageQortalRequest?.text2}
</TextP>
</Box>
<Spacer height="15px" />
</>
)}
{messageQortalRequest?.text3 && (
<>
<Box
sx={{
display: 'flex',
justifyContent: 'flex-start',
width: '90%',
}}
>
<TextP
sx={{
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 'normal',
}}
>
{messageQortalRequest?.text3}
</TextP>
<Spacer height="15px" />
</Box>
</>
)}
{messageQortalRequest?.text4 && (
<Box
sx={{
display: 'flex',
justifyContent: 'flex-start',
width: '90%',
}}
>
<TextP
sx={{
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 'normal',
}}
>
{messageQortalRequest?.text4}
</TextP>
</Box>
)}
{messageQortalRequest?.html && (
<div
dangerouslySetInnerHTML={{ __html: messageQortalRequest?.html }}
/>
)}
<Spacer height="15px" />
<TextP
sx={{
fontSize: '16px',
fontWeight: 700,
lineHeight: 1.2,
maxWidth: '90%',
textAlign: 'center',
}}
>
{messageQortalRequest?.highlightedText}
</TextP>
{messageQortalRequest?.fee && (
<>
<Spacer height="15px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 'normal',
maxWidth: '90%',
}}
>
{'Fee: '}
{messageQortalRequest?.fee}
{' QORT'}
</TextP>
<Spacer height="15px" />
</>
)}
{messageQortalRequest?.checkbox1 && (
<Box
sx={{
display: 'flex',
gap: '10px',
alignItems: 'center',
justifyContent: 'center',
width: '90%',
marginTop: '20px',
}}
>
<Checkbox
onChange={(e) => {
qortalRequestCheckbox1Ref.current = e.target.checked;
}}
edge="start"
tabIndex={-1}
disableRipple
defaultChecked={messageQortalRequest?.checkbox1?.value}
sx={{
'&.Mui-checked': {
color: 'white', // Customize the color when checked
},
'& .MuiSvgIcon-root': {
color: 'white',
},
}}
/>
<Typography
sx={{
fontSize: '14px',
}}
>
{messageQortalRequest?.checkbox1?.label}
</Typography>
</Box>
)}
<Spacer height="29px" />
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '14px',
}}
>
<CustomButton
sx={{
minWidth: '102px',
}}
onClick={() => onOkQortalRequest('accepted')}
>
accept
</CustomButton>
<CustomButton
sx={{
minWidth: '102px',
}}
onClick={() => onCancelQortalRequest()}
>
decline
</CustomButton>
</Box>
<ErrorText>{sendPaymentError}</ErrorText>
</>
)}
{extState === 'web-app-request-buy-order' && !isMainWindow && (
<>
<Spacer height="100px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '15px',
}}
>
The Application <br></br>{' '}
<TextItalic>{requestBuyOrder?.hostname}</TextItalic> <br></br>
<TextSpan>
is requesting {requestBuyOrder?.crosschainAtInfo?.length}{' '}
{`buy order${
requestBuyOrder?.crosschainAtInfo.length === 1 ? '' : 's'
}`}
</TextSpan>
</TextP>
<Spacer height="10px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '24px',
fontSize: '20px',
fontWeight: 700,
}}
>
{requestBuyOrder?.crosschainAtInfo?.reduce((latest, cur) => {
return latest + +cur?.qortAmount;
}, 0)}{' '}
QORT
</TextP>
<Spacer height="15px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '15px',
fontSize: '14px',
}}
>
FOR
</TextP>
<Spacer height="15px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '24px',
fontSize: '20px',
fontWeight: 700,
}}
>
{roundUpToDecimals(
requestBuyOrder?.crosschainAtInfo?.reduce((latest, cur) => {
return latest + +cur?.expectedForeignAmount;
}, 0)
)}
{` ${requestBuyOrder?.crosschainAtInfo?.[0]?.foreignBlockchain}`}
</TextP>
<Spacer height="29px" />
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '14px',
}}
>
<CustomButton
sx={{
minWidth: '102px',
}}
onClick={() => confirmBuyOrder(false)}
>
accept
</CustomButton>
<CustomButton
sx={{
minWidth: '102px',
}}
onClick={() => confirmBuyOrder(true)}
>
decline
</CustomButton>
</Box>
<ErrorText>{sendPaymentError}</ErrorText>
</>
)}
{extState === 'web-app-request-payment' && !isMainWindow && (
<>
<Spacer height="100px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '15px',
}}
>
The Application <br></br>{' '}
<TextItalic>{sendqortState?.hostname}</TextItalic> <br></br>
<TextSpan>is requesting a payment</TextSpan>
</TextP>
<Spacer height="10px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '15px',
fontSize: '10px',
}}
>
{sendqortState?.description}
</TextP>
<Spacer height="15px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '24px',
fontSize: '20px',
fontWeight: 700,
}}
>
{sendqortState?.amount} QORT
</TextP>
<Spacer height="29px" />
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '14px',
}}
>
<CustomButton
sx={{
minWidth: '102px',
}}
onClick={() => confirmPayment(false)}
>
accept
</CustomButton>
<CustomButton
sx={{
minWidth: '102px',
}}
onClick={() => confirmPayment(true)}
>
decline
</CustomButton>
</Box>
<ErrorText>{sendPaymentError}</ErrorText>
</>
)}
{extState === 'web-app-request-connection' && !isMainWindow && (
<>
<Spacer height="48px" />
<div
className="image-container"
style={{
width: '136px',
height: '154px',
}}
>
<img src={Logo1Dark} className="base-image" />
</div>
<Spacer height="38px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '15px',
}}
>
The Application <br></br>{' '}
<TextItalic>{requestConnection?.hostname}</TextItalic> <br></br>
<TextSpan>is requestion a connection</TextSpan>
</TextP>
<Spacer height="38px" />
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '14px',
}}
>
<CustomButton
sx={{
minWidth: '102px',
}}
onClick={() =>
responseToConnectionRequest(
true,
requestConnection?.hostname,
requestConnection.interactionId
)
}
>
accept
</CustomButton>
<CustomButton
sx={{
minWidth: '102px',
}}
onClick={() =>
responseToConnectionRequest(
false,
requestConnection?.hostname,
requestConnection.interactionId
)
}
>
decline
</CustomButton>
</Box>
</>
)}
{extState === 'web-app-request-authentication' && !isMainWindow && (
<>
<Spacer height="48px" />
<div
className="image-container"
style={{
width: '136px',
height: '154px',
}}
>
<img src={Logo1Dark} className="base-image" />
</div>
<Spacer height="38px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '15px',
}}
>
The Application <br></br>{' '}
<TextItalic>{requestConnection?.hostname}</TextItalic> <br></br>
<TextSpan>requests authentication</TextSpan>
</TextP>
<Spacer height="38px" />
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '14px',
}}
></Box>
<Spacer height="38px" />
<CustomButton {...getRootProps()}>
<input {...getInputProps()} />
Authenticate
</CustomButton>
<Spacer height="6px" />
<CustomButton
onClick={() => {
setExtstate('create-wallet');
}}
>
{t('auth:create_account', { postProcess: 'capitalize' })}
</CustomButton>
</>
)}
{extState === 'wallets' && (
<>
<Spacer height="22px" />
<Box
sx={{
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'flex-start',
maxWidth: '700px',
paddingLeft: '22px',
width: '100%',
}}
>
<Return
style={{
cursor: 'pointer',
height: '24px',
}}
onClick={() => {
setRawWallet(null);
setExtstate('not-authenticated');
logoutFunc();
}}
/>
</Box>
<Wallets
setRawWallet={setRawWallet}
setExtState={setExtstate}
rawWallet={rawWallet}
/>
</>
)}
{rawWallet && extState === 'wallet-dropped' && (
<>
<Spacer height="22px" />
<Box
sx={{
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'flex-start',
maxWidth: '700px',
paddingLeft: '22px',
width: '100%',
}}
>
<Return
style={{
cursor: 'pointer',
height: '24px',
}}
onClick={() => {
setRawWallet(null);
setExtstate('wallets');
logoutFunc();
}}
/>
</Box>
<Spacer height="10px" />
<div
className="image-container"
style={{
width: '136px',
height: '154px',
}}
>
<img src={Logo1Dark} className="base-image" />
</div>
<Spacer height="35px" />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography>
{rawWallet?.name ? rawWallet?.name : rawWallet?.address0}
</Typography>
<Spacer height="10px" />
<TextP
sx={{
textAlign: 'start',
lineHeight: '24px',
fontSize: '20px',
fontWeight: 600,
}}
>
{t('auth:authenticate', { postProcess: 'capitalize' })}
</TextP>
</Box>
<Spacer height="35px" />
<>
<CustomLabel htmlFor="standard-adornment-password">
{t('auth:wallet.password', { postProcess: 'capitalize' })}
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={authenticatePassword}
onChange={(e) => setAuthenticatePassword(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
authenticateWallet();
}
}}
ref={passwordRef}
/>
{useLocalNode ? (
<>
<Spacer height="20px" />
<Typography
sx={{
fontSize: '12px',
}}
>
{t('auth:node.using', { postProcess: 'capitalize' })}:{' '}
{currentNode?.url}
</Typography>
</>
) : (
<>
<Spacer height="20px" />
<Typography
sx={{
fontSize: '12px',
}}
>
{t('auth:node.using_public', { postProcess: 'capitalize' })}
</Typography>
</>
)}
<Spacer height="20px" />
<CustomButton onClick={authenticateWallet}>
{t('auth:authenticate', { postProcess: 'capitalize' })}
</CustomButton>
<ErrorText>{walletToBeDecryptedError}</ErrorText>
</>
</>
)}
{extState === 'download-wallet' && (
<>
<Spacer height="22px" />
<Box
sx={{
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'flex-start',
maxWidth: '700px',
paddingLeft: '22px',
width: '100%',
}}
>
<Return
style={{
cursor: 'pointer',
height: '24px',
}}
onClick={returnToMain}
/>
</Box>
<Spacer height="10px" />
<div
className="image-container"
style={{
width: '136px',
height: '154px',
}}
>
<img src={Logo1Dark} className="base-image" />
</div>
<Spacer height="35px" />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<TextP
sx={{
textAlign: 'start',
lineHeight: '24px',
fontSize: '20px',
fontWeight: 600,
}}
>
{t('auth:download_account', { postProcess: 'capitalize' })}
</TextP>
</Box>
<Spacer height="35px" />
{!walletToBeDownloaded && (
<>
<CustomLabel htmlFor="standard-adornment-password">
{t('auth:wallet.password_confirmation', {
postProcess: 'capitalize',
})}
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={walletToBeDownloadedPassword}
onChange={(e) =>
setWalletToBeDownloadedPassword(e.target.value)
}
/>
<Spacer height="20px" />
<CustomButton onClick={confirmPasswordToDownload}>
{t('auth:password_confirmation', {
postProcess: 'capitalize',
})}
</CustomButton>
<ErrorText>{walletToBeDownloadedError}</ErrorText>
</>
)}
{walletToBeDownloaded && (
<>
<CustomButton
onClick={async () => {
await saveFileToDiskFunc();
await showInfo({
message: t('auth:keep_secure', {
postProcess: 'capitalize',
}),
});
}}
>
{t('auth:download_account', {
postProcess: 'capitalize',
})}
</CustomButton>
</>
)}
</>
)}
{extState === 'create-wallet' && (
<>
{!walletToBeDownloaded && (
<>
<Spacer height="22px" />
<Box
sx={{
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'flex-start',
maxWidth: '700px',
paddingLeft: '22px',
width: '100%',
}}
>
<Return
style={{
cursor: 'pointer',
height: '24px',
}}
onClick={() => {
if (creationStep === 2) {
setCreationStep(1);
return;
}
setExtstate('not-authenticated');
setShowSeed(false);
setCreationStep(1);
setWalletToBeDownloadedPasswordConfirm('');
setWalletToBeDownloadedPassword('');
}}
/>
</Box>
<Spacer height="15px" />
<div
className="image-container"
style={{
width: '136px',
height: '154px',
}}
>
<img src={Logo1Dark} className="base-image" />
</div>
<Spacer height="38px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: 1.2,
fontSize: '18px',
}}
>
Set up your Qortal account
</TextP>
<Spacer height="14px" />
<Box
sx={{
display: 'flex',
maxWidth: '100%',
justifyContent: 'center',
padding: '10px',
}}
>
<Box
sx={{
display: creationStep === 1 ? 'flex' : 'none',
flexDirection: 'column',
alignItems: 'center',
width: '350px',
maxWidth: '95%',
}}
>
<Typography
sx={{
fontSize: '14px',
}}
>
A {' '}
<span
onClick={() => {
setShowSeed(true);
}}
style={{
fontSize: '14px',
color: 'steelblue',
cursor: 'pointer',
}}
>
SEEDPHRASE
</span>{' '}
has been randomly generated in the background.
</Typography>
<Typography
sx={{
fontSize: '14px',
marginTop: '5px',
}}
>
If you wish to VIEW THE SEEDPHRASE, click the word
'SEEDPHRASE' in this text. Seedphrases are used to
generate the private key for your Qortal account. For
security by default, seedphrases are NOT displayed unless
specifically chosen.
</Typography>
<Typography
sx={{
fontSize: '18px',
marginTop: '15px',
textAlign: 'center',
}}
>
Create your Qortal account by clicking{' '}
<span
style={{
fontWeight: 'bold',
}}
>
NEXT
</span>{' '}
below.
</Typography>
<Spacer height="17px" />
<CustomButton
onClick={() => {
setCreationStep(2);
}}
>
Next
</CustomButton>
</Box>
<div
style={{
display: 'none',
}}
>
<random-sentence-generator
ref={generatorRef}
template="adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun"
></random-sentence-generator>
</div>
<Dialog
open={showSeed}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogContent>
<Box
sx={{
alignItems: 'center',
display: showSeed ? 'flex' : 'none',
flexDirection: 'column',
gap: '10px',
maxWidth: '400px',
}}
>
<Typography
sx={{
fontSize: '14px',
}}
>
Your seedphrase
</Typography>
<Box
sx={{
background: theme.palette.background.paper,
borderRadius: '5px',
padding: '10px',
textAlign: 'center',
width: '100%',
}}
>
{generatorRef.current?.parsedString}
</Box>
<CustomButton
sx={{
padding: '7px',
fontSize: '12px',
}}
onClick={exportSeedphrase}
>
Export Seedphrase
</CustomButton>
</Box>
</DialogContent>
<DialogActions>
<Button
variant="contained"
onClick={() => setShowSeed(false)}
>
close
</Button>
</DialogActions>
</Dialog>
</Box>
<Box
sx={{
display: creationStep === 2 ? 'flex' : 'none',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Spacer height="14px" />
<CustomLabel htmlFor="standard-adornment-password">
Wallet Password
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={walletToBeDownloadedPassword}
onChange={(e) =>
setWalletToBeDownloadedPassword(e.target.value)
}
/>
<Spacer height="6px" />
<CustomLabel htmlFor="standard-adornment-password">
Confirm Wallet Password
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={walletToBeDownloadedPasswordConfirm}
onChange={(e) =>
setWalletToBeDownloadedPasswordConfirm(e.target.value)
}
/>
<Spacer height="5px" />
<Typography variant="body2">
There is no minimum length requirement
</Typography>
<Spacer height="17px" />
<CustomButton onClick={createAccountFunc}>
{t('auth:create_account', { postProcess: 'capitalize' })}
</CustomButton>
</Box>
<ErrorText>{walletToBeDownloadedError}</ErrorText>
</>
)}
{walletToBeDownloaded && (
<>
<Spacer height="48px" />
<img src={Success} />
<Spacer height="45px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '15px',
}}
>
Congrats, youre all set up!
</TextP>
<Spacer height="50px" />
<Box
sx={{
display: 'flex',
gap: '15px',
alignItems: 'center',
padding: '10px',
}}
>
<WarningIcon color="warning" />
<Typography>
Save your account in a place where you will remember it!
</Typography>
</Box>
<Spacer height="50px" />
<CustomButton
onClick={async () => {
await saveFileToDiskFunc();
returnToMain();
await showInfo({
message: `Keep your wallet file secure.`,
});
}}
>
{t('core:action.backup_account', {
postProcess: 'capitalize',
})}
</CustomButton>
</>
)}
</>
)}
{isOpenSendQortSuccess && (
<Box
sx={{
alignItems: 'center',
background: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
height: '100%',
position: 'fixed',
width: '100%',
zIndex: 10000,
}}
>
<Spacer height="48px" />
<img src={Success} />
<Spacer height="45px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '15px',
}}
>
{t('core:result.success.transfer', {
postProcess: 'capitalize',
})}
</TextP>
<Spacer height="100px" />
<CustomButton
onClick={() => {
returnToMain();
}}
>
{t('core:action.continue', { postProcess: 'capitalize' })}
</CustomButton>
</Box>
)}
{extState === 'transfer-success-request' && (
<>
<Spacer height="48px" />
<img src={Success} />
<Spacer height="45px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '15px',
}}
>
{t('core:result.success.transfer', {
postProcess: 'capitalize',
})}
</TextP>
<Spacer height="100px" />
<CustomButton
onClick={() => {
window.close();
}}
>
{t('core:action.continue', { postProcess: 'capitalize' })}
</CustomButton>
</>
)}
{extState === 'buy-order-submitted' && (
<>
<Spacer height="48px" />
<img src={Success} />
<Spacer height="45px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: '15px',
}}
>
{t('core:result.success.order_submitted', {
postProcess: 'capitalize',
})}
</TextP>
<Spacer height="100px" />
<CustomButton
onClick={() => {
window.close();
}}
>
{t('core:action.close', { postProcess: 'capitalize' })}
</CustomButton>
</> // TODO translate
)}
{countdown && (
<Box
style={{
position: 'absolute',
top: '20px',
left: '20px',
}}
>
{/* <Spacer height="25px"/> */}
<CountdownCircleTimer
isPlaying
duration={countdown}
colors={['#004777', '#F7B801', '#A30000', '#A30000']}
colorsTime={[7, 5, 2, 0]}
onComplete={() => {
window.close();
}}
size={75}
strokeWidth={8}
>
{({ remainingTime }) => <TextP>{remainingTime}</TextP>}
</CountdownCircleTimer>
</Box>
)}
{isLoading && <Loader />}
{isShow && (
<Dialog
open={isShow}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
sx={{
zIndex: 10001,
}}
>
<DialogTitle id="alert-dialog-title">
{message.paymentFee ? 'Payment' : 'Publish'}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{message.message}
</DialogContentText>
{message?.paymentFee && (
<DialogContentText id="alert-dialog-description2">
{t('core:fee.payment', {
postProcess: 'capitalize',
})}
: {message.paymentFee}
</DialogContentText>
)}
{message?.publishFee && (
<DialogContentText id="alert-dialog-description2">
{t('core:fee.publish', {
postProcess: 'capitalize',
})}
: {message.publishFee}
</DialogContentText>
)}
</DialogContent>
<DialogActions>
<Button
sx={{
backgroundColor: 'var(--green)',
color: theme.palette.text.primary,
fontWeight: 'bold',
opacity: 0.7,
'&:hover': {
backgroundColor: 'var(--green)',
color: 'black',
opacity: 1,
},
}}
variant="contained"
onClick={onOk}
autoFocus
>
{t('core:action.accept', {
postProcess: 'capitalize',
})}
</Button>
<Button
sx={{
backgroundColor: 'var(--danger)',
color: 'black',
fontWeight: 'bold',
opacity: 0.7,
'&:hover': {
backgroundColor: 'var(--danger)',
color: 'black',
opacity: 1,
},
}}
variant="contained"
onClick={onCancel}
>
{t('core:action.decline', {
postProcess: 'capitalize',
})}
</Button>
</DialogActions>
</Dialog>
)}
{isShowInfo && (
<Dialog
open={isShowInfo}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{'Important Info'}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{messageInfo.message}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={onOkInfo} autoFocus>
{t('core:action.close', {
postProcess: 'capitalize',
})}
</Button>
</DialogActions>
</Dialog>
)}
{isShowUnsavedChanges && (
<Dialog
open={isShowUnsavedChanges}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{'LOGOUT'}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{messageUnsavedChanges.message}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={onCancelUnsavedChanges}>
{t('core:action.cancel', {
postProcess: 'capitalize',
})}
</Button>
<Button
variant="contained"
onClick={onOkUnsavedChanges}
autoFocus
>
{t('core:action.decline', {
postProcess: 'capitalize',
})}
</Button>
</DialogActions>
</Dialog>
)}
{isShowQortalRequestExtension && isMainWindow && (
<Dialog
open={isShowQortalRequestExtension}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<CountdownCircleTimer
isPlaying
duration={60}
colors={['#004777', '#F7B801', '#A30000', '#A30000']}
colorsTime={[7, 5, 2, 0]}
onComplete={() => {
onCancelQortalRequestExtension();
}}
size={50}
strokeWidth={5}
>
{({ remainingTime }) => <TextP>{remainingTime}</TextP>}
</CountdownCircleTimer>
<Box
sx={{
display: 'flex',
padding: '20px',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-start',
maxHeight: '90vh',
overflow: 'auto',
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
width: '100%',
}}
>
<TextP
sx={{
lineHeight: 1.2,
maxWidth: '90%',
textAlign: 'center',
}}
>
{messageQortalRequestExtension?.text1}
</TextP>
</Box>
{messageQortalRequestExtension?.text2 && (
<>
<Spacer height="10px" />
<Box
sx={{
display: 'flex',
justifyContent: 'flex-start',
width: '90%',
}}
>
<TextP
sx={{
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 'normal',
}}
>
{messageQortalRequestExtension?.text2}
</TextP>
</Box>
<Spacer height="15px" />
</>
)}
{messageQortalRequestExtension?.text3 && (
<>
<Box
sx={{
display: 'flex',
justifyContent: 'flex-start',
width: '90%',
}}
>
<TextP
sx={{
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 'normal',
}}
>
{messageQortalRequestExtension?.text3}
</TextP>
<Spacer height="15px" />
</Box>
</>
)}
{messageQortalRequestExtension?.text4 && (
<Box
sx={{
display: 'flex',
justifyContent: 'flex-start',
width: '90%',
}}
>
<TextP
sx={{
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 'normal',
}}
>
{messageQortalRequestExtension?.text4}
</TextP>
</Box>
)}
{messageQortalRequestExtension?.html && (
<div
dangerouslySetInnerHTML={{
__html: messageQortalRequestExtension?.html,
}}
/>
)}
<Spacer height="15px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 700,
maxWidth: '90%',
}}
>
{messageQortalRequestExtension?.highlightedText}
</TextP>
{messageQortalRequestExtension?.json && (
<>
<Spacer height="15px" />
<JsonView
data={messageQortalRequestExtension?.json}
shouldExpandNode={allExpanded}
style={darkStyles}
/>
<Spacer height="15px" />
</>
)}
{messageQortalRequestExtension?.fee && (
<>
<Spacer height="15px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 'normal',
maxWidth: '90%',
}}
>
{'Fee: '}
{messageQortalRequestExtension?.fee}
{' QORT'}
</TextP>
<Spacer height="15px" />
</>
)}
{messageQortalRequestExtension?.appFee && (
<>
<TextP
sx={{
textAlign: 'center',
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 'normal',
maxWidth: '90%',
}}
>
{'App Fee: '}
{messageQortalRequestExtension?.appFee}
{' QORT'}
</TextP>
<Spacer height="15px" />
</>
)}
{messageQortalRequestExtension?.foreignFee && (
<>
<Spacer height="15px" />
<TextP
sx={{
textAlign: 'center',
lineHeight: 1.2,
fontSize: '16px',
fontWeight: 'normal',
maxWidth: '90%',
}}
>
{'Foreign Fee: '}
{messageQortalRequestExtension?.foreignFee}
</TextP>
<Spacer height="15px" />
</>
)}
{messageQortalRequestExtension?.checkbox1 && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '90%',
marginTop: '20px',
}}
>
<Checkbox
onChange={(e) => {
qortalRequestCheckbox1Ref.current = e.target.checked;
}}
edge="start"
tabIndex={-1}
disableRipple
defaultChecked={
messageQortalRequestExtension?.checkbox1?.value
}
sx={{
'&.Mui-checked': {
color: 'white', // Customize the color when checked
},
'& .MuiSvgIcon-root': {
color: 'white',
},
}}
/>
<Typography
sx={{
fontSize: '14px',
}}
>
{messageQortalRequestExtension?.checkbox1?.label}
</Typography>
</Box>
)}
{messageQortalRequestExtension?.confirmCheckbox && (
<FormControlLabel
control={
<Checkbox
onChange={(e) => setConfirmRequestRead(e.target.checked)}
checked={confirmRequestRead}
edge="start"
tabIndex={-1}
disableRipple
sx={{
'&.Mui-checked': {
color: 'white',
},
'& .MuiSvgIcon-root': {
color: 'white',
},
}}
/>
}
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography sx={{ fontSize: '14px' }}>
{t('core:result.success.request_read', {
postProcess: 'capitalize',
})}
</Typography>
<PriorityHighIcon color="warning" />
</Box>
}
/>
)}
<Spacer height="29px" />
<Box
sx={{
alignItems: 'center',
display: 'flex',
gap: '14px',
}}
>
<CustomButtonAccept
color="black"
bgColor="var(--green)"
sx={{
minWidth: '102px',
opacity:
messageQortalRequestExtension?.confirmCheckbox &&
!confirmRequestRead
? 0.1
: 0.7,
cursor:
messageQortalRequestExtension?.confirmCheckbox &&
!confirmRequestRead
? 'default'
: 'pointer',
'&:hover': {
opacity:
messageQortalRequestExtension?.confirmCheckbox &&
!confirmRequestRead
? 0.1
: 1,
},
}}
onClick={() => {
if (
messageQortalRequestExtension?.confirmCheckbox &&
!confirmRequestRead
)
return;
onOkQortalRequestExtension('accepted');
}}
>
{t('core:action.accept', {
postProcess: 'capitalize',
})}
</CustomButtonAccept>
<CustomButtonAccept
color="black"
bgColor="var(--danger)"
sx={{
minWidth: '102px',
}}
onClick={() => onCancelQortalRequestExtension()}
>
{t('core:action.decline', {
postProcess: 'capitalize',
})}
</CustomButtonAccept>
</Box>
<ErrorText>{sendPaymentError}</ErrorText>
</Box>
</Dialog>
)}
{isSettingsOpen && (
<Settings open={isSettingsOpen} setOpen={setIsSettingsOpen} />
)}
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}
info={infoSnack}
setInfo={setInfoSnack}
/>
<DrawerComponent
open={isOpenDrawerProfile}
setOpen={setIsOpenDrawerProfile}
>
{renderProfileLeft()}
</DrawerComponent>
<UserLookup
isOpenDrawerLookup={isOpenDrawerLookup}
setIsOpenDrawerLookup={setIsOpenDrawerLookup}
/>
<RegisterName
balance={balance}
show={show}
setTxList={setTxList}
userInfo={userInfo}
setOpenSnack={setOpenSnack}
setInfoSnack={setInfoSnack}
/>
<BuyQortInformation balance={balance} />
</GlobalContext.Provider>
{extState === 'create-wallet' && walletToBeDownloaded && (
<ButtonBase
onClick={() => {
showTutorial('important-information', true);
}}
sx={{
position: 'fixed',
bottom: '25px',
right: '25px',
}}
>
<HelpIcon
sx={{
color: 'var(--unread)',
}}
/>
</ButtonBase>
)}
{isOpenMinting && (
<Minting
setIsOpenMinting={setIsOpenMinting}
groups={memberGroups}
myAddress={address}
show={show}
setTxList={setTxList}
txList={txList}
/>
)}
<LanguageSelector />
<ThemeSelector />
</AppContainer>
);
}
export default App;