feat(MarketsOffers): Implement the market transactions table.

This commit is contained in:
a.bouhuolia 2022-06-02 22:49:36 +02:00
parent ec2193b460
commit e072d5b49b
16 changed files with 624 additions and 4 deletions

View file

@ -30,6 +30,7 @@ import {
PaymentMethods,
} from "@pages/Account";
import { MyWallet } from "@pages/MyWallet";
import { MarketsTransactions } from "@pages/Markets";
export function AppRoutes() {
return (
@ -38,6 +39,7 @@ export function AppRoutes() {
<Route path={ROUTES.Login} element={<Login />} />
<Route path={ROUTES.Welcome} element={<Welcome />} />
<Route path={ROUTES.CreateAccount} element={<CreateAccount />} />
<Route path={ROUTES.Markets} element={<MarketsTransactions />} />
<Route
path={ROUTES.MyWallet}
element={

View file

@ -38,4 +38,5 @@ export const Default = Template.bind({});
Default.args = {
currencyCode: "EUR",
value: 400000.12,
currentDisplay: "symbol",
};

View file

@ -17,13 +17,16 @@
import { useMemo } from "react";
import { useIntl } from "react-intl";
type CurrencyFormatType = "symbol" | "code" | "name" | "narrowSymbol";
interface CurrencyProps {
currencyCode?: string;
currentDisplay?: CurrencyFormatType;
value: number;
}
export function Currency(props: CurrencyProps) {
const { currencyCode, value } = props;
const { currencyCode, currentDisplay, value } = props;
const intl = useIntl();
const formattedNumber = useMemo(
@ -32,7 +35,7 @@ export function Currency(props: CurrencyProps) {
...(currencyCode
? {
currency: currencyCode,
currencyDisplay: "code",
currencyDisplay: currentDisplay || "code",
style: "currency",
}
: {

View file

@ -0,0 +1,56 @@
// =============================================================================
// 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 type { ComponentStory, ComponentMeta } from "@storybook/react";
import { MarketTransactions } from "./MarketTransactions";
export default {
title: "organisms/Markets/Transactions",
component: MarketTransactions,
} as ComponentMeta<typeof MarketTransactions>;
const Template: ComponentStory<typeof MarketTransactions> = () => {
return <MarketTransactions data={data} />;
};
export const Default = Template.bind({});
Default.args = {};
const data = [
{
price: 123,
priceComparison: 0.12,
amount: 123123,
amountCurrency: "EUR",
cost: 123,
costCurrency: "USD",
paymentMethod: "ASD",
accountAge: 12,
accountTrades: 1212,
},
{
price: 123,
priceComparison: 0.12,
amount: 123123,
amountCurrency: "EUR",
cost: 123,
costCurrency: "USD",
paymentMethod: "ASD",
accountAge: 12,
accountTrades: 1212,
},
];

View file

@ -0,0 +1,118 @@
// =============================================================================
// 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 { describe, expect, it } from "vitest";
import { render, screen } from "@testing-library/react";
import { MarketTransactions } from "./MarketTransactions";
import { AppProviders } from "@atoms/AppProviders";
describe("organisms::MarketTransactions", () => {
it("renders without exploding.", () => {
const { asFragment, unmount } = render(
<AppProviders>
<MarketTransactions data={data} />
</AppProviders>
);
expect(asFragment()).toMatchSnapshot();
unmount();
});
it("renders all columns.", () => {
const { unmount } = render(
<AppProviders>
<MarketTransactions data={data} />
</AppProviders>
);
expect(screen.queryByText("Price")).toBeInTheDocument();
expect(screen.queryByText("Amount")).toBeInTheDocument();
expect(screen.queryByText("Costs")).toBeInTheDocument();
expect(screen.queryByText("Payment Method")).toBeInTheDocument();
expect(screen.queryByText("Account Age")).toBeInTheDocument();
expect(screen.queryByText("Account Trades")).toBeInTheDocument();
unmount();
});
it("renders formatted price value with currency sign.", () => {
const { unmount } = render(
<AppProviders>
<MarketTransactions data={data} />
</AppProviders>
);
expect(screen.queryByText("€5,000.96")).toBeInTheDocument();
expect(screen.queryByText("€9,637.41")).toBeInTheDocument();
unmount();
});
it("renders formatted amount value with currency code.", () => {
const { unmount } = render(
<AppProviders>
<MarketTransactions data={data} />
</AppProviders>
);
expect(screen.queryByText("7,564.94 XMR")).toBeInTheDocument();
expect(screen.queryByText("6,483.23 XMR")).toBeInTheDocument();
unmount();
});
it("renders formatted cost value with currency sign.", () => {
const { unmount } = render(
<AppProviders>
<MarketTransactions data={data} />
</AppProviders>
);
expect(screen.queryByText("$532.34")).toBeInTheDocument();
expect(screen.queryByText("$983.32")).toBeInTheDocument();
unmount();
});
it("renders formatted account trades.", () => {
const { unmount } = render(
<AppProviders>
<MarketTransactions data={data} />
</AppProviders>
);
expect(screen.queryByText("3,412")).toBeInTheDocument();
expect(screen.queryByText("1,212")).toBeInTheDocument();
unmount();
});
});
const data = [
{
price: 5000.956,
priceComparison: 0.12,
priceCurrency: "EUR",
amount: 7564.94,
amountCurrency: "XMR",
cost: 532.34,
costCurrency: "USD",
paymentMethod: "ASD",
accountAge: 12,
accountTrades: 1212,
},
{
price: 9637.41,
priceComparison: 0.12,
priceCurrency: "EUR",
amount: 6483.23,
amountCurrency: "XMR",
cost: 983.32,
costCurrency: "USD",
paymentMethod: "ASD",
accountAge: 12,
accountTrades: 3412,
},
];

View file

@ -0,0 +1,96 @@
import { createStyles } from "@mantine/core";
import { createTable } from "@tanstack/react-table";
import { Table } from "@molecules/Table";
import type { MarketTransaction } from "./_types";
import {
MarketTransactionsPriceCell,
MarketTransactionsAmountCell,
MarketTransactionsAccountTradesCell,
MarketTransactionsCostsCell,
MarketTransactionsAccountAgeCell,
} from "./MarketTransactionsCell";
const table = createTable().setRowType<MarketTransaction>();
const columns = [
table.createDataColumn("price", {
id: "price",
header: "Price",
cell: ({ row }) => <MarketTransactionsPriceCell row={row.original} />,
size: 400,
}),
table.createDataColumn("amount", {
id: "amount",
header: "Amount",
size: 400,
cell: ({ row }) => <MarketTransactionsAmountCell row={row.original} />,
}),
table.createDataColumn("cost", {
id: "costs",
header: "Costs",
size: 400,
cell: ({ row }) => <MarketTransactionsCostsCell row={row.original} />,
}),
table.createDataColumn("paymentMethod", {
id: "paymentMethod",
header: "Payment Method",
size: 400,
}),
table.createDataColumn("accountAge", {
id: "accountAge",
header: "Account Age",
size: 400,
cell: ({ row }) => <MarketTransactionsAccountAgeCell row={row.original} />,
}),
table.createDataColumn("accountTrades", {
id: "accountTrades",
header: "Account Trades",
size: 400,
cell: ({ row }) => (
<MarketTransactionsAccountTradesCell row={row.original} />
),
}),
];
interface MarketTransactionsProps {
data: MarketTransaction[];
}
export function MarketTransactions({ data }: MarketTransactionsProps) {
const { classes, cx } = useStyles();
return (
<Table
table={table}
columns={columns}
data={data}
tableWrap={{
verticalSpacing: "md",
className: cx(classes.root),
}}
/>
);
}
const useStyles = createStyles(() => ({
root: {
"thead tr": {
backgroundColor: "#F8F8F8",
color: "#B7B6BD",
th: {
fontSize: 10,
letterSpacing: "0.05em",
textTransform: "uppercase",
borderBottomColor: "#E8E7EC",
},
},
"tbody tr": {
td: {
paddingTop: 22,
paddingBottom: 22,
borderBottomColor: "#E8E7EC",
},
},
},
}));

View file

@ -0,0 +1,76 @@
import { Currency } from "@atoms/Currency";
import { BodyText } from "@atoms/Typography";
import { Group, Text, ThemeIcon } from "@mantine/core";
import type { MarketTransaction } from "./_types";
export function MarketTransactionsAccountAgeCell({
row,
}: {
row?: MarketTransaction;
}) {
return (
<Group>
<ThemeIcon radius="xl" size="sm">
X
</ThemeIcon>
<Text>65 Days</Text>
</Group>
);
}
export function MarketTransactionsPriceCell({
row,
}: {
row?: MarketTransaction;
}) {
return (
<Group>
<BodyText heavy>
<Currency
currencyCode={row?.priceCurrency}
value={row?.price || 0}
currentDisplay="symbol"
/>
</BodyText>
<Text color="gray">-1%</Text>
</Group>
);
}
export function MarketTransactionsAmountCell({
row,
}: {
row?: MarketTransaction;
}) {
return (
<Currency currencyCode={row?.amountCurrency} value={row?.amount || 0} />
);
}
export function MarketTransactionsCostsCell({
row,
}: {
row?: MarketTransaction;
}) {
return (
<BodyText heavy>
<Currency
currencyCode={row?.costCurrency}
value={row?.cost || 0}
currentDisplay="symbol"
/>
</BodyText>
);
}
export function MarketTransactionsAccountTradesCell({
row,
}: {
row?: MarketTransaction;
}) {
return (
<BodyText heavy>
<Currency value={row?.accountTrades || 0} />
</BodyText>
);
}

View file

@ -0,0 +1,184 @@
// Vitest Snapshot v1
exports[`organisms::MarketTransactions > renders without exploding. 1`] = `
<DocumentFragment>
<table
class="mantine-Table-root mantine-ydlwhr"
>
<thead>
<tr>
<th
colspan="1"
style="width: 400px;"
>
Price
</th>
<th
colspan="1"
style="width: 400px;"
>
Amount
</th>
<th
colspan="1"
style="width: 400px;"
>
Costs
</th>
<th
colspan="1"
style="width: 400px;"
>
Payment Method
</th>
<th
colspan="1"
style="width: 400px;"
>
Account Age
</th>
<th
colspan="1"
style="width: 400px;"
>
Account Trades
</th>
</tr>
</thead>
<tbody>
<tr>
<td
style="width: 400px;"
>
<div
class="mantine-Group-root mantine-6y1794"
>
<div
class="mantine-Text-root mantine-Group-child mantine-1h17kkk"
>
€5,000.96
</div>
<div
class="mantine-Text-root mantine-Group-child mantine-zvhzrs"
>
-1%
</div>
</div>
</td>
<td
style="width: 400px;"
>
XMR 7,564.94
</td>
<td
style="width: 400px;"
>
<div
class="mantine-Text-root mantine-ix3vgq"
>
$532.34
</div>
</td>
<td
style="width: 400px;"
>
ASD
</td>
<td
style="width: 400px;"
>
<div
class="mantine-Group-root mantine-6y1794"
>
<div
class="mantine-ThemeIcon-root mantine-Group-child mantine-11491oq"
>
X
</div>
<div
class="mantine-Text-root mantine-Group-child mantine-1xtm85p"
>
65 Days
</div>
</div>
</td>
<td
style="width: 400px;"
>
<div
class="mantine-Text-root mantine-ix3vgq"
>
1,212.00
</div>
</td>
</tr>
<tr>
<td
style="width: 400px;"
>
<div
class="mantine-Group-root mantine-6y1794"
>
<div
class="mantine-Text-root mantine-Group-child mantine-1h17kkk"
>
€9,637.41
</div>
<div
class="mantine-Text-root mantine-Group-child mantine-zvhzrs"
>
-1%
</div>
</div>
</td>
<td
style="width: 400px;"
>
XMR 6,483.23
</td>
<td
style="width: 400px;"
>
<div
class="mantine-Text-root mantine-ix3vgq"
>
$983.32
</div>
</td>
<td
style="width: 400px;"
>
ASD
</td>
<td
style="width: 400px;"
>
<div
class="mantine-Group-root mantine-6y1794"
>
<div
class="mantine-ThemeIcon-root mantine-Group-child mantine-11491oq"
>
X
</div>
<div
class="mantine-Text-root mantine-Group-child mantine-1xtm85p"
>
65 Days
</div>
</div>
</td>
<td
style="width: 400px;"
>
<div
class="mantine-Text-root mantine-ix3vgq"
>
3,412.00
</div>
</td>
</tr>
</tbody>
</table>
</DocumentFragment>
`;

View file

@ -0,0 +1,12 @@
export interface MarketTransaction {
price: number;
priceComparison: number;
priceCurrency: string;
amount: number;
amountCurrency: string;
cost: number;
costCurrency: string;
paymentMethod: string;
accountAge: number;
accountTrades: number;
}

View file

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

View file

@ -26,6 +26,7 @@ export const NAV_LINKS = [
{
icon: <MarketsIcon />,
label: "Markets",
link: "/markets",
},
{
icon: <OffersIcon />,

View file

@ -14,7 +14,6 @@
// limitations under the License.
// =============================================================================
export const HAVENO_DAEMON_URL =
import.meta.env.VITE_HAVENO_URL ?? "http://localhost:8080";
export const HAVENO_DAEMON_URL = "http://170.187.138.125:8080";
export const HAVENO_DAEMON_PASSWORD =
import.meta.env.VITE_HAVENO_PASSWORD ?? "apitest";

View file

@ -22,6 +22,7 @@ export const ROUTES = {
CreateAccount: "/onboarding/create-account",
RestoreBackup: "/onboarding/restore-backup",
MyWallet: "/my-wallet",
Markets: "/markets",
// Account routes
PaymentAccounts: "/account/payment-accounts",

View file

@ -0,0 +1,34 @@
// =============================================================================
// 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 { useQuery } from "react-query";
import type { OfferInfo } from "haveno-ts";
import { QueryKeys } from "@constants/query-keys";
import { useHavenoClient } from "./useHavenoClient";
interface MarketsOfferesQuery {
assetCode: string;
direction?: "buy" | "sell";
}
export function useMarketsOffers(query: MarketsOfferesQuery) {
const client = useHavenoClient();
return useQuery<OfferInfo[], Error>(
[QueryKeys.MoneroNodeIsRunning, query],
() => client.getOffers(query.assetCode, query.direction)
);
}

View file

@ -0,0 +1,35 @@
import { NavbarLayout } from "@templates/NavbarLayout";
import { MarketTransactions } from "@organisms/MarketTransactions";
export function MarketsTransactions() {
return (
<NavbarLayout>
<MarketTransactions data={data} />
</NavbarLayout>
);
}
const data = [
{
price: 123,
priceComparison: 0.12,
amount: 123123,
amountCurrency: "EUR",
cost: 123,
costCurrency: "USD",
paymentMethod: "ASD",
accountAge: 12,
accountTrades: 1212,
},
{
price: 123,
priceComparison: 0.12,
amount: 123123,
amountCurrency: "EUR",
cost: 123,
costCurrency: "USD",
paymentMethod: "ASD",
accountAge: 12,
accountTrades: 1212,
},
];

View file

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