fix: My Wallet page

- download qr code
- ui inconsistencies fixed

---
Reviewed-by @schowdhuri
This commit is contained in:
Ahmed Bouhuolia 2022-06-02 18:10:38 +02:00 committed by GitHub
parent b40fe25930
commit ec2193b460
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 353 additions and 66 deletions

View file

@ -0,0 +1 @@
export * from "./DetailItemCard";

View file

@ -45,14 +45,14 @@ const useStyles = createStyles((theme, _params, getRef) => {
paddingRight: 0,
textTransform: "uppercase",
"&:first-child": {
"&:first-of-type": {
marginLeft: 0,
},
"&:last-child": {
marginRight: 0,
},
[`&.${tabActiveRef}`]: {
borderBottomColor: theme.primaryColor,
borderBottomColor: theme.colors.blue[6],
color: theme.colors.dark[9],
},
},

View file

@ -0,0 +1,14 @@
import { Anchor as MAnchor, createStyles } from "@mantine/core";
import type { AnchorProps as MAnchorProps } from "@mantine/core";
export function Anchor(props: MAnchorProps<"a">) {
const { classes, cx } = useStyles();
return <MAnchor {...props} className={cx(props.className, classes.anchor)} />;
}
const useStyles = createStyles((theme) => ({
anchor: {
color: theme.colors.blue[6],
},
}));

View file

@ -16,3 +16,4 @@
export * from "./Heading";
export * from "./Text";
export * from "./Anchor";

View file

@ -17,20 +17,17 @@
import { FormattedMessage, useIntl } from "react-intl";
import QRCode from "react-qr-code";
import type { OpenConfirmModal } from "@mantine/modals/lib/context";
import { showNotification } from "@mantine/notifications";
import { useModals } from "@mantine/modals";
import { useClipboard } from "@mantine/hooks";
import {
Anchor,
Box,
createStyles,
Group,
SimpleGrid,
Skeleton,
} from "@mantine/core";
import { Box, createStyles, Group, SimpleGrid, Skeleton } from "@mantine/core";
import { DetailItem } from "@atoms/DetailItem";
import { Button } from "@atoms/Buttons";
import { Anchor } from "@atoms/Typography";
import { DetailItemCard } from "@atoms/DetailItemCard";
import { LangKeys } from "@constants/lang";
import { DetailItemCard } from "@atoms/DetailItemCard/DetailItemCard";
import { useSetDownloadQRCode } from "@hooks/haveno/useSetDownloadQRCode";
import { Modals } from "@constants/modals";
interface AddressCardProps {
label?: string;
@ -49,17 +46,34 @@ export function AddressCard({
}: AddressCardProps) {
const modals = useModals();
const { classes } = useStyles();
const { formatMessage } = useIntl();
const { mutateAsync: downloadQRCode } = useSetDownloadQRCode();
const clipboard = useClipboard({ timeout: COPY_TEXT_TIMEOUT });
const handleCopyClick = () => {
clipboard.copy(address);
};
const handleQRDownloadClick = (addressCode: string) => {
downloadQRCode(addressCode).then(() => {
showNotification({
color: "green",
message: formatMessage({
id: LangKeys.AddressCardQRCodeSavedSuccessNotif,
defaultMessage: "The QR code has been saved successfully.",
}),
});
modals.closeModal(Modals.QRCodeAddress);
});
};
const handleQRClick = () => {
const modalId = modals.openModal({
id: Modals.QRCodeAddress,
children: (
<AddressCardQRModalContent
address={address}
onQRDownloadClick={handleQRDownloadClick}
onReturnClick={() => modals.closeModal(modalId)}
/>
),
@ -137,7 +151,7 @@ export function AddressCardSkeleton({
interface AddressCardQRModalContentProps {
address: string;
onQRDownloadClick?: () => void;
onQRDownloadClick?: (address: string) => void;
onReturnClick?: () => void;
}
@ -149,10 +163,16 @@ function AddressCardQRModalContent({
const { classes } = useStyles();
const { formatMessage } = useIntl();
const handleQRDownloadClick = () => {
onQRDownloadClick && onQRDownloadClick(address);
};
return (
<Box>
<Box className={classes.qrModalRoot}>
<DetailItem
classNames={{ content: classes.qrModalAddress }}
classNames={{
content: classes.qrModalAddress,
label: classes.qrModalAddressLabel,
}}
label={formatMessage({
id: LangKeys.MyWalletQRModalPrimaryAddress,
defaultMessage: "Primary Address",
@ -173,7 +193,7 @@ function AddressCardQRModalContent({
/>
</Button>
<Button onClick={onQRDownloadClick}>
<Button onClick={handleQRDownloadClick}>
<FormattedMessage
id={LangKeys.MyWalletQRModalDownloadQRBtn}
defaultMessage="Download QR"
@ -197,11 +217,18 @@ const useStyles = createStyles((theme) => ({
addressBtns: {
marginLeft: "auto",
},
qrModalRoot: {
padding: "1.5rem",
},
qrRoot: {
marginTop: theme.spacing.xl,
marginTop: theme.spacing.xl * 1.5,
marginBottom: theme.spacing.xl * 1.5,
textAlign: "center",
},
qrModalAddress: {
fontSize: theme.fontSizes.lg,
},
qrModalAddressLabel: {
fontSize: theme.fontSizes.xs,
},
}));

View file

@ -44,12 +44,12 @@ exports[`molecules::AddressCard > renders without exploding 1`] = `
class="mantine-Group-root mantine-Group-child mantine-ze53qg"
>
<a
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-16zvpw1"
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-1yvb6qd"
>
Copy
</a>
<a
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-16zvpw1"
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-1yvb6qd"
>
QR
</a>

View file

@ -64,7 +64,7 @@ export function WalletTransactions({ data }: WalletTransactionsProps) {
const useStyles = createStyles(() => ({
root: {
"tbody tr td:first-child": {
"tbody tr td:first-of-type": {
paddingLeft: 0,
},
"tbody tr td:last-child": {

View file

@ -33,22 +33,32 @@ export function MyWalletMoneroBalanceContent() {
<MoneroBalance>
<MoneroBalance.Detail
label={formatMessage({
id: LangKeys.MyWalletMoneroAvaliableBalance,
defaultMessage: "Avaliable Balance",
id: LangKeys.MyWalletMoneroTotalBalance,
defaultMessage: "Total Balance",
})}
data-testid="avaliable-balance"
data-testid="total-balance"
>
<Currency value={balanceInfo.balance} />
<Currency value={balanceInfo.total} />
</MoneroBalance.Detail>
<MoneroBalance.Detail
label={formatMessage({
id: LangKeys.MyWalletMoneroReserveredFunds,
defaultMessage: "Reservered Funds",
id: LangKeys.MyWalletMoneroAvailableBalance,
defaultMessage: "Available Balance",
})}
data-testid="reserverd-funds"
data-testid="available-balance"
>
<Currency value={balanceInfo.reservedOfferBalance || 0} />
<Currency value={balanceInfo.availableBalance || 0} />
</MoneroBalance.Detail>
<MoneroBalance.Detail
label={formatMessage({
id: LangKeys.MyWalletMoneroReservedFunds,
defaultMessage: "Reserved Funds",
})}
data-testid="reserved-funds"
>
<Currency value={balanceInfo.reservedBalance || 0} />
</MoneroBalance.Detail>
<MoneroBalance.Detail

View file

@ -26,8 +26,8 @@ export function MyWalletMeneroBalanceSkeleton() {
<MoneroBalance>
<MoneroBalance.Detail
label={formatMessage({
id: LangKeys.MyWalletMoneroAvaliableBalance,
defaultMessage: "Avaliable Balance",
id: LangKeys.MyWalletMoneroTotalBalance,
defaultMessage: "Total Balance",
})}
>
<Skeleton height={8} radius="xl" mt={6} />
@ -35,8 +35,17 @@ export function MyWalletMeneroBalanceSkeleton() {
<MoneroBalance.Detail
label={formatMessage({
id: LangKeys.MyWalletMoneroReserveredFunds,
defaultMessage: "Reservered Funds",
id: LangKeys.MyWalletMoneroAvailableBalance,
defaultMessage: "Available Balance",
})}
>
<Skeleton height={8} radius="xl" mt={6} />
</MoneroBalance.Detail>
<MoneroBalance.Detail
label={formatMessage({
id: LangKeys.MyWalletMoneroReservedFunds,
defaultMessage: "Reserved Funds",
})}
>
<Skeleton height={8} radius="xl" mt={6} />

View file

@ -28,9 +28,10 @@ describe("organisms::MyWalletMoneroBalance", () => {
data: {
balance: MoneroBalance.balance,
lockedBalance: MoneroBalance.lockedBalance,
reservedOfferBalance: MoneroBalance.reserverdBalance,
reservedTradeBalance: MoneroBalance.reserverdBalance,
reservedBalance: MoneroBalance.reservedBalance,
unlockedBalance: MoneroBalance.unlockedBalance,
total: MoneroBalance.total,
availableBalance: MoneroBalance.availableBalance,
},
}),
}));
@ -46,28 +47,30 @@ describe("organisms::MyWalletMoneroBalance", () => {
unmount();
});
it("contains avaliable balance, reservered funds and locked funds details", () => {
it("contains total, available balance, reserved funds and locked funds details", () => {
const { unmount } = render(
<AppProviders>
<MyWalletMoneroBalance />
</AppProviders>
);
expect(screen.queryByTestId("avaliable-balance")).toBeInTheDocument();
expect(screen.queryByTestId("total-balance")).toBeInTheDocument();
expect(screen.queryByTestId("available-balance")).toBeInTheDocument();
expect(screen.queryByTestId("locked-funds")).toBeInTheDocument();
expect(screen.queryByTestId("reserverd-funds")).toBeInTheDocument();
expect(screen.queryByTestId("reserved-funds")).toBeInTheDocument();
unmount();
});
it("contains avaliable balance, reservered funds and locked funds details", () => {
it("contains total. available balance, reserved funds and locked funds details values.", () => {
const { unmount } = render(
<AppProviders>
<MyWalletMoneroBalance />
</AppProviders>
);
expect(screen.queryByTestId("avaliable-balance")).toHaveTextContent(
"835,120.34017"
expect(screen.queryByTestId("total-balance")).toHaveTextContent("1,000.1");
expect(screen.queryByTestId("available-balance")).toHaveTextContent(
"8,000.65"
);
expect(screen.queryByTestId("reserverd-funds")).toHaveTextContent(
expect(screen.queryByTestId("reserved-funds")).toHaveTextContent(
"74,610.1236"
);
expect(screen.queryByTestId("locked-funds")).toHaveTextContent(
@ -79,7 +82,9 @@ describe("organisms::MyWalletMoneroBalance", () => {
const MoneroBalance = {
balance: 835120.34017,
reserverdBalance: 74610.1236,
reservedBalance: 74610.1236,
lockedBalance: 90371.161239,
unlockedBalance: 0,
total: 1000.1,
availableBalance: 8000.65,
};

View file

@ -43,27 +43,42 @@ exports[`organisms::MyWalletMoneroBalance > renders without exploding 1`] = `
>
<div
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-2d5onn"
data-testid="avaliable-balance"
data-testid="total-balance"
>
<div
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
>
Avaliable Balance
Total Balance
</div>
<div
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"
>
835,120.34017
1,000.10
</div>
</div>
<div
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-2d5onn"
data-testid="reserverd-funds"
data-testid="available-balance"
>
<div
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
>
Reservered Funds
Available Balance
</div>
<div
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"
>
8,000.65
</div>
</div>
<div
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-2d5onn"
data-testid="reserved-funds"
>
<div
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
>
Reserved Funds
</div>
<div
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"

View file

@ -47,7 +47,7 @@ exports[`organisms::MyWalletMeneroBalanceSkeleton > renders without exploding 1`
<div
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
>
Avaliable Balance
Total Balance
</div>
<div
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"
@ -63,7 +63,23 @@ exports[`organisms::MyWalletMeneroBalanceSkeleton > renders without exploding 1`
<div
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
>
Reservered Funds
Available Balance
</div>
<div
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"
>
<div
class="mantine-Skeleton-root mantine-Skeleton-visible mantine-12edv24"
/>
</div>
</div>
<div
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-2d5onn"
>
<div
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
>
Reserved Funds
</div>
<div
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"

View file

@ -28,12 +28,12 @@ exports[`organisms::MyWalletPrimaryAddress > renders without exploding 1`] = `
class="mantine-Group-root mantine-Group-child mantine-ze53qg"
>
<a
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-16zvpw1"
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-1yvb6qd"
>
Copy
</a>
<a
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-16zvpw1"
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-1yvb6qd"
>
QR
</a>

View file

@ -77,7 +77,7 @@ export function MyWalletSendForm() {
return (
<form onSubmit={form.onSubmit(handleFormSubmit)}>
<Stack spacing="xl">
<Stack spacing="xl" mt="md">
<SimpleGrid cols={2}>
<TextInput
id="amount"

View file

@ -3,7 +3,7 @@
exports[`organisms::MyWalletMoneroBalance > renders without exploding. 1`] = `
<DocumentFragment>
<table
class="mantine-Table-root mantine-16jrhzw"
class="mantine-Table-root mantine-u2os4d"
>
<tbody>
<tr>

View file

@ -59,14 +59,16 @@ export enum LangKeys {
AddressCardCopyBtn = "accountCard.copyButton",
AddressCardCopiedBtn = "accountCard.copiedButton",
AddressCardQRBtn = "accountCard.qrButton",
AddressCardQRCodeSavedSuccessNotif = "accountCode.qrCodeSavedSuccessfully.notification",
MyWalletSendFieldAmount = "myWallet.send.amountField",
MyWalletSendFieldPaymentId = "myWallet.send.paymentIdField",
MyWalletSendFieldPaymentIdPlaceholder = "myWallet.send.paymentIdFieldPlaceholder",
MyWalletSendFieldAddress = "myWallet.send.addressField",
MyWalletSendFieldAddressPlaceholder = "myWallet.send.addressFieldPlaceholder",
MyWalletReceiveTitle = "myWallet.receive.receiveTitle",
MyWalletMoneroAvaliableBalance = "myWallet.monero.avaliableBalance",
MyWalletMoneroReserveredFunds = "myWallet.monero.reserveredFunds",
MyWalletMoneroTotalBalance = "myWallet.monero.totalBalance",
MyWalletMoneroAvailableBalance = "myWallet.monero.availableBalance",
MyWalletMoneroReservedFunds = "myWallet.monero.reservedFunds",
MyWalletMoneroLockedFunds = "myWallet.monero.lockedFunds",
MyWalletTabTransactions = "myWallet.transactionsTab",
MyWalletTabSend = "myWallet.sendTab",

View file

@ -69,13 +69,16 @@ const LangPackEN: { [key in LangKeys]: string } = {
[LangKeys.AddressCardCopyBtn]: "Copy",
[LangKeys.AddressCardCopiedBtn]: "Copied",
[LangKeys.AddressCardQRBtn]: "QR",
[LangKeys.AddressCardQRCodeSavedSuccessNotif]:
"The QR code has been saved successfully.",
[LangKeys.MyWalletSendFieldAmount]: "Amount",
[LangKeys.MyWalletSendFieldAddress]: "Address",
[LangKeys.MyWalletSendFieldAddressPlaceholder]: "Paste in address here...",
[LangKeys.MyWalletSendFieldPaymentId]: "Payment ID",
[LangKeys.MyWalletSendFieldPaymentIdPlaceholder]: "Type",
[LangKeys.MyWalletMoneroAvaliableBalance]: "Avaliable Balance",
[LangKeys.MyWalletMoneroReserveredFunds]: "Reservered Funds",
[LangKeys.MyWalletMoneroTotalBalance]: "Total Balance",
[LangKeys.MyWalletMoneroAvailableBalance]: "Available Balance",
[LangKeys.MyWalletMoneroReservedFunds]: "Reserved Funds",
[LangKeys.MyWalletMoneroLockedFunds]: "Locked Funds",
[LangKeys.MyWalletTabTransactions]: "Transactions",
[LangKeys.MyWalletTabSend]: "Send",

View file

@ -69,13 +69,16 @@ const LangPackES: { [key in LangKeys]: string } = {
[LangKeys.AddressCardCopyBtn]: "Dupdo",
[LangKeys.AddressCardCopiedBtn]: "Copiada",
[LangKeys.AddressCardQRBtn]: "QR",
[LangKeys.AddressCardQRCodeSavedSuccessNotif]:
"El código QR se ha guardado correctamente.",
[LangKeys.MyWalletSendFieldAmount]: "Monto",
[LangKeys.MyWalletSendFieldAddress]: "Dirección",
[LangKeys.MyWalletSendFieldAddressPlaceholder]: "Pegue la dirección aquí...",
[LangKeys.MyWalletSendFieldPaymentId]: "ID de pago",
[LangKeys.MyWalletSendFieldPaymentIdPlaceholder]: "Tipo",
[LangKeys.MyWalletMoneroAvaliableBalance]: "Saldo disponible",
[LangKeys.MyWalletMoneroReserveredFunds]: "Fondos Reservados",
[LangKeys.MyWalletMoneroTotalBalance]: "Balance total",
[LangKeys.MyWalletMoneroAvailableBalance]: "Saldo disponible",
[LangKeys.MyWalletMoneroReservedFunds]: "Fondos Reservados",
[LangKeys.MyWalletMoneroLockedFunds]: "Fondos bloqueados",
[LangKeys.MyWalletTabTransactions]: "Transactions",
[LangKeys.MyWalletTabSend]: "Enviar",

View file

@ -0,0 +1,19 @@
// =============================================================================
// Copyright 2022 Haveno
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
export enum Modals {
QRCodeAddress = "QRCodeAddress",
}

View file

@ -24,6 +24,9 @@ interface BalanceInfo {
lockedBalance: number;
reservedOfferBalance: number;
reservedTradeBalance: number;
availableBalance: number;
reservedBalance: number;
total: number;
}
export function useBalances() {
@ -33,12 +36,25 @@ export function useBalances() {
const xmrBalances = await client.getBalances();
const balances = xmrBalances.toObject();
const balance = parseFloat(balances.balance);
const unlockedBalance = parseFloat(balances.unlockedBalance);
const lockedBalance = parseFloat(balances.lockedBalance);
const reservedOfferBalance = parseFloat(balances.reservedOfferBalance);
const reservedTradeBalance = parseFloat(balances.reservedTradeBalance);
const availableBalance = unlockedBalance;
const reservedBalance = reservedOfferBalance + reservedTradeBalance;
const total = availableBalance + lockedBalance + reservedBalance;
return {
balance: parseFloat(balances.balance),
unlockedBalance: parseFloat(balances.unlockedBalance),
lockedBalance: parseFloat(balances.lockedBalance),
reservedOfferBalance: parseFloat(balances.reservedOfferBalance),
reservedTradeBalance: parseFloat(balances.reservedTradeBalance),
balance,
unlockedBalance,
lockedBalance,
reservedOfferBalance,
reservedTradeBalance,
availableBalance,
reservedBalance,
total,
};
});
}

View file

@ -0,0 +1,23 @@
// =============================================================================
// Copyright 2022 Haveno
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
import { useMutation } from "react-query";
export function useSetDownloadQRCode() {
return useMutation(async (code: string) => {
await window.haveno.downloadQRCode(code);
});
}