import { createContext, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import reactLogo from "./assets/react.svg"; import viteLogo from "/vite.svg"; import "./App.css"; import { useDropzone } from "react-dropzone"; import { Box, Button, ButtonBase, Checkbox, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Input, InputLabel, Popover, Tooltip, Typography, } from "@mui/material"; import { decryptStoredWallet } from "./utils/decryptWallet"; import { CountdownCircleTimer } from "react-countdown-circle-timer"; import Logo1 from "./assets/svgs/Logo1.svg"; import Logo1Dark from "./assets/svgs/Logo1Dark.svg"; import RefreshIcon from "@mui/icons-material/Refresh"; import Logo2 from "./assets/svgs/Logo2.svg"; import Copy from "./assets/svgs/Copy.svg"; import ltcLogo from "./assets/ltc.png"; import qortLogo from "./assets/qort.png"; import { CopyToClipboard } from "react-copy-to-clipboard"; import Download from "./assets/svgs/Download.svg"; import Logout from "./assets/svgs/Logout.svg"; import Return from "./assets/svgs/Return.svg"; import Success from "./assets/svgs/Success.svg"; import Info from "./assets/svgs/Info.svg"; import CloseIcon from "@mui/icons-material/Close"; import { JsonView, allExpanded, darkStyles } from 'react-json-view-lite'; import 'react-json-view-lite/dist/index.css'; import HelpIcon from '@mui/icons-material/Help'; import EngineeringIcon from '@mui/icons-material/Engineering'; import { createAccount, generateRandomSentence, 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, CustomInput, CustomLabel, TextItalic, TextP, TextSpan, } from "./App-styles"; import { Spacer } from "./common/Spacer"; import { Loader } from "./components/Loader"; import { PasswordField, ErrorText } from "./components"; import { ChatGroup } from "./components/Chat/ChatGroup"; import { Group, requestQueueMemberNames } from "./components/Group/Group"; import { TaskManger } from "./components/TaskManager/TaskManger"; import { useModal } from "./common/useModal"; import { LoadingButton } from "@mui/lab"; import { Label } from "./components/Group/AddGroup"; import { CustomizedSnackbars } from "./components/Snackbar/Snackbar"; import SettingsIcon from "@mui/icons-material/Settings"; import { cleanUrl, getFee, getProtocol, groupApi, groupApiLocal, groupApiSocket, groupApiSocketLocal, } 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, fullScreenAtom, hasSettingsChangedAtom, isDisabledEditorEnterAtom, isUsingImportExportSettingsAtom, oldPinnedAppsAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, sortablePinnedAppsAtom } from "./atoms/global"; import { useAppFullScreen } from "./useAppFullscreen"; import { NotAuthenticated } from "./ExtStates/NotAuthenticated"; import { useFetchResources } from "./common/useFetchResources"; import { Tutorials } from "./components/Tutorials/Tutorials"; import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials"; import { CoreSyncStatus } from "./components/CoreSyncStatus"; import BoundedNumericTextField from "./common/BoundedNumericTextField"; import { Wallets } from "./Wallets"; import './utils/seedPhrase/RandomSentenceGenerator'; import { test } from "vitest"; import { useHandleUserInfo } from "./components/Group/useHandleUserInfo"; import { Minting } from "./components/Minting/Minting"; import { isRunningGateway } from "./qortalRequests"; 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" | "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 let isMobile = false; const isMobileDevice = () => { const userAgent = navigator.userAgent || navigator.vendor || window.opera; if (/android/i.test(userAgent)) { return true; // Android device } if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { return true; // iOS device } return false; }; if (isMobileDevice()) { isMobile = true; console.log("Running on a mobile device"); } else { console.log("Running on a desktop"); } 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"); chrome?.runtime?.sendMessage({ action: "pauseAllQueues", payload: {}, }); }; export const resumeAllQueues = () => { controlAllQueues("resume"); chrome?.runtime?.sendMessage({ action: "resumeAllQueues", payload: {}, }); }; const defaultValuesGlobal = { openTutorialModal: null, setOpenTutorialModal: ()=> {} } export const MyContext = createContext(defaultValues); export const GlobalContext = createContext(defaultValuesGlobal); export let globalApiKey: string | null = null; export const getBaseApiReact = (customApi?: string) => { if (customApi) { return customApi; } if (globalApiKey) { return globalApiKey?.url; } else { return groupApi; } }; // export const getArbitraryEndpointReact = () => { // if (globalApiKey) { // return `/arbitrary/resources/search`; // } else { // return `/arbitrary/resources/searchsimple`; // } // }; 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) { return `${getProtocol(globalApiKey?.url) === 'http' ? 'ws://': 'wss://'}${cleanUrl(globalApiKey?.url)}` } else { return groupApiSocket; } }; export const isMainWindow = window?.location?.href?.includes("?main=true"); function App() { const [extState, setExtstate] = useState("not-authenticated"); const [desktopViewMode, setDesktopViewMode] = useState('home') const [backupjson, setBackupjson] = useState(null); const [rawWallet, setRawWallet] = useState(null); const [ltcBalanceLoading, setLtcBalanceLoading] = useState(false); const [qortBalanceLoading, setQortBalanceLoading] = useState(false); const [decryptedWallet, setdecryptedWallet] = useState(null); const [requestConnection, setRequestConnection] = useState(null); const [requestBuyOrder, setRequestBuyOrder] = useState(null); const [authenticatedMode, setAuthenticatedMode] = useState("qort"); const [requestAuthentication, setRequestAuthentication] = useState(null); const [userInfo, setUserInfo] = useState(null); const [balance, setBalance] = useState(null); const [ltcBalance, setLtcBalance] = useState(null); const [paymentTo, setPaymentTo] = useState(""); const [paymentAmount, setPaymentAmount] = useState(0); const [paymentPassword, setPaymentPassword] = useState(""); const [sendPaymentError, setSendPaymentError] = useState(""); const [sendPaymentSuccess, setSendPaymentSuccess] = useState(""); const [countdown, setCountdown] = useState(null); const [walletToBeDownloaded, setWalletToBeDownloaded] = useState(null); const [walletToBeDownloadedPassword, setWalletToBeDownloadedPassword] = useState(""); const [isMain, setIsMain] = useState( window?.location?.href?.includes("?main=true") ); const isMainRef = useRef(false); const [authenticatePassword, setAuthenticatePassword] = useState(""); const [sendqortState, setSendqortState] = useState(null); const [isLoading, setIsLoading] = useState(false); const [ walletToBeDownloadedPasswordConfirm, setWalletToBeDownloadedPasswordConfirm, ] = useState(""); const [walletToBeDownloadedError, setWalletToBeDownloadedError] = useState(""); const [walletToBeDecryptedError, setWalletToBeDecryptedError] = useState(""); const [txList, setTxList] = useState([]); const [memberGroups, setMemberGroups] = useState([]); const [isFocused, setIsFocused] = useState(true); const [hasSettingsChanged, setHasSettingsChanged] = useRecoilState(hasSettingsChangedAtom) const holdRefExtState = useRef("not-authenticated"); const isFocusedRef = useRef(true); const { isShow, onCancel, onOk, show, message } = useModal(); const { isShow: isShowUnsavedChanges, onCancel: onCancelUnsavedChanges, onOk: onOkUnsavedChanges, show: showUnsavedChanges, message: messageUnsavedChanges } = useModal(); const {downloadResource} = useFetchResources() const [showSeed, setShowSeed] = useState(false) const [creationStep, setCreationStep] = useState(1) const [isOpenMinting, setIsOpenMinting] = useState(false) const setIsDisabledEditorEnter = useSetRecoilState(isDisabledEditorEnterAtom) 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 [openRegisterName, setOpenRegisterName] = useState(false); const registerNamePopoverRef = useRef(null); const [isLoadingRegisterName, setIsLoadingRegisterName] = useState(false); const [registerNameValue, setRegisterNameValue] = useState(""); const [infoSnack, setInfoSnack] = useState(null); const [openSnack, setOpenSnack] = useState(false); const [hasLocalNode, setHasLocalNode] = useState(false); const [isOpenDrawerProfile, setIsOpenDrawerProfile] = useState(false); const [apiKey, setApiKey] = useState(""); const [isOpenSendQort, setIsOpenSendQort] = useState(false); const [isOpenSendQortSuccess, setIsOpenSendQortSuccess] = useState(false); const [rootHeight, setRootHeight] = useState("100%"); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const qortalRequestCheckbox1Ref = useRef(null); useRetrieveDataLocalStorage() useQortalGetSaveSettings(userInfo?.name, extState === "authenticated") const [fullScreen, setFullScreen] = useRecoilState(fullScreenAtom); const resetAtomIsUsingImportExportSettingsAtom = useResetRecoilState(isUsingImportExportSettingsAtom) const { toggleFullScreen } = useAppFullScreen(setFullScreen); const generatorRef = useRef(null) const exportSeedphrase = ()=> { const seedPhrase = generatorRef.current.parsedString saveSeedPhraseToDisk(seedPhrase) } const {showTutorial, openTutorialModal, shownTutorialsInitiated, setOpenTutorialModal} = useHandleTutorials() const {getIndividualUserInfo} = useHandleUserInfo() const passwordRef = useRef(null); useEffect(() => { if (extState === "wallet-dropped" && passwordRef.current) { passwordRef.current.focus(); } }, [extState]); 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 resetAtomCanSaveSettingToQdnAtom = useResetRecoilState(canSaveSettingToQdnAtom); const resetAtomSettingsQDNLastUpdatedAtom = useResetRecoilState(settingsQDNLastUpdatedAtom); const resetAtomSettingsLocalLastUpdatedAtom = useResetRecoilState(settingsLocalLastUpdatedAtom); const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom); const resetAllRecoil = () => { resetAtomSortablePinnedAppsAtom(); resetAtomCanSaveSettingToQdnAtom(); resetAtomSettingsQDNLastUpdatedAtom(); resetAtomSettingsLocalLastUpdatedAtom(); resetAtomOldPinnedAppsAtom(); resetAtomIsUsingImportExportSettingsAtom() }; useEffect(() => { if (!isMobile) return; // Function to set the height of the app to the viewport height const resetHeight = () => { const height = window.visualViewport ? window.visualViewport.height : window.innerHeight; // Set the height to the root element (usually #root) document.getElementById("root").style.height = height + "px"; setRootHeight(height + "px"); }; // Set the initial height resetHeight(); // Add event listeners for resize and visualViewport changes window.addEventListener("resize", resetHeight); window.visualViewport?.addEventListener("resize", resetHeight); // Clean up the event listeners when the component unmounts return () => { window.removeEventListener("resize", resetHeight); window.visualViewport?.removeEventListener("resize", resetHeight); }; }, []); const handleSetGlobalApikey = (key)=> { globalApiKey = key; } useEffect(() => { chrome?.runtime?.sendMessage({ action: "getApiKey" }, (response) => { if (response) { handleSetGlobalApikey(response) setApiKey(response); } }); }, []); useEffect(() => { if (extState) { holdRefExtState.current = extState; } }, [extState]); useEffect(() => { isFocusedRef.current = isFocused; }, [isFocused]); 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]) // const checkIfUserHasLocalNode = useCallback(async () => { // try { // const url = `http://127.0.0.1:12391/admin/status`; // const response = await fetch(url, { // method: "GET", // headers: { // "Content-Type": "application/json", // }, // }); // const data = await response.json(); // if (data?.isSynchronizing === false && data?.syncPercent === 100) { // setHasLocalNode(true); // } // } catch (error) {} // }, []); // useEffect(() => { // checkIfUserHasLocalNode(); // }, [extState]); const address = useMemo(() => { 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) {} 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"); } // storeWalletInfo(pf); setRawWallet(pf); // setExtstate("authenticated"); setExtstate("wallet-dropped"); setdecryptedWallet(null); } catch (e) { console.log(e); error = 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 storeWalletInfo = (wallet: any) => { chrome?.runtime?.sendMessage( { action: "storeWalletInfo", wallet }, (response) => { if (response) { } } ); chrome.tabs.query( { active: true, currentWindow: true }, function (tabs: any[]) { chrome.tabs.sendMessage( tabs[0].id, { from: "popup", subject: "anySubject" }, function (response) {} ); } ); }; const getBalanceFunc = () => { setQortBalanceLoading(true); chrome?.runtime?.sendMessage({ action: "balance" }, (response) => { if (!response?.error && !isNaN(+response)) { setBalance(response); } setQortBalanceLoading(false); }); }; const getLtcBalanceFunc = () => { setLtcBalanceLoading(true); chrome?.runtime?.sendMessage({ action: "ltcBalance" }, (response) => { if (!response?.error && !isNaN(+response)) { setLtcBalance(response); } setLtcBalanceLoading(false); }); }; const sendCoinFunc = async () => { try { setSendPaymentError(""); setSendPaymentSuccess(""); if (!paymentTo) { setSendPaymentError("Please enter a recipient"); return; } if (!paymentAmount) { setSendPaymentError("Please enter an amount greater than 0"); return; } if (!paymentPassword) { setSendPaymentError("Please enter your wallet password"); return; } const fee = await getFee('PAYMENT') await show({ message: `Would you like to transfer ${Number(paymentAmount)} QORT?` , paymentFee: fee.fee + ' QORT' }) setIsLoading(true); chrome?.runtime?.sendMessage( { action: "sendCoin", payload: { amount: Number(paymentAmount), receiver: paymentTo.trim(), password: paymentPassword, }, }, (response) => { if (response?.error) { setSendPaymentError(response.error); } else { setIsOpenSendQort(false); setIsOpenSendQortSuccess(true); // setExtstate("transfer-success-regular"); // setSendPaymentSuccess("Payment successfully sent"); } setIsLoading(false); } ); } catch (error) { //error } }; const clearAllStates = () => { setRequestConnection(null); setRequestAuthentication(null); }; const qortalRequestPermisson = async (message, sender, sendResponse) => { if (message.action === "QORTAL_REQUEST_PERMISSION" && !isMainWindow) { try { await showQortalRequest(message?.payload); if (qortalRequestCheckbox1Ref.current) { sendResponse({ accepted: true, checkbox1: qortalRequestCheckbox1Ref.current, }); return; } sendResponse({ accepted: true }); } catch (error) { sendResponse({ accepted: false }); } finally { window.close(); } } }; const qortalRequestPermissonFromExtension = async (message, sender, sendResponse) => { if (message.action === "QORTAL_REQUEST_PERMISSION" && isMainWindow) { try { if(message?.payload?.checkbox1){ qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1?.value || false } await showQortalRequestExtension(message?.payload); if (qortalRequestCheckbox1Ref.current) { sendResponse({ accepted: true, checkbox1: qortalRequestCheckbox1Ref.current, }); return; } sendResponse({ accepted: true }); } catch (error) { sendResponse({ accepted: false }); } } }; useEffect(() => { // Listen for messages from the background script const messageListener = (message, sender, sendResponse) => { // Handle various actions if ( message.action === "UPDATE_STATE_CONFIRM_SEND_QORT" && !isMainWindow ) { setSendqortState(message.payload); setExtstate("web-app-request-payment"); } else if (message.action === "closePopup" && !isMainWindow) { window.close(); } else if ( message.action === "UPDATE_STATE_REQUEST_CONNECTION" && !isMainWindow ) { setRequestConnection(message.payload); setExtstate("web-app-request-connection"); } else if ( message.action === "UPDATE_STATE_REQUEST_BUY_ORDER" && !isMainWindow ) { setRequestBuyOrder(message.payload); setExtstate("web-app-request-buy-order"); } else if ( message.action === "UPDATE_STATE_REQUEST_AUTHENTICATION" && !isMainWindow ) { setRequestAuthentication(message.payload); setExtstate("web-app-request-authentication"); } else if (message.action === "SET_COUNTDOWN" && !isMainWindow) { setCountdown(message.payload); } else if (message.action === "INITIATE_MAIN") { setIsMain(true); isMainRef.current = true; } else if (message.action === "CHECK_FOCUS" && isMainWindow) { sendResponse(isFocusedRef.current); // Synchronous response return true; // Return true if you plan to send a response asynchronously } else if ( message.action === "NOTIFICATION_OPEN_DIRECT" && isMainWindow ) { executeEvent("openDirectMessage", { from: message.payload.from, }); } else if (message.action === "NOTIFICATION_OPEN_GROUP" && isMainWindow) { executeEvent("openGroupMessage", { from: message.payload.from, }); } else if ( message.action === "NOTIFICATION_OPEN_ANNOUNCEMENT_GROUP" && isMainWindow ) { executeEvent("openGroupAnnouncement", { from: message.payload.from, }); } else if ( message.action === "NOTIFICATION_OPEN_THREAD_NEW_POST" && isMainWindow ) { executeEvent("openThreadNewPost", { data: message.payload.data, }); } // Call the permission request handler for "QORTAL_REQUEST_PERMISSION" qortalRequestPermisson(message, sender, sendResponse); if (message.action === "QORTAL_REQUEST_PERMISSION" && !isMainWindow) { return true; // Return true to indicate an async response is coming } if (message.action === "QORTAL_REQUEST_PERMISSION" && isMainWindow && message?.isFromExtension) { qortalRequestPermissonFromExtension(message, sender, sendResponse); return true; } if (message.action === "QORTAL_REQUEST_PERMISSION" && isMainWindow) { return; } }; // Add message listener chrome.runtime?.onMessage.addListener(messageListener); // Clean up the listener on component unmount return () => { chrome.runtime?.onMessage.removeListener(messageListener); }; }, []); //param = isDecline const confirmPayment = (isDecline: boolean) => { if (isDecline) { chrome?.runtime?.sendMessage( { action: "sendQortConfirmation", payload: { amount: sendqortState?.amount, receiver: sendqortState?.address, password: paymentPassword, interactionId: sendqortState?.interactionId, isDecline: true, }, }, (response) => { if (response) { setSendPaymentSuccess("Payment successfully sent"); } else { window.close(); } } ); return; } setIsLoading(true); chrome?.runtime?.sendMessage( { action: "sendQortConfirmation", payload: { amount: sendqortState.amount, receiver: sendqortState.address, password: paymentPassword, interactionId: sendqortState.interactionId, isDecline: false, }, }, (response) => { if (response === true) { setExtstate("transfer-success-request"); setCountdown(null); } else { setSendPaymentError( response?.error || "Unable to perform payment. Please try again." ); } setIsLoading(false); } ); }; const confirmBuyOrder = (isDecline: boolean) => { if (isDecline) { chrome?.runtime?.sendMessage( { action: "buyOrderConfirmation", payload: { crosschainAtInfo: requestBuyOrder?.crosschainAtInfo, interactionId: requestBuyOrder?.interactionId, isDecline: true, useLocal: requestBuyOrder?.useLocal, }, }, (response) => { window.close(); } ); return; } setIsLoading(true); chrome?.runtime?.sendMessage( { action: "buyOrderConfirmation", payload: { crosschainAtInfo: requestBuyOrder?.crosschainAtInfo, interactionId: requestBuyOrder?.interactionId, isDecline: false, useLocal: requestBuyOrder?.useLocal, }, }, (response) => { if (response === true) { setExtstate("buy-order-submitted"); setCountdown(null); } else { setSendPaymentError( response?.error || "Unable to perform payment. Please try again." ); } setIsLoading(false); } ); }; const responseToConnectionRequest = ( isOkay: boolean, hostname: string, interactionId: string ) => { chrome?.runtime?.sendMessage( { action: "responseToConnectionRequest", payload: { isOkay, hostname, interactionId }, }, (response) => { if (response === false || response === true) { window.close(); } } ); }; // const rawWalletRef = useRef(null) // useEffect(()=> { // rawWalletRef.current = rawWallet // }, [rawWallet]) useEffect(() => { try { setIsLoading(true); chrome?.runtime?.sendMessage({ action: "getWalletInfo" }, (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) { } finally { setIsLoading(false); } }, []); const getUserInfo = useCallback(async (useTimer?: boolean) => { try { if (useTimer) { await new Promise((res) => { setTimeout(() => { res(null); }, 10000); }); } chrome?.runtime?.sendMessage({ action: "userInfo" }, (response) => { if (response && !response.error) { setUserInfo(response); } }); getBalanceFunc(); } catch (error) {} }, []); useEffect(() => { if (!address) return; getUserInfo(); }, [address]); 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) { } }, []) 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((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); } finally { } }; 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((res) => { setTimeout(() => { res(); }, 250); }); const res = await createAccount(generatorRef.current.parsedString); const wallet = await res.generateSaveWalletData( walletToBeDownloadedPassword, crypto.kdfThreads, () => {} ); chrome?.runtime?.sendMessage( { action: "decryptWallet", payload: { password: walletToBeDownloadedPassword, wallet, }, }, (response) => { if (response && !response?.error) { setRawWallet(wallet); setWalletToBeDownloaded({ wallet, qortAddress: wallet.address0, }); chrome?.runtime?.sendMessage( { action: "userInfo" }, (response2) => { setIsLoading(false); if (response2 && !response2.error) { setUserInfo(response); } } ); getBalanceFunc(); } else if (response?.error) { setIsLoading(false); setWalletToBeDecryptedError(response.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.'}) } else if(extState === 'authenticated') { await showUnsavedChanges({ message: "Are you sure you would like to logout?", }); } chrome?.runtime?.sendMessage({ action: "logout" }, (response) => { if (response) { executeEvent("logout-event", {}); resetAllStates(); } }); } catch (error) {} }; const returnToMain = () => { setPaymentTo(""); setPaymentAmount(0); setPaymentPassword(""); setSendPaymentError(""); setSendPaymentSuccess(""); setCountdown(null); setWalletToBeDownloaded(null); setWalletToBeDownloadedPassword(""); setExtstate("authenticated"); setIsOpenSendQort(false); setIsOpenSendQortSuccess(false); setShowSeed(false) setCreationStep(1) }; 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(""); setWalletToBeDownloadedPasswordConfirm(""); setWalletToBeDownloadedError(""); setSendqortState(null); setHasLocalNode(false); setTxList([]); setMemberGroups([]); resetAllRecoil() setShowSeed(false) setCreationStep(1) }; 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((res) => { setTimeout(() => { res(); }, 250); }); chrome?.runtime?.sendMessage( { action: "decryptWallet", payload: { password: authenticatePassword, wallet: rawWallet, }, }, (response) => { if (response && !response?.error) { setAuthenticatePassword(""); setExtstate("authenticated"); setWalletToBeDecryptedError(""); chrome?.runtime?.sendMessage({ action: "userInfo" }, (response) => { setIsLoading(false); if (response && !response.error) { setUserInfo(response); } }); getBalanceFunc(); chrome?.runtime?.sendMessage( { action: "getWalletInfo" }, (response) => { if (response && response?.walletInfo) { setRawWallet(response?.walletInfo); } } ); } else if (response?.error) { setIsLoading(false); setWalletToBeDecryptedError(response.error); } } ); } catch (error) { setWalletToBeDecryptedError("Unable to authenticate. Wrong password"); } }; // const handleBeforeUnload = (e)=> { // const shouldClose = confirm('Are you sure you want to close this window? You may have unsaved changes.'); // if (!shouldClose) { // // Prevent the window from closing // e.preventDefault(); // e.returnValue = ''; // Required for Chrome // } else { // // Allow the window to close // // No need to call preventDefault here; returnValue must be left empty // } // } // useEffect(()=> { // window.addEventListener('beforeunload', handleBeforeUnload); // return ()=> { // window.removeEventListener('beforeunload', handleBeforeUnload); // } // }, []) useEffect(() => { if (!isMainWindow || isMobile) return; const handleBeforeUnload = (e) => { e.preventDefault(); e.returnValue = ""; // This is required for Chrome to display the confirmation dialog. return ""; }; // Add the event listener when the component mounts window.addEventListener("beforeunload", handleBeforeUnload); // Clean up the event listener when the component unmounts return () => { window.removeEventListener("beforeunload", handleBeforeUnload); }; }, []); useEffect(() => { if (!isMainWindow) return; // Handler for when the window gains focus const handleFocus = () => { setIsFocused(true); if (isMobile) { chrome?.runtime?.sendMessage({ action: "clearAllNotifications", payload: {}, }); } }; // 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); if (isMobile) { chrome?.runtime?.sendMessage({ action: "clearAllNotifications", payload: {}, }); } } 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 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 registerName = async () => { try { if (!userInfo?.address) throw new Error("Your address was not found"); const fee = await getFee("REGISTER_NAME"); await show({ message: "Would you like to register this name?", publishFee: fee.fee + " QORT", }); setIsLoadingRegisterName(true); new Promise((res, rej) => { chrome?.runtime?.sendMessage( { action: "registerName", payload: { name: registerNameValue, }, }, (response) => { if (!response?.error) { res(response); setIsLoadingRegisterName(false); setInfoSnack({ type: "success", message: "Successfully registered. It may take a couple of minutes for the changes to propagate", }); setOpenRegisterName(false); setRegisterNameValue(""); setOpenSnack(true); setTxList((prev) => [ { ...response, type: "register-name", label: `Registered name: awaiting confirmation. This may take a couple minutes.`, labelDone: `Registered name: success!`, done: false, }, ...prev.filter((item) => !item.done), ]); return; } setInfoSnack({ type: "error", message: response?.error, }); setOpenSnack(true); rej(response.error); } ); }); } catch (error) { if (error?.message) { setInfoSnack({ type: "error", message: error?.message, }); } } finally { setIsLoadingRegisterName(false); } }; const renderProfile = () => { return ( {isMobile && ( { setIsOpenDrawerProfile(false); }} sx={{ cursor: "pointer", color: "white", }} /> )} {desktopViewMode !== "apps" && desktopViewMode !== "dev" && desktopViewMode !== "chat" && ( {authenticatedMode === "ltc" ? ( <> {rawWallet?.ltcAddress?.slice(0, 6)}... {rawWallet?.ltcAddress?.slice(-4)} {ltcBalanceLoading && ( )} {!isNaN(+ltcBalance) && !ltcBalanceLoading && ( {ltcBalance} LTC )} ) : ( <> {userInfo?.name} {rawWallet?.address0?.slice(0, 6)}... {rawWallet?.address0?.slice(-4)} {qortBalanceLoading && ( )} {!qortBalanceLoading && balance >= 0 && ( {balance?.toFixed(2)} QORT )} {userInfo && !userInfo?.name && ( { setOpenRegisterName(true); }} > REGISTER NAME )} { setIsOpenSendQort(true); // setExtstate("send-qort"); setIsOpenDrawerProfile(false); }} > Transfer QORT )} { executeEvent("addTab", { data: { service: 'APP', name: 'q-trade' } }); executeEvent("open-apps-mode", { }); }} > Get QORT at Q-Trade )} {!isMobile && ( <> { logoutFunc(); setIsOpenDrawerProfile(false); }} style={{ cursor: "pointer", }} /> )} { setIsSettingsOpen(true); }} > {authenticatedMode === "qort" && ( { setAuthenticatedMode("ltc"); }} src={ltcLogo} style={{ cursor: "pointer", width: "20px", height: "auto", }} /> )} {authenticatedMode === "ltc" && ( { setAuthenticatedMode("qort"); }} src={qortLogo} style={{ cursor: "pointer", width: "20px", height: "auto", }} /> )} {extState === "authenticated" && isMainWindow && ( )} { 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 }) } }}> {(desktopViewMode === "apps" || desktopViewMode === "home") && ( { if(desktopViewMode === "apps"){ showTutorial('qapps', true) } else { showTutorial('getting-started', true) } }} > )} { setExtstate("download-wallet"); setIsOpenDrawerProfile(false); }} src={Download} style={{ cursor: "pointer", }} /> ); }; return ( {extState === "not-authenticated" && ( )} {/* {extState !== "not-authenticated" && ( )} */} {extState === "authenticated" && isMainWindow && ( {!isMobile && renderProfile()} )} {isOpenSendQort && isMainWindow && ( Transfer QORT Balance: {balance?.toFixed(2)} QORT To setPaymentTo(e.target.value)} autoComplete="off" /> Amount setPaymentAmount(+e)} /> Confirm Wallet Password setPaymentPassword(e.target.value)} autoComplete="off" /> {sendPaymentError} {/* {sendPaymentSuccess} */} { sendCoinFunc(); }} > Send )} {isShowQortalRequest && !isMainWindow && ( <> {messageQortalRequest?.text1} {messageQortalRequest?.text2 && ( <> {messageQortalRequest?.text2} )} {messageQortalRequest?.text3 && ( <> {messageQortalRequest?.text3} )} {messageQortalRequest?.text4 && ( {messageQortalRequest?.text4} )} {messageQortalRequest?.html && (
)} {messageQortalRequest?.highlightedText} {messageQortalRequest?.fee && ( <> {'Fee: '}{messageQortalRequest?.fee}{' QORT'} )} {messageQortalRequest?.checkbox1 && ( { 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", }, }} /> {messageQortalRequest?.checkbox1?.label} )} onOkQortalRequest("accepted")} > accept onCancelQortalRequest()} > decline {sendPaymentError} )} {extState === "web-app-request-buy-order" && !isMainWindow && ( <> The Application

{" "} {requestBuyOrder?.hostname}

is requesting {requestBuyOrder?.crosschainAtInfo?.length}{" "} {`buy order${ requestBuyOrder?.crosschainAtInfo.length === 1 ? "" : "s" }`}
{requestBuyOrder?.crosschainAtInfo?.reduce((latest, cur) => { return latest + +cur?.qortAmount; }, 0)}{" "} QORT FOR {roundUpToDecimals( requestBuyOrder?.crosschainAtInfo?.reduce((latest, cur) => { return latest + +cur?.expectedForeignAmount; }, 0) )} {` ${requestBuyOrder?.crosschainAtInfo?.[0]?.foreignBlockchain}`} {/* Confirm Wallet Password setPaymentPassword(e.target.value)} /> */} confirmBuyOrder(false)} > accept confirmBuyOrder(true)} > decline {sendPaymentError} )} {extState === "web-app-request-payment" && !isMainWindow && ( <> The Application

{" "} {sendqortState?.hostname}

is requesting a payment
{sendqortState?.description} {sendqortState?.amount} QORT {/* Confirm Wallet Password setPaymentPassword(e.target.value)} /> */} confirmPayment(false)} > accept confirmPayment(true)} > decline {sendPaymentError} )} {extState === "web-app-request-connection" && !isMainWindow && ( <>
The Application

{" "} {requestConnection?.hostname}

is requestion a connection
responseToConnectionRequest( true, requestConnection?.hostname, requestConnection.interactionId ) } > accept responseToConnectionRequest( false, requestConnection?.hostname, requestConnection.interactionId ) } > decline )} {extState === "web-app-request-authentication" && !isMainWindow && ( <>
The Application

{" "} {requestConnection?.hostname}

requests authentication
Authenticate { setExtstate("create-wallet"); }} > Create account )} {extState === "wallets" && ( <> { setRawWallet(null); setExtstate("not-authenticated"); logoutFunc(); }} src={Return} /> )} {rawWallet && extState === "wallet-dropped" && ( <> { setRawWallet(null); setExtstate("wallets"); logoutFunc(); }} src={Return} />
{rawWallet?.name ? rawWallet?.name : rawWallet?.address0} Authenticate <> Wallet Password setAuthenticatePassword(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { authenticateWallet(); } }} ref={passwordRef} /> Authenticate {walletToBeDecryptedError} )} {extState === "download-wallet" && ( <>
Download Wallet {!walletToBeDownloaded && ( <> Confirm Wallet Password setWalletToBeDownloadedPassword(e.target.value) } /> Confirm password {walletToBeDownloadedError} )} {walletToBeDownloaded && ( <> Download wallet )} )} {extState === "create-wallet" && ( <> {!walletToBeDownloaded && ( <> { if(creationStep === 2){ setCreationStep(1) return } setExtstate("not-authenticated"); setShowSeed(false) setCreationStep(1) setWalletToBeDownloadedPasswordConfirm('') setWalletToBeDownloadedPassword('') }} src={Return} />
Set up your Qortal account A ‘ { setShowSeed(true) }} style={{ fontSize: '14px', color: 'steelblue', cursor: 'pointer' }}>SEEDPHRASE ’ has been randomly generated in the background. 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. Create your Qortal account by clicking NEXT below. { setCreationStep(2) }}> Next
Your seedphrase {generatorRef.current?.parsedString} Export Seedphrase
Wallet Password setWalletToBeDownloadedPassword(e.target.value) } /> Confirm Wallet Password setWalletToBeDownloadedPasswordConfirm(e.target.value) } /> Create Account {walletToBeDownloadedError} )} {walletToBeDownloaded && ( <> Congrats, you’re all set up! { await saveFileToDiskFunc(); returnToMain(); await showInfo({ message: `Keep your wallet file secure.`, }); }} > Backup Account )} )} {isOpenSendQortSuccess && ( The transfer was succesful! { returnToMain(); }} > Continue )} {extState === "transfer-success-request" && ( <> The transfer was succesful! { window.close(); }} > Continue )} {extState === "buy-order-submitted" && ( <> Your buy order was submitted { window.close(); }} > Close )} {countdown && ( {/* */} { window.close(); }} size={75} strokeWidth={8} > {({ remainingTime }) => {remainingTime}} )} {isLoading && } {isShow && ( {message.paymentFee ? "Payment" : "Publish"} {message.message} {message?.paymentFee && ( payment fee: {message.paymentFee} )} {message?.publishFee && ( publish fee: {message.publishFee} )} )} {isShowInfo && ( {"Important Info"} {messageInfo.message} )} {isShowUnsavedChanges && ( {"Warning"} {messageUnsavedChanges.message} )} {isShowQortalRequestExtension && isMainWindow && ( { onCancelQortalRequestExtension() }} size={50} strokeWidth={5} > {({ remainingTime }) => {remainingTime}} {messageQortalRequestExtension?.text1} {messageQortalRequestExtension?.text2 && ( <> {messageQortalRequestExtension?.text2} )} {messageQortalRequestExtension?.text3 && ( <> {messageQortalRequestExtension?.text3} )} {messageQortalRequestExtension?.text4 && ( {messageQortalRequestExtension?.text4} )} {messageQortalRequestExtension?.html && (
)} {messageQortalRequestExtension?.highlightedText} {messageQortalRequestExtension?.json && ( <> )} {messageQortalRequestExtension?.fee && ( <> {'Fee: '}{messageQortalRequestExtension?.fee}{' QORT'} )} {messageQortalRequestExtension?.foreignFee && ( <> {"Foreign Fee: "} {messageQortalRequestExtension?.foreignFee} )} {messageQortalRequestExtension?.checkbox1 && ( { 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", }, }} /> {messageQortalRequestExtension?.checkbox1?.label} )} onOkQortalRequestExtension("accepted")} > accept onCancelQortalRequestExtension()} > decline {sendPaymentError}
)} { setOpenRegisterName(false); setRegisterNameValue(""); }} anchorOrigin={{ vertical: "bottom", horizontal: "center", }} transformOrigin={{ vertical: "top", horizontal: "center", }} style={{ marginTop: "8px" }} > setRegisterNameValue(e.target.value)} value={registerNameValue} placeholder="Choose a name" /> Register Name {isSettingsOpen && ( )} {renderProfile()} {extState === "create-wallet" && walletToBeDownloaded && ( { showTutorial('important-information', true) }} sx={{ position: 'fixed', bottom: '25px', right: '25px' }}> )} {isOpenMinting && ( )} ); } export default App;