From 39ca19b0791bb90b1e4e5dcae6bec52801745fe2 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 8 May 2025 15:46:41 +0300 Subject: [PATCH] updates --- connect-qapp.sh | 20 +++ src/AppWrapper.tsx | 13 +- src/components/RegisterName.tsx | 36 ++++- src/components/Tables/ForSaleTable.tsx | 79 +++++++++-- src/components/Tables/NameTable.tsx | 160 ++++++++++++++++++---- src/components/Tables/PendingTxsTable.tsx | 87 ++++++++++++ src/hooks/useHandleNameData.tsx | 11 +- src/hooks/useHandlePendingTxs.tsx | 24 ++++ src/hooks/useIframeListener.tsx | 50 +++---- src/pages/Market.tsx | 3 - src/pages/MyNames.tsx | 1 - src/state/contexts/PendingTxsProvider.tsx | 108 +++++++++++++++ src/state/global/names.ts | 35 +++++ src/styles/Layout.tsx | 2 + stop-qapp.sh | 20 +++ 15 files changed, 572 insertions(+), 77 deletions(-) create mode 100755 connect-qapp.sh create mode 100644 src/components/Tables/PendingTxsTable.tsx create mode 100644 src/hooks/useHandlePendingTxs.tsx create mode 100644 src/state/contexts/PendingTxsProvider.tsx create mode 100755 stop-qapp.sh diff --git a/connect-qapp.sh b/connect-qapp.sh new file mode 100755 index 0000000..e4f50ff --- /dev/null +++ b/connect-qapp.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# CONFIGURATION +REMOTE_USER=phil +REMOTE_HOST=devnet-nodes.qortal.link +QAPP_PORT=22393 +CORE_API_PORT=22391 +EDITOR_PORT=5174 + +# Start reverse tunnel in background +echo "Starting SSH tunnel to $REMOTE_HOST..." + +ssh -p22221 -o ServerAliveInterval=30 \ + -L $QAPP_PORT:127.0.0.1:$QAPP_PORT \ + -L $CORE_API_PORT:127.0.0.1:$CORE_API_PORT \ + -R $EDITOR_PORT:127.0.0.1:$EDITOR_PORT \ + $REMOTE_USER@$REMOTE_HOST + + + diff --git a/src/AppWrapper.tsx b/src/AppWrapper.tsx index ec551cd..d9d1649 100644 --- a/src/AppWrapper.tsx +++ b/src/AppWrapper.tsx @@ -1,6 +1,7 @@ -import { Routes } from "./Routes"; -import { GlobalProvider } from "qapp-core"; -import { publicSalt } from "./qapp-config.ts"; +import { Routes } from './Routes'; +import { GlobalProvider } from 'qapp-core'; +import { publicSalt } from './qapp-config.ts'; +import { PendingTxsProvider } from './state/contexts/PendingTxsProvider.tsx'; export const AppWrapper = () => { return ( @@ -14,10 +15,12 @@ export const AppWrapper = () => { authenticateOnMount: true, }, publicSalt: publicSalt, - appName: 'names' + appName: 'names', }} > - + + + ); }; diff --git a/src/components/RegisterName.tsx b/src/components/RegisterName.tsx index 71b01ba..f3d9250 100644 --- a/src/components/RegisterName.tsx +++ b/src/components/RegisterName.tsx @@ -5,10 +5,6 @@ import { DialogActions, DialogContent, DialogTitle, - List, - ListItem, - ListItemIcon, - ListItemText, styled, TextField, Typography, @@ -23,10 +19,11 @@ import { useGlobal, } from 'qapp-core'; import { useEffect, useState } from 'react'; -import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked'; import { BarSpinner } from '../common/Spinners/BarSpinner/BarSpinner'; import CheckIcon from '@mui/icons-material/Check'; import ErrorIcon from '@mui/icons-material/Error'; +import { useSetAtom } from 'jotai'; +import { namesAtom, pendingTxsAtom } from '../state/global/names'; export enum Availability { NULL = 'null', LOADING = 'loading', @@ -45,21 +42,48 @@ const Label = styled('label')` const RegisterName = () => { const [isOpen, setIsOpen] = useState(false); const balance = useGlobal().auth.balance; + const setNames = useSetAtom(namesAtom); + + const address = useGlobal().auth.address; const [nameValue, setNameValue] = useState(''); const [isNameAvailable, setIsNameAvailable] = useState( Availability.NULL ); + const setPendingTxs = useSetAtom(pendingTxsAtom); + const [isLoadingRegisterName, setIsLoadingRegisterName] = useState(false); const theme = useTheme(); const [nameFee, setNameFee] = useState(null); const registerNameFunc = async () => { + if (!address) return; const loadId = showLoading('Registering name...please wait'); try { setIsLoadingRegisterName(true); - await qortalRequest({ + const res = await qortalRequest({ action: 'REGISTER_NAME', name: nameValue, }); + setPendingTxs((prev) => { + return { + ...prev, // preserve existing categories + ['REGISTER_NAME']: { + ...(prev['REGISTER_NAME'] || {}), // preserve existing transactions in this category + [res.signature]: { + ...res, + status: 'PENDING', + callback: () => { + setNames((prev) => [ + ...prev, + { + name: res.name, + owner: res.creatorAddress, + }, + ]); + }, + }, // add or overwrite this transaction + }, + }; + }); showSuccess('Successfully registered a name'); setNameValue(''); setIsOpen(false); diff --git a/src/components/Tables/ForSaleTable.tsx b/src/components/Tables/ForSaleTable.tsx index 9acf2e2..b760028 100644 --- a/src/components/Tables/ForSaleTable.tsx +++ b/src/components/Tables/ForSaleTable.tsx @@ -8,12 +8,23 @@ import { Paper, Button, } from '@mui/material'; -import { useAtom } from 'jotai'; +import { useAtom, useSetAtom } from 'jotai'; import { forwardRef, useMemo } from 'react'; import { TableVirtuoso, TableComponents } from 'react-virtuoso'; -import { forSaleAtom, namesAtom } from '../../state/global/names'; +import { + forSaleAtom, + namesAtom, + pendingTxsAtom, +} from '../../state/global/names'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; +import { + dismissToast, + showError, + showLoading, + showSuccess, + useGlobal, +} from 'qapp-core'; interface NameData { name: string; @@ -78,21 +89,53 @@ function fixedHeaderContent( ); } -function rowContent(_index: number, row: NameData) { - const handleUpdate = () => { - console.log('Update:', row.name); - // Your logic here - }; - +function rowContent( + _index: number, + row: NameData, + setPendingTxs, + setNames, + setNamesForSale, + address +) { const handleBuy = async (name: string) => { + const loadId = showLoading('Attempting to purchase name...please wait'); + try { - console.log('hello'); - await qortalRequest({ + const res = await qortalRequest({ action: 'BUY_NAME', nameForSale: name, }); + showSuccess('Purchased name'); + setPendingTxs((prev) => { + return { + ...prev, // preserve existing categories + ['BUY_NAME']: { + ...(prev['BUY_NAME'] || {}), // preserve existing transactions in this category + [res.signature]: { + ...res, + status: 'PENDING', + callback: () => { + setNamesForSale((prev) => + prev.filter((item) => item?.name !== res.name) + ); + setNames((prev) => [ + ...prev, + { + name: res.name, + owner: res.creatorAddress, + }, + ]); + }, + }, // add or overwrite this transaction + }, + }; + }); } catch (error) { + showError(error?.message || 'Unable to purchase name'); + console.log('error', error); + } finally { + dismissToast(loadId); } }; @@ -119,6 +162,11 @@ export const ForSaleTable = ({ sortBy, handleSort, }) => { + const address = useGlobal().auth.address; + const setNames = useSetAtom(namesAtom); + const setNamesForSale = useSetAtom(forSaleAtom); + const setPendingTxs = useSetAtom(pendingTxsAtom); + return ( fixedHeaderContent(sortBy, sortDirection, handleSort) } - itemContent={rowContent} + itemContent={(index, row) => + rowContent( + index, + row, + setPendingTxs, + setNames, + setNamesForSale, + address + ) + } /> ); diff --git a/src/components/Tables/NameTable.tsx b/src/components/Tables/NameTable.tsx index f81ab2d..9aedde4 100644 --- a/src/components/Tables/NameTable.tsx +++ b/src/components/Tables/NameTable.tsx @@ -20,10 +20,17 @@ import { CircularProgress, Avatar, } from '@mui/material'; -import { useAtom } from 'jotai'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react'; import { TableVirtuoso, TableComponents } from 'react-virtuoso'; -import { forSaleAtom, namesAtom } from '../../state/global/names'; +import { + forceRefreshAtom, + forSaleAtom, + namesAtom, + pendingTxsAtom, + refreshAtom, + sortedPendingTxsByCategoryAtom, +} from '../../state/global/names'; import PersonIcon from '@mui/icons-material/Person'; import { useModal } from '../../hooks/useModal'; import { @@ -40,6 +47,7 @@ import { Availability } from '../RegisterName'; import CheckIcon from '@mui/icons-material/Check'; import ErrorIcon from '@mui/icons-material/Error'; import { BarSpinner } from '../../common/Spinners/BarSpinner/BarSpinner'; +import { usePendingTxs } from '../../hooks/useHandlePendingTxs'; interface NameData { name: string; isSelling?: boolean; @@ -80,10 +88,17 @@ function fixedHeaderContent() { } const ManageAvatar = ({ name, modalFunctionsAvatar }) => { - const [hasAvatar, setHasAvatar] = useState(null); + const { setHasAvatar, getHasAvatar } = usePendingTxs(); + const [refresh] = useAtom(refreshAtom); // just to subscribe + const [hasAvatarState, setHasAvatarState] = useState(null); const checkIfAvatarExists = useCallback(async (name) => { try { + const res = getHasAvatar(name); + if (res !== null) { + setHasAvatarState(res); + return; + } const identifier = `qortal_avatar`; const url = `/arbitrary/resources/searchsimple?mode=ALL&service=THUMBNAIL&identifier=${identifier}&limit=1&name=${name}&includemetadata=false&prefix=true`; const response = await getNameQueue.enqueue(() => @@ -97,9 +112,10 @@ const ManageAvatar = ({ name, modalFunctionsAvatar }) => { const responseData = await response.json(); if (responseData?.length > 0) { - setHasAvatar(true); + setHasAvatarState(true); + setHasAvatar(name, true); } else { - setHasAvatar(false); + setHasAvatarState(false); } } catch (error) { console.log(error); @@ -108,17 +124,19 @@ const ManageAvatar = ({ name, modalFunctionsAvatar }) => { useEffect(() => { if (!name) return; checkIfAvatarExists(name); - }, [name, checkIfAvatarExists]); + }, [name, checkIfAvatarExists, refresh]); return (