diff --git a/applications/tari_walletd/web_ui/src/components/AccountName.tsx b/applications/tari_walletd/web_ui/src/components/AccountName.tsx new file mode 100644 index 0000000000..ed708b0bbe --- /dev/null +++ b/applications/tari_walletd/web_ui/src/components/AccountName.tsx @@ -0,0 +1,147 @@ +// Copyright 2025. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import React, { useState } from "react"; +import { Box, TextField, IconButton } from "@mui/material"; +import { IoCheckmark, IoClose } from "react-icons/io5"; +import { ComponentAddress } from "@tari-project/typescript-bindings"; +import { useAccountsRename } from "../services/api/hooks/useAccounts"; +import { LuPencilLine } from "react-icons/lu"; +import { useTheme } from "@mui/material/styles"; + +export interface AccountNameProps { + accountAddress: ComponentAddress; + currentName?: string | null; + showRenameButton?: boolean; + onRenameSuccess?: (newName: string) => void; + onRenameError?: (error: any) => void; +} + +const AccountName: React.FC = ({ + accountAddress, + currentName, + showRenameButton = true, + onRenameSuccess, + onRenameError, +}) => { + const [isEditingName, setIsEditingName] = useState(false); + const [newName, setNewName] = useState(""); + + const renameAccountMutation = useAccountsRename(); + + const theme = useTheme(); + + const handleStartEdit = () => { + setIsEditingName(true); + setNewName(currentName || ""); + }; + + const handleCancelEdit = () => { + setIsEditingName(false); + setNewName(""); + }; + + const handleSaveRename = () => { + if (newName.trim() && newName !== currentName) { + renameAccountMutation.mutate( + { + account: accountAddress, + newName: newName.trim(), + }, + { + onSuccess: () => { + const trimmedName = newName.trim(); + setIsEditingName(false); + setNewName(""); + onRenameSuccess?.(trimmedName); + }, + onError: (error: any) => { + console.error("Error renaming account:", error); + onRenameError?.(error); + }, + }, + ); + } else { + handleCancelEdit(); + } + }; + + const handleKeyPress = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + handleSaveRename(); + } else if (event.key === "Escape") { + handleCancelEdit(); + } + }; + + return ( + + {isEditingName ? ( + setNewName(e.target.value)} + onKeyDown={handleKeyPress} + size="small" + autoFocus + disabled={renameAccountMutation.isPending} + placeholder="Account name" + /> + ) : ( + {currentName || ""} + )} + {showRenameButton && ( + <> + {isEditingName ? ( + + + + + + + + + ) : ( + + + + )} + + )} + + ); +}; + +export default AccountName; diff --git a/applications/tari_walletd/web_ui/src/routes/AccountDetails/AccountDetails.tsx b/applications/tari_walletd/web_ui/src/routes/AccountDetails/AccountDetails.tsx index eba65a6d6e..6eda700c46 100644 --- a/applications/tari_walletd/web_ui/src/routes/AccountDetails/AccountDetails.tsx +++ b/applications/tari_walletd/web_ui/src/routes/AccountDetails/AccountDetails.tsx @@ -23,24 +23,20 @@ import { useState } from "react"; import PageHeading from "@components/PageHeading"; import Grid from "@mui/material/Grid"; -import { StyledPaper } from "@components/StyledComponents"; +import { StyledPaper, InnerHeading } from "@components/StyledComponents"; import TableContainer from "@mui/material/TableContainer"; import Table from "@mui/material/Table"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import TableCell from "@mui/material/TableCell"; import TableBody from "@mui/material/TableBody"; -import { useAccountsGetBalances, useAccountsGet } from "@api/hooks/useAccounts"; +import { useAccountsGetBalances, useAccountsGet } from "../../services/api/hooks/useAccounts"; +import AccountName from "@components/AccountName"; import { useNFTsList } from "@api/hooks/useNfts"; import { ApiError } from "@api/helpers/types"; import { DataTableCell } from "@components/StyledComponents"; import FetchStatusCheck from "@components/FetchStatusCheck"; -import { - BalanceEntry, - decodeOotleAddress, - decodeOotleAddressOrNull, - substateIdToString, -} from "@tari-project/typescript-bindings"; +import { BalanceEntry, decodeOotleAddressOrNull, substateIdToString } from "@tari-project/typescript-bindings"; import NftList from "@routes/AssetVault/NFTs/NFTList"; import CopyAddress from "@components/CopyAddress"; import { Form, useParams } from "react-router-dom"; @@ -49,6 +45,7 @@ import Loading from "@components/Loading"; import { IoAdd } from "react-icons/io5"; import { Box, Fade, TextField, Button } from "@mui/material"; import { handleChangePage, handleChangeRowsPerPage } from "@utils/helpers"; +import { formatCurrency } from "@utils/helpers"; function BalanceRow(props: BalanceEntry) { return ( @@ -57,8 +54,8 @@ function BalanceRow(props: BalanceEntry) { {props.resource_type} - {props.balance} - {props.confidential_balance} + {formatCurrency(props.balance)} + {formatCurrency(props.confidential_balance)} ); } @@ -171,7 +168,9 @@ function AccountDetailsLayout() { - {accountsData?.account?.name || ""} + + + @@ -195,7 +194,7 @@ function AccountDetailsLayout() { - Balances + Balances - Account NFTs + Account NFTs Loading...; } + const handleRenameSuccess = (newName: string) => { + setAccount({ ...account, name: newName }); + }; + return ( @@ -51,7 +55,13 @@ function AccountDetails() { - {account.name} + + + diff --git a/applications/tari_walletd/web_ui/src/services/api/hooks/useAccounts.ts b/applications/tari_walletd/web_ui/src/services/api/hooks/useAccounts.ts index d9fc1d0cfc..c4c0b4afc6 100644 --- a/applications/tari_walletd/web_ui/src/services/api/hooks/useAccounts.ts +++ b/applications/tari_walletd/web_ui/src/services/api/hooks/useAccounts.ts @@ -30,6 +30,7 @@ import { accountsGetBalances, accountsGetDefault, accountsList, + accountsRename, accountsStealthTransfer, accountsTransfer, mintFaucetNfts, @@ -89,6 +90,30 @@ export const useAccountsCreate = () => { }); }; +export type AccountsRenameMutate = { + account: ComponentAddress; + newName: string; +}; + +export const useAccountsRename = () => { + return useMutation({ + mutationFn: async (req: AccountsRenameMutate) => { + return await accountsRename({ + account: { ComponentAddress: req.account }, + new_name: req.newName, + }); + }, + onError: (error: ApiError) => { + error; + }, + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ queryKey: ["accounts"] }); + queryClient.invalidateQueries({ queryKey: [`accounts_get_${variables.account}`] }); + queryClient.refetchQueries({ queryKey: ["accounts"] }); + }, + }); +}; + export interface TransferParams { account: ComponentAddress; amount: number; diff --git a/applications/tari_walletd/web_ui/src/utils/helpers.tsx b/applications/tari_walletd/web_ui/src/utils/helpers.tsx index 1dd04ffaf7..d4c4a0993f 100644 --- a/applications/tari_walletd/web_ui/src/utils/helpers.tsx +++ b/applications/tari_walletd/web_ui/src/utils/helpers.tsx @@ -252,7 +252,7 @@ export function bigintToDecimalString(int: bigint | Amount, decimalPlaces: numbe return `${wholeValues}.${padding}${fractionalValues}`; } -export const formatCurrency = (amount: number | bigint): string => { +export const formatCurrency = (amount: number | bigint | Amount): string => { const { currencySymbol } = useCurrencyStore.getState(); if (typeof amount === "bigint") { @@ -272,8 +272,37 @@ export const formatCurrency = (amount: number | bigint): string => { minimumFractionDigits: 0, maximumFractionDigits: CURRENCY.DECIMALS, })} ${currencySymbol}`; + } else if (typeof amount === "string") { + // Handle Amount type + try { + const numericAmount = BigInt(amount); + const divisor = BigInt(CURRENCY.DIVISOR); + const integerPart = numericAmount / divisor; + const remainder = numericAmount % divisor; + + const fractionalPart = remainder.toString().padStart(CURRENCY.DECIMALS, "0"); + + return `${Number(integerPart).toLocaleString("en-US")}.${fractionalPart} ${currencySymbol}`; + } catch (error) { + console.error("Failed to parse Amount:", amount, error); + return `0 ${currencySymbol}`; + } } else { - return `0 ${currencySymbol}`; + // Handle any other type (object, etc.) + try { + const stringValue = String(amount); + const numericAmount = BigInt(stringValue); + const divisor = BigInt(CURRENCY.DIVISOR); + const integerPart = numericAmount / divisor; + const remainder = numericAmount % divisor; + + const fractionalPart = remainder.toString().padStart(CURRENCY.DECIMALS, "0"); + + return `${Number(integerPart).toLocaleString("en-US")}.${fractionalPart} ${currencySymbol}`; + } catch (error) { + console.error("Failed to parse Amount:", amount, error); + return `0 ${currencySymbol}`; + } } }; diff --git a/applications/tari_walletd/web_ui/src/utils/json_rpc.ts b/applications/tari_walletd/web_ui/src/utils/json_rpc.ts index b343a308a8..e76d987f43 100644 --- a/applications/tari_walletd/web_ui/src/utils/json_rpc.ts +++ b/applications/tari_walletd/web_ui/src/utils/json_rpc.ts @@ -30,6 +30,8 @@ import type { AccountsCreateFreeTestCoinsResponse, AccountsCreateRequest, AccountsCreateResponse, + AccountsRenameRequest, + AccountsRenameResponse, AccountSetDefaultRequest, AccountSetDefaultResponse, AccountsGetBalancesRequest, @@ -271,7 +273,8 @@ export const accountsClaimBurn = (request: ClaimBurnRequest): Promise c.accountsClaimBurn(request)); export const accountsCreate = (request: AccountsCreateRequest): Promise => client().then((c) => c.accountsCreate(request)); - +export const accountsRename = (request: AccountsRenameRequest): Promise => + client().then((c) => c.accountsRename(request)); export const accountsList = (request: AccountsListRequest): Promise => client().then((c) => c.accountsList(request)); export const accountsGetBalances = (request: AccountsGetBalancesRequest): Promise =>