diff --git a/package-lock.json b/package-lock.json index 0590d47..f2853c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "ag-grid-react": "^32.0.1", "axios": "^1.7.2", "file-saver": "^2.0.5", + "jotai": "^2.12.3", "lodash": "^4.17.21", "moment": "^2.30.1", "qapp-core": "^1.0.22", @@ -5968,6 +5969,26 @@ "node": ">=8" } }, + "node_modules/jotai": { + "version": "2.12.3", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.12.3.tgz", + "integrity": "sha512-DpoddSkmPGXMFtdfnoIHfueFeGP643nqYUWC6REjUcME+PG2UkAtYnLbffRDw3OURI9ZUTcRWkRGLsOvxuWMCg==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 89c87c0..0be65a9 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "ag-grid-react": "^32.0.1", "axios": "^1.7.2", "file-saver": "^2.0.5", + "jotai": "^2.12.3", "lodash": "^4.17.21", "moment": "^2.30.1", "qapp-core": "^1.0.22", diff --git a/src/components/common/reusable-modal/ReusableModal-styles.tsx b/src/components/common/reusable-modal/ReusableModal-styles.tsx index 8ed0f36..1315264 100644 --- a/src/components/common/reusable-modal/ReusableModal-styles.tsx +++ b/src/components/common/reusable-modal/ReusableModal-styles.tsx @@ -6,6 +6,7 @@ export const ReusableModalContainer = styled(Box)(({ theme }) => ({ display: "flex", alignItems: "center", justifyContent: "center", + flexDirection: 'column', backgroundColor: theme.palette.background.default, position: "fixed", top: "50%", @@ -16,8 +17,10 @@ export const ReusableModalContainer = styled(Box)(({ theme }) => ({ maxWidth: '90vw', height: "auto", borderRadius: "20px", - border: "20px solid #3F3F3F", - zIndex: "100", + border: "5px solid #3F3F3F", + zIndex: 9000, + maxHeight: '90vh', + overflow: 'auto', boxShadow: "0px 4px 5px 0px hsla(0,0%,0%,0.14), \n\t\t0px 1px 10px 0px hsla(0,0%,0%,0.12), \n\t\t0px 2px 4px -1px hsla(0,0%,0%,0.2)", })); @@ -49,8 +52,8 @@ export const ReusableModalCloseIcon = styled(CloseIcon)(({ theme }) => ({ cursor: "pointer", fontSize: "30px", position: "absolute", - top: "20px", - right: "20px", + top: "10px", + right: "10px", transition: "all 0.3s ease-in-out", "&:hover": { transform: "scale(1.1)", diff --git a/src/components/common/reusable-modal/ReusableModal.tsx b/src/components/common/reusable-modal/ReusableModal.tsx index 877b136..928dc41 100644 --- a/src/components/common/reusable-modal/ReusableModal.tsx +++ b/src/components/common/reusable-modal/ReusableModal.tsx @@ -1,26 +1,76 @@ import { - ReusableModalBackdrop, - ReusableModalCloseIcon, - ReusableModalContainer, -} from "./ReusableModal-styles"; + Dialog, + DialogContent, + IconButton, + useTheme, + SxProps, +} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { Spacer } from '../Spacer'; + interface ReusableModalProps { backdrop?: boolean; onClickClose: () => void; children: React.ReactNode; + styles?: SxProps; + open: boolean; } export const ReusableModal: React.FC = ({ - backdrop, + backdrop = true, children, onClickClose, + styles, + open, }) => { + const theme = useTheme(); return ( - <> - - - {children} - - {backdrop && } - + + + + + + + {children} + ); }; diff --git a/src/components/header/Header-styles.tsx b/src/components/header/Header-styles.tsx index ce648c1..22644fa 100644 --- a/src/components/header/Header-styles.tsx +++ b/src/components/header/Header-styles.tsx @@ -228,7 +228,7 @@ export const CoinActionContainer = styled(Box)({ gap: "25px", alignItems: "center", justifyContent: "center", - width: "80%", + width: "100%", }); export const CoinActionRow = styled(Box)({ diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx index fc8f2f0..d282b42 100644 --- a/src/components/header/Header.tsx +++ b/src/components/header/Header.tsx @@ -68,6 +68,7 @@ import { NotificationContext } from "../../contexts/notificationContext"; import UnsignedFees from "../sell/UnsignedFees"; import { FeeManager } from "../sell/FeeManager"; import { Info } from "../sell/Info"; +import { Settings } from "../sell/Settings"; const checkIfLocal = async () => { try { @@ -126,7 +127,7 @@ const getCoinIcon = (coin) => { return img; }; -const SelectRow = ({ coin }) => { +export const SelectRow = ({ coin }) => { let img = getCoinIcon(coin); return ( @@ -367,7 +368,14 @@ export const Header = ({ {cropAddress(userInfo?.address)} ) : null} - + + + + + {openCoinActionModal.type === "send" ? ( diff --git a/src/components/sell/CreateSell.tsx b/src/components/sell/CreateSell.tsx index 635703d..49d3a2e 100644 --- a/src/components/sell/CreateSell.tsx +++ b/src/components/sell/CreateSell.tsx @@ -26,6 +26,7 @@ export const CustomLabel = styled(InputLabel)` font-size: 14px; line-height: 1.2; color: rgba(255, 255, 255, 0.5); + white-space: normal; `; export const minimumAmountSellTrades = { diff --git a/src/components/sell/FeeManager.tsx b/src/components/sell/FeeManager.tsx index 6134293..a0bd558 100644 --- a/src/components/sell/FeeManager.tsx +++ b/src/components/sell/FeeManager.tsx @@ -29,6 +29,8 @@ import { Spacer } from "../common/Spacer"; import { usePublish, Service, QortalGetMetadata } from "qapp-core"; import { SetLeftFeature } from "ag-grid-community"; import { formatTimestampForum } from "../../utils/formatTime"; +import { useAtom } from "jotai/react"; +import { selectedFeePublisherAtom } from "../../global/state"; function calculateFeeFromRate(feePerKb, sizeInBytes) { const fee = (feePerKb / 1000) * sizeInBytes; @@ -46,6 +48,8 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { service: "JSON", }); const { resource } = usePublish(3, "JSON", feeLocation); + const [selectedFeePublisher, setSelectedFeePublisher] = useAtom(selectedFeePublisherAtom) + const [editFee, setEditFee] = useState(""); const [openModal, setOpenModal] = useState(false); const [recommendedFee, setRecommendedFee] = useState("m"); @@ -104,12 +108,7 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { const recommendedFeeData = useMemo(() => { if (!resource?.data) return null; - if ( - !resource?.data?.["BTC"] || - !resource?.data?.["LTC"] || - !resource?.data?.["DOGE"] - ) - return null; + return resource.data; }, [resource?.data]); @@ -183,7 +182,7 @@ export const FeeManager = ({ selectedCoin, setFee, fee }) => { const getLatestFees = useCallback(async () => { try { const res = await fetch( - `/arbitrary/resources/searchsimple?service=JSON&identifier=foreign-fee&name=Foreign-Fee-Publisher&prefix=true&limit=1&reverse=true` + `/arbitrary/resources/searchsimple?service=JSON&identifier=foreign-fee&name=${selectedFeePublisher}&prefix=true&limit=1&reverse=true` ); const data = await res.json(); if (data && data?.length > 0) { @@ -196,7 +195,7 @@ const timestampSec = parseInt(parts[2], 10); } catch (error) { console.error(error) } - }, []); + }, [selectedFeePublisher]); useEffect(() => { getLatestFees(); @@ -239,12 +238,15 @@ const timestampSec = parseInt(parts[2], 10); setOpenModal(false); setEditFee(fee); }} + open={openModal} backdrop - > - + maxWidth: '95vw', + padding: '15px' + }} + > + Update fee {!hideRecommendations && feeTimestamp && ( - *Recommended fees last updated: {formatTimestampForum(feeTimestamp)} + *Recommended fees last updated: {formatTimestampForum(feeTimestamp)} )} diff --git a/src/components/sell/Info.tsx b/src/components/sell/Info.tsx index c73434c..e8cd505 100644 --- a/src/components/sell/Info.tsx +++ b/src/components/sell/Info.tsx @@ -67,11 +67,14 @@ export const Info = () => { setOpenModal(false); }} backdrop - > - + maxWidth: '95vw', + padding: '15px' + }} + open={openModal} + > + { + const [openModal, setOpenModal] = useState(false); + const [lockingFee, setLockingFee] = useState(""); + const [editLockingFee, setEditLockingFee] = useState(""); + const [openAlert, setOpenAlert] = useState(false); + const [info, setInfo] = useState(null); + const [selectedCoin, setSelectedCoin] = useState("LTC"); + const [selectedFeePublisher, setSelectedFeePublisher] = useAtom(selectedFeePublisherAtom) + const handleCloseAlert = ( + event?: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason + ) => { + if (reason === "clickaway") { + return; + } + + setOpenAlert(false); + setInfo(null); + }; + + const updateLockingFee = async () => { + const typeRequest = "feekb"; + + try { + const feeToSave = editLockingFee; + + const response = await qortalRequestWithTimeout( + { + action: "UPDATE_FOREIGN_FEE", + coin: selectedCoin, + type: typeRequest, + value: feeToSave, + }, + 1800000 + ); + + if (response && !isNaN(+response)) { + setLockingFee(response); + setEditLockingFee(response); + setOpenAlert(true); + setInfo({ + type: "success", + message: "Fee updated!", + }); + setOpenModal(false); + } else throw new Error("Unable to update fee"); + } catch (error) { + setOpenAlert(true); + setInfo({ + type: "error", + message: error?.message || "Unable to update fee", + }); + } + }; + + const establishUpdateFeeForm = useCallback(async (coin) => { + setLockingFee(""); + setEditLockingFee(""); + // if the coin or type is not set, then abort + if (!coin) { + return; + } + // const coinRequest = coin.current.toLowerCase(); + const typeRequest = "feekb"; + + try { + const response = await qortalRequestWithTimeout( + { + action: "GET_FOREIGN_FEE", + coin: coin.toLowerCase(), + type: typeRequest, + }, + 1800000 + ); + if (response !== null && response !== undefined && !isNaN(+response)) { + setLockingFee(response); + setEditLockingFee(response); + } + } catch (error) { + setLockingFee(""); + setEditLockingFee(""); + console.error(error); + } + }, []); + + useEffect(() => { + establishUpdateFeeForm(selectedCoin); + }, [selectedCoin, establishUpdateFeeForm]); + + return ( + <> + + {openModal && ( + { + setOpenModal(false); + setEditLockingFee(lockingFee); + }} + backdrop + styles={{ + width: "450px", + maxWidth: "95vw", + padding: "15px", + }} + open={openModal} + > + + Locking fees + + + + + Locking fee for {selectedCoin} (sats) + + + setEditLockingFee(e.target.value)} + autoComplete="off" + /> + + + + + + Update locking fee + + + + + Fee publisher + + + + )} + + + {info?.message} + + + + ); +}; diff --git a/src/components/sell/UnsignedFees.tsx b/src/components/sell/UnsignedFees.tsx index a4dc9cf..829bd3d 100644 --- a/src/components/sell/UnsignedFees.tsx +++ b/src/components/sell/UnsignedFees.tsx @@ -171,6 +171,12 @@ export default function UnsignedFees({ qortAddress }) { setOpenModal(false); }} backdrop + styles={{ + width: '450px', + maxWidth: '95vw', + padding: '15px' + }} + open={openModal} > diff --git a/src/global/state.ts b/src/global/state.ts new file mode 100644 index 0000000..d80242f --- /dev/null +++ b/src/global/state.ts @@ -0,0 +1,5 @@ +import { atomWithReset } from 'jotai/utils'; + + + +export const selectedFeePublisherAtom = atomWithReset('Foreign-Fee-Publisher'); diff --git a/src/index.scss b/src/index.scss index 0d42d70..1fb95bb 100644 --- a/src/index.scss +++ b/src/index.scss @@ -25,6 +25,7 @@ html, body { width: 100%; height: 100%; + word-break: break-word; } body { diff --git a/src/styles/theme.tsx b/src/styles/theme.tsx index cca7a85..14c1e74 100644 --- a/src/styles/theme.tsx +++ b/src/styles/theme.tsx @@ -154,6 +154,13 @@ const darkTheme = createTheme({ disableRipple: true, }, }, + MuiDialog: { + styleOverrides: { + paper: { + backgroundImage: 'none', + }, + }, + }, }, });