mirror of
https://github.com/haveno-dex/haveno-ui.git
synced 2024-10-01 07:35:39 -04:00
feat: my wallet
- transaction list - primary address and qr code - generate sub address - send and receive xmr --- Reviewed-by: @schowdhuri
This commit is contained in:
parent
500be9a7fd
commit
b40fe25930
@ -88,6 +88,7 @@
|
||||
"@mantine/hooks": "^4.1.2",
|
||||
"@mantine/modals": "^4.1.2",
|
||||
"@mantine/notifications": "^4.1.2",
|
||||
"@tanstack/react-table": "^8.0.0-alpha.87",
|
||||
"dayjs": "^1.11.0",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "4.6.5",
|
||||
@ -98,6 +99,7 @@
|
||||
"react": "<18.0.0",
|
||||
"react-dom": "<18.0.0",
|
||||
"react-intl": "^5.24.8",
|
||||
"react-qr-code": "^2.0.7",
|
||||
"react-query": "^3.34.19",
|
||||
"react-router-dom": "6",
|
||||
"recoil": "^0.7.0",
|
||||
|
3
packages/renderer/assets/arrow-north.svg
Normal file
3
packages/renderer/assets/arrow-north.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.16623 1.03657L1.26729 5.5307C0.815851 5.94484 0.815851 6.63506 1.26729 7.0492C1.71873 7.46333 2.47113 7.46333 2.92256 7.0492L5.83183 4.39566L5.83183 14.2122C5.83183 14.8104 6.35015 15.2859 7.00223 15.2859C7.65431 15.2859 8.17262 14.8104 8.17262 14.2122L8.17262 4.39566L11.0652 7.0492C11.5166 7.46333 12.269 7.46333 12.7204 7.0492C12.9545 6.83446 13.0716 6.55837 13.0716 6.28228C13.0716 6.00619 12.9545 5.7301 12.7204 5.51536L7.83823 1.03657C7.62087 0.837168 7.31991 0.714461 7.00223 0.714461C6.68455 0.714461 6.38359 0.821829 6.16623 1.03657Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 681 B |
3
packages/renderer/assets/arrow-west.svg
Normal file
3
packages/renderer/assets/arrow-west.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.83401 14.9637L12.733 10.4695C13.1844 10.0554 13.1844 9.36518 12.733 8.95105C12.2815 8.53691 11.5291 8.53691 11.0777 8.95105L8.16841 11.6046L8.16841 1.78804C8.16841 1.18984 7.65009 0.714355 6.99802 0.714355C6.34594 0.714355 5.82762 1.18984 5.82762 1.78804L5.82762 11.6046L2.93507 8.95105C2.48363 8.53691 1.73124 8.53691 1.2798 8.95105C1.04572 9.16578 0.928677 9.44187 0.928677 9.71796C0.928677 9.99405 1.04572 10.2701 1.2798 10.4849L6.16202 14.9637C6.37938 15.1631 6.68034 15.2858 6.99801 15.2858C7.31569 15.2858 7.61665 15.1784 7.83401 14.9637Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 683 B |
11
packages/renderer/assets/monero.svg
Normal file
11
packages/renderer/assets/monero.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg id="monero-icon" width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2924_33103)">
|
||||
<path d="M15.9979 0C7.16414 0 -0.0112325 7.17316 1.32008e-05 15.9979C0.00222827 17.7635 0.283798 19.4618 0.812262 21.0505H5.5992V7.59219L15.9979 17.9897L26.3961 7.59219V21.0507H31.1841C31.7133 19.4622 31.9934 17.7638 31.9966 15.9982C32.0117 7.16422 24.8326 0.00212965 15.9979 0.00212965V0Z" fill="#111111"/>
|
||||
<path d="M13.6084 20.3801L9.0703 15.8423V24.3109H5.60074L2.32678 24.3115C5.13506 28.9184 10.2103 32 15.9998 32C21.7893 32 26.8648 28.9177 29.6736 24.3108H22.9283V15.8423L18.39 20.3801L15.9993 22.7705L13.6086 20.3801H13.6084Z" fill="#111111"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2924_33103">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 819 B |
@ -29,6 +29,7 @@ import {
|
||||
AddPaymentAccount,
|
||||
PaymentMethods,
|
||||
} from "@pages/Account";
|
||||
import { MyWallet } from "@pages/MyWallet";
|
||||
|
||||
export function AppRoutes() {
|
||||
return (
|
||||
@ -37,6 +38,14 @@ export function AppRoutes() {
|
||||
<Route path={ROUTES.Login} element={<Login />} />
|
||||
<Route path={ROUTES.Welcome} element={<Welcome />} />
|
||||
<Route path={ROUTES.CreateAccount} element={<CreateAccount />} />
|
||||
<Route
|
||||
path={ROUTES.MyWallet}
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<MyWallet />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.PaymentAccounts}
|
||||
element={
|
||||
|
@ -18,6 +18,7 @@ import type { FC } from "react";
|
||||
import { RecoilRoot } from "recoil";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
import { NotificationsProvider } from "@mantine/notifications";
|
||||
import { ModalsProvider } from "@atoms/Modal";
|
||||
import { QueryClientProvider } from "./QueryClientProvider";
|
||||
import { IntlProvider } from "./IntlProvider";
|
||||
import { ThemeProvider } from "./ThemeProvider";
|
||||
@ -28,7 +29,9 @@ export const AppProviders: FC = ({ children }) => (
|
||||
<IntlProvider>
|
||||
<QueryClientProvider>
|
||||
<ThemeProvider>
|
||||
<NotificationsProvider>{children}</NotificationsProvider>
|
||||
<ModalsProvider>
|
||||
<NotificationsProvider>{children}</NotificationsProvider>
|
||||
</ModalsProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</IntlProvider>
|
||||
|
@ -0,0 +1,46 @@
|
||||
// =============================================================================
|
||||
// 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 { Stack } from "@mantine/core";
|
||||
import { CircleIcon } from "./CircleIcon";
|
||||
import { ReactComponent as ArrowNorth } from "@assets/arrow-north.svg";
|
||||
import { ReactComponent as ArrowWest } from "@assets/arrow-west.svg";
|
||||
|
||||
export default {
|
||||
title: "atoms/CircleIcon",
|
||||
component: CircleIcon,
|
||||
} as ComponentMeta<typeof CircleIcon>;
|
||||
|
||||
const Template: ComponentStory<typeof CircleIcon> = ({ ...props }) => {
|
||||
return (
|
||||
<Stack>
|
||||
<CircleIcon {...props}>
|
||||
<ArrowNorth />
|
||||
</CircleIcon>
|
||||
|
||||
<CircleIcon {...props}>
|
||||
<ArrowWest />
|
||||
</CircleIcon>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
color: "#75B377",
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
// =============================================================================
|
||||
// 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 } from "@testing-library/react";
|
||||
import { CircleIcon } from "./CircleIcon";
|
||||
import { ReactComponent as ArrowNorth } from "@assets/arrow-north.svg";
|
||||
|
||||
describe("atoms::CircleIcon", () => {
|
||||
it("renders without exploding", () => {
|
||||
const { asFragment } = render(
|
||||
<CircleIcon color="#0B65DA">
|
||||
<ArrowNorth />
|
||||
</CircleIcon>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,65 @@
|
||||
// =============================================================================
|
||||
// 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 { DefaultProps } from "@mantine/core";
|
||||
import { Box, createStyles } from "@mantine/core";
|
||||
|
||||
export interface CircleIconProps extends DefaultProps {
|
||||
color?: string;
|
||||
size?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
type CircleIconStyle = Pick<CircleIconProps, "color" | "size">;
|
||||
|
||||
export function CircleIcon({
|
||||
children,
|
||||
classNames,
|
||||
className,
|
||||
color,
|
||||
size,
|
||||
...otherProps
|
||||
}: CircleIconProps) {
|
||||
const { classes, cx } = useStyles(
|
||||
{ color, size },
|
||||
{
|
||||
name: "CircleIcon",
|
||||
classNames,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Box className={cx(classes.root, className)} {...otherProps}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createStyles((theme, { color, size }: CircleIconStyle) => ({
|
||||
root: {
|
||||
borderRadius: "50%",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
||||
color: color || theme.colors.gray[9],
|
||||
display: "flex",
|
||||
height: size || 34,
|
||||
lineHeight: 1,
|
||||
width: size || 34,
|
||||
|
||||
svg: {
|
||||
margin: "auto",
|
||||
},
|
||||
},
|
||||
}));
|
@ -0,0 +1,22 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`atoms::CircleIcon > renders without exploding 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mantine-CircleIcon-root mantine-hg5ly7"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 14 16"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.16623 1.03657L1.26729 5.5307C0.815851 5.94484 0.815851 6.63506 1.26729 7.0492C1.71873 7.46333 2.47113 7.46333 2.92256 7.0492L5.83183 4.39566L5.83183 14.2122C5.83183 14.8104 6.35015 15.2859 7.00223 15.2859C7.65431 15.2859 8.17262 14.8104 8.17262 14.2122L8.17262 4.39566L11.0652 7.0492C11.5166 7.46333 12.269 7.46333 12.7204 7.0492C12.9545 6.83446 13.0716 6.55837 13.0716 6.28228C13.0716 6.00619 12.9545 5.7301 12.7204 5.51536L7.83823 1.03657C7.62087 0.837168 7.31991 0.714461 7.00223 0.714461C6.68455 0.714461 6.38359 0.821829 6.16623 1.03657Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,40 @@
|
||||
// =============================================================================
|
||||
// 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 { Stack } from "@mantine/core";
|
||||
import type { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { DetailItem } from "./DetailItem";
|
||||
|
||||
export default {
|
||||
title: "atoms/DetailItem",
|
||||
component: DetailItem,
|
||||
} as ComponentMeta<typeof DetailItem>;
|
||||
|
||||
const Template: ComponentStory<typeof DetailItem> = ({ ...props }) => {
|
||||
return (
|
||||
<Stack spacing="xl">
|
||||
<DetailItem {...props} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
label: "RECEIPIENT ADDRESS",
|
||||
children: "a1b848fdf7fb77f1dae266331d23c522db267ced63566a6e35800421c988d9f1",
|
||||
textAlign: "left",
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
// =============================================================================
|
||||
// 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 { DetailItem } from "./DetailItem";
|
||||
|
||||
describe("atoms::DetailItem", () => {
|
||||
it("renders without exploding", () => {
|
||||
const { asFragment, unmount } = render(
|
||||
<DetailItem label="Label">Content</DetailItem>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders detail label.", () => {
|
||||
const { unmount } = render(<DetailItem label="Label">Content</DetailItem>);
|
||||
|
||||
expect(screen.queryByText("Label")).toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders detail content.", () => {
|
||||
const { unmount } = render(<DetailItem>Content here ...</DetailItem>);
|
||||
|
||||
expect(screen.queryByText("Content here ...")).toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
});
|
@ -0,0 +1,67 @@
|
||||
// =============================================================================
|
||||
// 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 { DefaultProps } from "@mantine/core";
|
||||
import { Stack, createStyles, Text } from "@mantine/core";
|
||||
import { BodyText } from "@atoms/Typography";
|
||||
export interface DetailItemProps extends DefaultProps {
|
||||
label?: string;
|
||||
textAlign?: "left" | "right";
|
||||
children: React.ReactNode | string;
|
||||
}
|
||||
|
||||
type DetailItemStyleProps = Pick<DetailItemProps, "textAlign">;
|
||||
|
||||
export function DetailItem({
|
||||
label,
|
||||
children,
|
||||
classNames,
|
||||
className,
|
||||
textAlign,
|
||||
...other
|
||||
}: DetailItemProps) {
|
||||
const { classes, cx } = useStyles(
|
||||
{ textAlign },
|
||||
{ name: "DetailItem", classNames }
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack spacing={0} className={cx(classes.root, className)} {...other}>
|
||||
{label && <Text className={classes.label}>{label}</Text>}
|
||||
<BodyText heavy className={classes.content}>
|
||||
{children}
|
||||
</BodyText>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createStyles(
|
||||
(theme, { textAlign }: DetailItemStyleProps) => ({
|
||||
root: {
|
||||
textAlign: textAlign || undefined,
|
||||
},
|
||||
label: {
|
||||
color: theme.colors.gray[6],
|
||||
fontSize: theme.fontSizes.sm,
|
||||
fontWeight: 600,
|
||||
letterSpacing: "0.075rem",
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
content: {
|
||||
fontWeight: 500,
|
||||
},
|
||||
})
|
||||
);
|
@ -0,0 +1,20 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`atoms::DetailItem > renders without exploding 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-DetailItem-root mantine-fpp2be"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-label mantine-1mkytp"
|
||||
>
|
||||
Label
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-content mantine-17mq3ni"
|
||||
>
|
||||
Content
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
17
packages/renderer/src/components/atoms/DetailItem/index.ts
Normal file
17
packages/renderer/src/components/atoms/DetailItem/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./DetailItem";
|
@ -0,0 +1,40 @@
|
||||
// =============================================================================
|
||||
// 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 { Stack } from "@mantine/core";
|
||||
import type { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { DetailItemCard } from "./DetailItemCard";
|
||||
|
||||
export default {
|
||||
title: "atoms/DetailItemCard",
|
||||
component: DetailItemCard,
|
||||
} as ComponentMeta<typeof DetailItemCard>;
|
||||
|
||||
const Template: ComponentStory<typeof DetailItemCard> = (props) => {
|
||||
return (
|
||||
<Stack>
|
||||
<DetailItemCard {...props} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
label: "Primary Label",
|
||||
children: "Content here",
|
||||
primary: false,
|
||||
};
|
@ -0,0 +1,62 @@
|
||||
// =============================================================================
|
||||
// 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 { createStyles, Group } from "@mantine/core";
|
||||
import type { DetailItemProps } from "@atoms/DetailItem";
|
||||
import { DetailItem } from "@atoms/DetailItem";
|
||||
|
||||
export interface DetailItemCardProps extends DetailItemProps {
|
||||
primary?: boolean;
|
||||
}
|
||||
|
||||
interface DetailItemCardStyleProps {
|
||||
primary?: boolean;
|
||||
}
|
||||
|
||||
const useStyles = createStyles(
|
||||
(theme, { primary }: DetailItemCardStyleProps) => ({
|
||||
root: {
|
||||
background: primary ? theme.colors.gray[2] : theme.white,
|
||||
borderRadius: theme.radius.md,
|
||||
border: `1px solid ${
|
||||
primary ? theme.colors.gray[2] : theme.colors.gray[3]
|
||||
}`,
|
||||
paddingTop: theme.spacing.sm,
|
||||
paddingBottom: theme.spacing.sm,
|
||||
paddingLeft: theme.spacing.md,
|
||||
paddingRight: theme.spacing.md,
|
||||
},
|
||||
detailRoot: {
|
||||
width: "100%",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
export function DetailItemCard({
|
||||
label,
|
||||
primary,
|
||||
children,
|
||||
}: DetailItemCardProps) {
|
||||
const { classes } = useStyles({ primary });
|
||||
|
||||
return (
|
||||
<Group className={classes.root}>
|
||||
<DetailItem label={label} classNames={{ root: classes.detailRoot }}>
|
||||
{children}
|
||||
</DetailItem>
|
||||
</Group>
|
||||
);
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
// =============================================================================
|
||||
// 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 { useState } from "react";
|
||||
import { Group, Text } from "@mantine/core";
|
||||
import type { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Modal } from "./Modal";
|
||||
import { Button } from "@atoms/Buttons";
|
||||
|
||||
export default {
|
||||
title: "atoms/Modal",
|
||||
component: Modal,
|
||||
} as ComponentMeta<typeof Modal>;
|
||||
|
||||
const Template: ComponentStory<typeof Modal> = ({ ...props }) => {
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
{...props}
|
||||
opened={opened}
|
||||
onClose={() => setOpened(false)}
|
||||
title="Funds are sent!"
|
||||
>
|
||||
<Text color="gray">
|
||||
You’ve sent 1.15 XMR to:
|
||||
44tgg3TkQ2jGRDabB5cjbNWDF7PKDBKqw2bsjgRRCQSThiE15ePWk6kJFH7YWnPKR88JQB8WwDX34TwfYnhWVeT1J1rC6b7
|
||||
</Text>
|
||||
<Group mt="lg">
|
||||
<Button>Go to Wallet</Button>
|
||||
</Group>
|
||||
</Modal>
|
||||
|
||||
<Group position="center">
|
||||
<Button onClick={() => setOpened(true)}>Open Modal</Button>
|
||||
</Group>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {};
|
55
packages/renderer/src/components/atoms/Modal/Modal.tsx
Normal file
55
packages/renderer/src/components/atoms/Modal/Modal.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
// =============================================================================
|
||||
// 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 { ModalProps as MModalProps } from "@mantine/core";
|
||||
import { createStyles, Modal as MModal } from "@mantine/core";
|
||||
import type { ModalsProviderProps } from "@mantine/modals";
|
||||
import { ModalsProvider as MModalsProvider } from "@mantine/modals";
|
||||
|
||||
const commonModalProps = {
|
||||
overlayOpacity: 0.25,
|
||||
padding: 25,
|
||||
};
|
||||
|
||||
export function Modal(props: MModalProps) {
|
||||
const style = useStyles();
|
||||
|
||||
return <MModal classNames={style.classes} {...commonModalProps} {...props} />;
|
||||
}
|
||||
|
||||
export function ModalsProvider({ ...props }: ModalsProviderProps) {
|
||||
const style = useStyles();
|
||||
|
||||
return (
|
||||
<MModalsProvider
|
||||
modalProps={{
|
||||
classNames: style.classes,
|
||||
...commonModalProps,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
modal: {
|
||||
borderRadius: theme.radius.xl / 1.6,
|
||||
},
|
||||
title: {
|
||||
fontSize: theme.fontSizes.xl,
|
||||
fontWeight: 600,
|
||||
},
|
||||
}));
|
17
packages/renderer/src/components/atoms/Modal/index.ts
Normal file
17
packages/renderer/src/components/atoms/Modal/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./Modal";
|
@ -9,7 +9,7 @@ exports[`atoms::Sync Status > renders the fully synced status 1`] = `
|
||||
class="mantine-Group-child mantine-16wvh2m"
|
||||
/>
|
||||
<div
|
||||
class="mantine-Text-root mantine-Group-child mantine-1mga3um"
|
||||
class="mantine-Text-root mantine-Group-child mantine-6ffw9j"
|
||||
>
|
||||
Fully Synced
|
||||
</div>
|
||||
@ -26,7 +26,7 @@ exports[`atoms::Sync Status > renders the not synced status by default 1`] = `
|
||||
class="mantine-Group-child mantine-1mmj0t1"
|
||||
/>
|
||||
<div
|
||||
class="mantine-Text-root mantine-Group-child mantine-1mga3um"
|
||||
class="mantine-Text-root mantine-Group-child mantine-6ffw9j"
|
||||
>
|
||||
Not Synced
|
||||
</div>
|
||||
@ -43,7 +43,7 @@ exports[`atoms::Sync Status > renders the sync in progress status 1`] = `
|
||||
class="mantine-Group-child mantine-6c6x3l"
|
||||
/>
|
||||
<div
|
||||
class="mantine-Text-root mantine-Group-child mantine-1mga3um"
|
||||
class="mantine-Text-root mantine-Group-child mantine-6ffw9j"
|
||||
>
|
||||
Syncing
|
||||
</div>
|
||||
|
37
packages/renderer/src/components/atoms/Tabs/Tabs.stories.tsx
Normal file
37
packages/renderer/src/components/atoms/Tabs/Tabs.stories.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
// =============================================================================
|
||||
// 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 { Tabs } from "./Tabs";
|
||||
|
||||
export default {
|
||||
title: "atoms/Tabs",
|
||||
component: Tabs,
|
||||
} as ComponentMeta<typeof Tabs>;
|
||||
|
||||
const Template: ComponentStory<typeof Tabs> = () => {
|
||||
return (
|
||||
<Tabs>
|
||||
<Tabs.Tab label="Gallery">Gallery tab content</Tabs.Tab>
|
||||
<Tabs.Tab label="Messages">Messages tab content</Tabs.Tab>
|
||||
<Tabs.Tab label="Settings">Settings tab content</Tabs.Tab>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {};
|
60
packages/renderer/src/components/atoms/Tabs/Tabs.tsx
Normal file
60
packages/renderer/src/components/atoms/Tabs/Tabs.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
// =============================================================================
|
||||
// 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 { TabsProps as MTabsProps } from "@mantine/core";
|
||||
import { Tabs as MTabs, Tab as MTab, createStyles } from "@mantine/core";
|
||||
|
||||
export function Tabs(props: MTabsProps) {
|
||||
const style = useStyles();
|
||||
|
||||
return <MTabs classNames={style.classes} variant="unstyled" {...props} />;
|
||||
}
|
||||
|
||||
Tabs.Tab = MTab;
|
||||
|
||||
const useStyles = createStyles((theme, _params, getRef) => {
|
||||
const tabActiveRef = getRef("tabActive");
|
||||
|
||||
return {
|
||||
tabsList: {
|
||||
borderBottom: `2px solid ${theme.colors.gray[3]}`,
|
||||
},
|
||||
tabControl: {
|
||||
borderBottom: "2px solid transparent",
|
||||
color: theme.colors.gray[6],
|
||||
fontWeight: 700,
|
||||
height: 32,
|
||||
letterSpacing: 0.8,
|
||||
marginBottom: -2,
|
||||
marginLeft: theme.spacing.md,
|
||||
marginRight: theme.spacing.md,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
textTransform: "uppercase",
|
||||
|
||||
"&:first-child": {
|
||||
marginLeft: 0,
|
||||
},
|
||||
"&:last-child": {
|
||||
marginRight: 0,
|
||||
},
|
||||
[`&.${tabActiveRef}`]: {
|
||||
borderBottomColor: theme.primaryColor,
|
||||
color: theme.colors.dark[9],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
17
packages/renderer/src/components/atoms/Tabs/index.ts
Normal file
17
packages/renderer/src/components/atoms/Tabs/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./Tabs";
|
@ -36,11 +36,14 @@ export function BodyText<TComponent = "p">(props: BodyTextProps<TComponent>) {
|
||||
return (
|
||||
<MText
|
||||
{...rest}
|
||||
className={cx(className, {
|
||||
[classes.body]: !size || size === "md",
|
||||
[classes.bodyLg]: size === "lg",
|
||||
[classes.bodyHeavy]: Boolean(heavy),
|
||||
})}
|
||||
className={cx(
|
||||
{
|
||||
[classes.body]: !size || size === "md",
|
||||
[classes.bodyLg]: size === "lg",
|
||||
[classes.bodyHeavy]: Boolean(heavy),
|
||||
},
|
||||
className
|
||||
)}
|
||||
size={size}
|
||||
>
|
||||
{stringId ? (
|
||||
|
@ -0,0 +1,50 @@
|
||||
// =============================================================================
|
||||
// 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 { Stack } from "@mantine/core";
|
||||
import { AddressCard } from "./AddressCard";
|
||||
|
||||
export default {
|
||||
title: "molecules/AddressCard",
|
||||
component: AddressCard,
|
||||
} as ComponentMeta<typeof AddressCard>;
|
||||
|
||||
const Template: ComponentStory<typeof AddressCard> = (args) => {
|
||||
return (
|
||||
<Stack>
|
||||
<AddressCard
|
||||
{...args}
|
||||
primary={true}
|
||||
qrModalProps={{ target: "#root" }}
|
||||
/>
|
||||
<AddressCard
|
||||
{...args}
|
||||
primary={false}
|
||||
qrModalProps={{ target: "#root" }}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
label: "Primary Address",
|
||||
primary: true,
|
||||
address:
|
||||
"44tgg3TkQ2jGRDabB5cjbNWDF7PKDBKqw2bsjgRRCQSThiE15ePWk6kJFH7YWnPKR88JQB8WwDX34TwfYnhWVeT1J1rC6b7",
|
||||
};
|
@ -0,0 +1,52 @@
|
||||
// =============================================================================
|
||||
// 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 { AddressCard } from "./AddressCard";
|
||||
import { AppProviders } from "@atoms/AppProviders";
|
||||
|
||||
describe("molecules::AddressCard", () => {
|
||||
it("renders without exploding", () => {
|
||||
const { asFragment, unmount } = render(
|
||||
<AppProviders>
|
||||
<AddressCard address="Address here" label="Address label" />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renderes the address hash code.", () => {
|
||||
const { unmount } = render(
|
||||
<AppProviders>
|
||||
<AddressCard address="ASDASDQWEKOOQLMWEM" />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(screen.queryByText("ASDASDQWEKOOQLMWEM")).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renderes the address label.", () => {
|
||||
const { unmount } = render(
|
||||
<AppProviders>
|
||||
<AddressCard label="Primary Address" address="ASDASDQWEKOOQLMWEM" />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(screen.queryByText("Primary Address")).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
});
|
@ -0,0 +1,207 @@
|
||||
// =============================================================================
|
||||
// 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 { FormattedMessage, useIntl } from "react-intl";
|
||||
import QRCode from "react-qr-code";
|
||||
import type { OpenConfirmModal } from "@mantine/modals/lib/context";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { useClipboard } from "@mantine/hooks";
|
||||
import {
|
||||
Anchor,
|
||||
Box,
|
||||
createStyles,
|
||||
Group,
|
||||
SimpleGrid,
|
||||
Skeleton,
|
||||
} from "@mantine/core";
|
||||
import { DetailItem } from "@atoms/DetailItem";
|
||||
import { Button } from "@atoms/Buttons";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { DetailItemCard } from "@atoms/DetailItemCard/DetailItemCard";
|
||||
|
||||
interface AddressCardProps {
|
||||
label?: string;
|
||||
address: string;
|
||||
primary?: boolean;
|
||||
qrModalProps?: OpenConfirmModal;
|
||||
}
|
||||
|
||||
const COPY_TEXT_TIMEOUT = 500;
|
||||
|
||||
export function AddressCard({
|
||||
label,
|
||||
address,
|
||||
primary = false,
|
||||
qrModalProps,
|
||||
}: AddressCardProps) {
|
||||
const modals = useModals();
|
||||
const { classes } = useStyles();
|
||||
|
||||
const clipboard = useClipboard({ timeout: COPY_TEXT_TIMEOUT });
|
||||
|
||||
const handleCopyClick = () => {
|
||||
clipboard.copy(address);
|
||||
};
|
||||
const handleQRClick = () => {
|
||||
const modalId = modals.openModal({
|
||||
children: (
|
||||
<AddressCardQRModalContent
|
||||
address={address}
|
||||
onReturnClick={() => modals.closeModal(modalId)}
|
||||
/>
|
||||
),
|
||||
labels: { confirm: "Confirm", cancel: "Cancel" },
|
||||
size: "lg",
|
||||
withCloseButton: false,
|
||||
...qrModalProps,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DetailItemCard label={label} primary={primary}>
|
||||
<Group noWrap className={classes.contentGroup}>
|
||||
<Box className={classes.address}>{address}</Box>
|
||||
|
||||
<Group noWrap className={classes.addressBtns}>
|
||||
<Anchor onClick={handleCopyClick} underline>
|
||||
{!clipboard.copied ? (
|
||||
<FormattedMessage
|
||||
id={LangKeys.AddressCardCopyBtn}
|
||||
defaultMessage="Copy"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id={LangKeys.AddressCardCopiedBtn}
|
||||
defaultMessage="Copied"
|
||||
/>
|
||||
)}
|
||||
</Anchor>
|
||||
|
||||
<Anchor onClick={handleQRClick} underline>
|
||||
<FormattedMessage
|
||||
id={LangKeys.AddressCardQRBtn}
|
||||
defaultMessage="QR"
|
||||
/>
|
||||
</Anchor>
|
||||
</Group>
|
||||
</Group>
|
||||
</DetailItemCard>
|
||||
);
|
||||
}
|
||||
|
||||
type AddressCardSkeletonProps = Pick<AddressCardProps, "label" | "primary">;
|
||||
|
||||
export function AddressCardSkeleton({
|
||||
label,
|
||||
primary,
|
||||
}: AddressCardSkeletonProps) {
|
||||
const { classes } = useStyles();
|
||||
|
||||
return (
|
||||
<DetailItemCard label={label} primary={primary}>
|
||||
<Box className={classes.address}>
|
||||
<Skeleton
|
||||
width="80%"
|
||||
height={8}
|
||||
mt="xs"
|
||||
sx={(theme) => ({
|
||||
"&:before": {
|
||||
backgroundColor: primary
|
||||
? theme.colors.gray[1]
|
||||
: theme.colors.gray[0],
|
||||
},
|
||||
"&:after": {
|
||||
backgroundColor: primary
|
||||
? theme.colors.gray[4]
|
||||
: theme.colors.gray[3],
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
</DetailItemCard>
|
||||
);
|
||||
}
|
||||
|
||||
interface AddressCardQRModalContentProps {
|
||||
address: string;
|
||||
onQRDownloadClick?: () => void;
|
||||
onReturnClick?: () => void;
|
||||
}
|
||||
|
||||
function AddressCardQRModalContent({
|
||||
address,
|
||||
onQRDownloadClick,
|
||||
onReturnClick,
|
||||
}: AddressCardQRModalContentProps) {
|
||||
const { classes } = useStyles();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<DetailItem
|
||||
classNames={{ content: classes.qrModalAddress }}
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletQRModalPrimaryAddress,
|
||||
defaultMessage: "Primary Address",
|
||||
})}
|
||||
>
|
||||
{address}
|
||||
</DetailItem>
|
||||
|
||||
<Box className={classes.qrRoot}>
|
||||
<QRCode value={address} size={370} />
|
||||
</Box>
|
||||
|
||||
<SimpleGrid cols={2} mt="xl">
|
||||
<Button flavor="neutral" onClick={onReturnClick}>
|
||||
<FormattedMessage
|
||||
id={LangKeys.MyWalletQRModalReturnBtn}
|
||||
defaultMessage="Return"
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button onClick={onQRDownloadClick}>
|
||||
<FormattedMessage
|
||||
id={LangKeys.MyWalletQRModalDownloadQRBtn}
|
||||
defaultMessage="Download QR"
|
||||
/>
|
||||
</Button>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
contentGroup: {
|
||||
minWidth: "100%",
|
||||
},
|
||||
address: {
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
width: "100%",
|
||||
},
|
||||
addressBtns: {
|
||||
marginLeft: "auto",
|
||||
},
|
||||
qrRoot: {
|
||||
marginTop: theme.spacing.xl,
|
||||
textAlign: "center",
|
||||
},
|
||||
qrModalAddress: {
|
||||
fontSize: theme.fontSizes.lg,
|
||||
},
|
||||
}));
|
@ -0,0 +1,40 @@
|
||||
// =============================================================================
|
||||
// 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 { Stack } from "@mantine/core";
|
||||
import { AddressCardSkeleton } from "./AddressCard";
|
||||
|
||||
export default {
|
||||
title: "molecules/AddressCardSkeleton",
|
||||
component: AddressCardSkeleton,
|
||||
} as ComponentMeta<typeof AddressCardSkeleton>;
|
||||
|
||||
const Template: ComponentStory<typeof AddressCardSkeleton> = (args) => {
|
||||
return (
|
||||
<Stack>
|
||||
<AddressCardSkeleton {...args} primary={false} />
|
||||
<AddressCardSkeleton {...args} primary={true} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
label: "Primary Address",
|
||||
primary: true,
|
||||
};
|
@ -0,0 +1,62 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`molecules::AddressCard > renderes the address hash code. 1`] = `
|
||||
<div
|
||||
class="mantine-Group-child mantine-2w2ufd"
|
||||
>
|
||||
ASDASDQWEKOOQLMWEM
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`molecules::AddressCard > renderes the address label. 1`] = `
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
|
||||
>
|
||||
Primary Address
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`molecules::AddressCard > renders without exploding 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mantine-Group-root mantine-chiw4t"
|
||||
>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-1a4fzbd"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
|
||||
>
|
||||
Address label
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"
|
||||
>
|
||||
<div
|
||||
class="mantine-Group-root mantine-1i932k4"
|
||||
>
|
||||
<div
|
||||
class="mantine-Group-child mantine-2w2ufd"
|
||||
>
|
||||
Address here
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Group-root mantine-Group-child mantine-ze53qg"
|
||||
>
|
||||
<a
|
||||
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-16zvpw1"
|
||||
>
|
||||
Copy
|
||||
</a>
|
||||
<a
|
||||
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-16zvpw1"
|
||||
>
|
||||
QR
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./AddressCard";
|
@ -0,0 +1,123 @@
|
||||
// =============================================================================
|
||||
// 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 { createTable } from "@tanstack/react-table";
|
||||
import type { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Table } from "./Table";
|
||||
|
||||
export default {
|
||||
title: "atoms/Table",
|
||||
component: Table,
|
||||
} as ComponentMeta<typeof Table>;
|
||||
|
||||
const Template: ComponentStory<typeof Table> = () => {
|
||||
return (
|
||||
<Table
|
||||
table={table}
|
||||
data={data}
|
||||
columns={columns}
|
||||
rowSubComponent={() => "asdasd"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {};
|
||||
|
||||
interface Person {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
age: number;
|
||||
visits: number;
|
||||
status: string;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
const table = createTable().setRowType<Person>();
|
||||
|
||||
const columns = [
|
||||
table.createGroup({
|
||||
header: "Name",
|
||||
footer: (props) => props.column.id,
|
||||
columns: [
|
||||
table.createDataColumn("firstName", {
|
||||
cell: (info) => info.getValue(),
|
||||
footer: (props) => props.column.id,
|
||||
}),
|
||||
table.createDataColumn((row) => row.lastName, {
|
||||
id: "lastName",
|
||||
cell: (info) => info.getValue(),
|
||||
header: () => <span>Last Name</span>,
|
||||
footer: (props) => props.column.id,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
table.createGroup({
|
||||
header: "Info",
|
||||
footer: (props) => props.column.id,
|
||||
columns: [
|
||||
table.createDataColumn("age", {
|
||||
header: () => "Age",
|
||||
footer: (props) => props.column.id,
|
||||
}),
|
||||
table.createGroup({
|
||||
header: "More Info",
|
||||
columns: [
|
||||
table.createDataColumn("visits", {
|
||||
header: () => <span>Visits</span>,
|
||||
footer: (props) => props.column.id,
|
||||
}),
|
||||
table.createDataColumn("status", {
|
||||
header: "Status",
|
||||
footer: (props) => props.column.id,
|
||||
}),
|
||||
table.createDataColumn("progress", {
|
||||
header: "Profile Progress",
|
||||
footer: (props) => props.column.id,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const data: Array<Person> = [
|
||||
{
|
||||
firstName: "tanner",
|
||||
lastName: "linsley",
|
||||
age: 24,
|
||||
visits: 100,
|
||||
status: "In Relationship",
|
||||
progress: 50,
|
||||
},
|
||||
{
|
||||
firstName: "tandy",
|
||||
lastName: "miller",
|
||||
age: 40,
|
||||
visits: 40,
|
||||
status: "Single",
|
||||
progress: 80,
|
||||
},
|
||||
{
|
||||
firstName: "joe",
|
||||
lastName: "dirte",
|
||||
age: 45,
|
||||
visits: 20,
|
||||
status: "Complicated",
|
||||
progress: 10,
|
||||
},
|
||||
];
|
@ -0,0 +1,99 @@
|
||||
// =============================================================================
|
||||
// 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 { createTable } from "@tanstack/react-table";
|
||||
import { Table } from "./Table";
|
||||
|
||||
describe("molecules::Table", () => {
|
||||
it("renders without exploding.", () => {
|
||||
const { asFragment, unmount } = render(
|
||||
<Table table={table} data={data} columns={columns} />
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders all columns.", () => {
|
||||
const { unmount } = render(
|
||||
<Table table={table} data={data} columns={columns} />
|
||||
);
|
||||
expect(screen.queryByText("Name")).toBeInTheDocument();
|
||||
expect(screen.queryByText("First Name")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Last Name")).toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("shouldn't render columns if `showHeader` was true.", () => {
|
||||
const { unmount } = render(
|
||||
<Table table={table} data={data} columns={columns} showHeader={false} />
|
||||
);
|
||||
expect(screen.queryByText("Name")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("First Name")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Last Name")).not.toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders all columns.", () => {
|
||||
const { unmount } = render(
|
||||
<Table table={table} data={data} columns={columns} />
|
||||
);
|
||||
expect(screen.queryByText("Ahmed")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Subir")).toBeInTheDocument();
|
||||
expect(screen.queryByText("In Relationship")).not.toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
type Person = {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
age: number;
|
||||
visits: number;
|
||||
status: string;
|
||||
progress: number;
|
||||
};
|
||||
const table = createTable().setRowType<Person>();
|
||||
|
||||
const data: Array<Person> = [
|
||||
{
|
||||
firstName: "Ahmed",
|
||||
lastName: "Subir",
|
||||
age: 24,
|
||||
visits: 100,
|
||||
status: "In Relationship",
|
||||
progress: 50,
|
||||
},
|
||||
];
|
||||
|
||||
const columns = [
|
||||
table.createGroup({
|
||||
header: "Name",
|
||||
footer: (props) => props.column.id,
|
||||
columns: [
|
||||
table.createDataColumn("firstName", {
|
||||
header: "First Name",
|
||||
footer: (props) => props.column.id,
|
||||
}),
|
||||
table.createDataColumn("lastName", {
|
||||
id: "lastName",
|
||||
header: "Last Name",
|
||||
footer: (props) => props.column.id,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
51
packages/renderer/src/components/molecules/Table/Table.tsx
Normal file
51
packages/renderer/src/components/molecules/Table/Table.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
// =============================================================================
|
||||
// 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 {
|
||||
useTableInstance,
|
||||
getCoreRowModel,
|
||||
getExpandedRowModel,
|
||||
} from "@tanstack/react-table";
|
||||
import { Table as MTable } from "@mantine/core";
|
||||
import type { TableProps } from "./_types";
|
||||
import { TableProvider } from "./use-table-context";
|
||||
import { TableHeader } from "./TableHeader";
|
||||
import { TableBody } from "./TableBody";
|
||||
|
||||
export function Table(props: TableProps) {
|
||||
const { table, columns, data, tableWrap } = props;
|
||||
|
||||
const tableInstance = useTableInstance(table, {
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getExpandedRowModel: getExpandedRowModel(),
|
||||
});
|
||||
|
||||
return (
|
||||
<MTable {...tableWrap}>
|
||||
<TableProvider value={{ table: tableInstance, props }}>
|
||||
<TableHeader />
|
||||
<TableBody />
|
||||
</TableProvider>
|
||||
</MTable>
|
||||
);
|
||||
}
|
||||
|
||||
Table.defaultProps = {
|
||||
showHeader: true,
|
||||
showFooter: true,
|
||||
};
|
@ -0,0 +1,58 @@
|
||||
// =============================================================================
|
||||
// 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 { Fragment } from "react";
|
||||
import { useTableContext } from "./use-table-context";
|
||||
|
||||
export function TableBody() {
|
||||
const {
|
||||
table,
|
||||
props: { rowSubComponent },
|
||||
} = useTableContext();
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<Fragment key={row.id}>
|
||||
<tr
|
||||
key={row.id}
|
||||
onClick={() => {
|
||||
row.toggleExpanded();
|
||||
}}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
style={{
|
||||
width: cell.column.getSize(),
|
||||
}}
|
||||
>
|
||||
{cell.renderCell()}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
{row.getIsExpanded() && rowSubComponent ? (
|
||||
<tr key={`${row.id}-subcomponent`}>
|
||||
<td colSpan={row.getVisibleCells()?.length}>
|
||||
{rowSubComponent({ row })}
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
);
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
// =============================================================================
|
||||
// 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 { useTableContext } from "./use-table-context";
|
||||
|
||||
export function TableFooter() {
|
||||
const { table } = useTableContext();
|
||||
|
||||
return (
|
||||
<tfoot>
|
||||
{table.getFooterGroups().map((footerGroup) => (
|
||||
<tr key={footerGroup.id}>
|
||||
{footerGroup.headers.map((header) => (
|
||||
<th key={header.id} colSpan={header.colSpan}>
|
||||
{header.isPlaceholder ? null : header.renderFooter()}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tfoot>
|
||||
);
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
// =============================================================================
|
||||
// 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 { useTableContext } from "./use-table-context";
|
||||
|
||||
export function TableHeader() {
|
||||
const {
|
||||
table,
|
||||
props: { showHeader },
|
||||
} = useTableContext();
|
||||
|
||||
if (!showHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th
|
||||
key={header.id}
|
||||
colSpan={header.colSpan}
|
||||
style={{
|
||||
width: header.getSize(),
|
||||
}}
|
||||
>
|
||||
{header.isPlaceholder ? null : header.renderHeader()}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
);
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`molecules::Table > renders without exploding. 1`] = `
|
||||
<DocumentFragment>
|
||||
<table
|
||||
class="mantine-Table-root mantine-1uvqbeb"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
colspan="2"
|
||||
style="width: 300px;"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
colspan="1"
|
||||
style="width: 150px;"
|
||||
>
|
||||
First Name
|
||||
</th>
|
||||
<th
|
||||
colspan="1"
|
||||
style="width: 150px;"
|
||||
>
|
||||
Last Name
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
style="width: 150px;"
|
||||
>
|
||||
Ahmed
|
||||
</td>
|
||||
<td
|
||||
style="width: 150px;"
|
||||
>
|
||||
Subir
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
32
packages/renderer/src/components/molecules/Table/_types.tsx
Normal file
32
packages/renderer/src/components/molecules/Table/_types.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
// =============================================================================
|
||||
// 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.
|
||||
// =============================================================================
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { ColumnDef, Row } from "@tanstack/react-table";
|
||||
import type { TableProps as MTableProps } from "@mantine/core";
|
||||
|
||||
export interface TableProps {
|
||||
columns: Array<ColumnDef<any>>;
|
||||
table: any;
|
||||
data: Array<any>;
|
||||
|
||||
showHeader?: boolean;
|
||||
showFooter?: boolean;
|
||||
|
||||
rowSubComponent?: ({ row }: { row: Row<any> }) => React.ReactNode;
|
||||
|
||||
tableWrap?: MTableProps;
|
||||
}
|
17
packages/renderer/src/components/molecules/Table/index.ts
Normal file
17
packages/renderer/src/components/molecules/Table/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./Table";
|
@ -0,0 +1,43 @@
|
||||
// =============================================================================
|
||||
// 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.
|
||||
// =============================================================================
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { createContext, useContext } from "react";
|
||||
import type { TableInstance } from "@tanstack/react-table";
|
||||
import type { TableProps } from "../_types";
|
||||
|
||||
interface TableContextValue {
|
||||
table: TableInstance<any>;
|
||||
props: TableProps;
|
||||
}
|
||||
|
||||
interface TableProviderProps {
|
||||
value: TableContextValue;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const TableContext = createContext<TableContextValue>(
|
||||
{} as TableContextValue
|
||||
);
|
||||
|
||||
export function TableProvider({ children, value }: TableProviderProps) {
|
||||
return (
|
||||
<TableContext.Provider value={value}>{children}</TableContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useTableContext = () =>
|
||||
useContext<TableContextValue>(TableContext);
|
@ -21,14 +21,14 @@ import { WalletBalance } from ".";
|
||||
|
||||
describe("molecules::WalletBalance", () => {
|
||||
beforeAll(() => {
|
||||
vi.mock("@hooks/haveno/useHavenoClient", () => ({
|
||||
useHavenoClient: () => ({
|
||||
getBalances: async () => {
|
||||
return {
|
||||
getLockedBalance: () => 12,
|
||||
getReservedTradeBalance: () => 14,
|
||||
getBalance: () => 15,
|
||||
};
|
||||
vi.mock("@hooks/haveno/useBalances", () => ({
|
||||
useBalances: () => ({
|
||||
isLoading: false,
|
||||
isSuccess: true,
|
||||
data: {
|
||||
lockedBalance: 12,
|
||||
reservedTradeBalance: 14,
|
||||
balance: 15,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
@ -53,16 +53,17 @@ describe("molecules::WalletBalance", () => {
|
||||
});
|
||||
|
||||
it("renders loading state", () => {
|
||||
const { asFragment } = render(
|
||||
const { asFragment, unmount } = render(
|
||||
<AppProviders>
|
||||
<WalletBalance />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders after loading data", async () => {
|
||||
const { asFragment, queryByText } = render(
|
||||
const { asFragment, queryByText, unmount } = render(
|
||||
<AppProviders>
|
||||
<WalletBalance />
|
||||
</AppProviders>
|
||||
@ -71,5 +72,6 @@ describe("molecules::WalletBalance", () => {
|
||||
await waitForElementToBeRemoved(() => queryByText("Loading..."));
|
||||
}
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
@ -34,18 +34,15 @@ import { BodyText } from "@atoms/Typography";
|
||||
export function WalletBalance() {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const { classes } = useStyles({ isOpen });
|
||||
const { data: availableBalances, isLoading: isLoadingBalance } =
|
||||
useBalances();
|
||||
|
||||
const { data: balances, isLoading: isLoadingBalance } = useBalances();
|
||||
const { data: accountInfo, isLoading: isLoadingAccountInfo } =
|
||||
useAccountInfo();
|
||||
const { data: price } = usePrice(accountInfo?.primaryFiat);
|
||||
|
||||
const totalBalance = useMemo(() => {
|
||||
return (
|
||||
Number(availableBalances?.getLockedBalance() || 0) +
|
||||
Number(availableBalances?.getReservedTradeBalance() || 0)
|
||||
);
|
||||
}, [availableBalances]);
|
||||
return balances?.balance || 0 + (balances?.reservedTradeBalance || 0);
|
||||
}, [balances]);
|
||||
|
||||
const fiatBalance = useMemo(() => {
|
||||
if (!totalBalance || !price || !accountInfo?.primaryFiat) {
|
||||
@ -76,9 +73,7 @@ export function WalletBalance() {
|
||||
<Stack spacing={4}>
|
||||
<Group>
|
||||
<Text className={classes.xmr}>
|
||||
<Currency
|
||||
value={Number(availableBalances?.getBalance() ?? 0)}
|
||||
/>
|
||||
<Currency value={Number(balances?.balance || 0)} />
|
||||
</Text>
|
||||
<ArrowDown className={classes.toggleIcon} />
|
||||
</Group>
|
||||
@ -102,19 +97,13 @@ export function WalletBalance() {
|
||||
<Stack spacing={4}>
|
||||
<Text className={classes.balanceLabel}>Reserved</Text>
|
||||
<Text className={classes.balanceValue}>
|
||||
<Currency
|
||||
value={Number(
|
||||
availableBalances?.getReservedTradeBalance() ?? 0
|
||||
)}
|
||||
/>
|
||||
<Currency value={balances?.reservedTradeBalance || 0} />
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack spacing={4}>
|
||||
<Text className={classes.balanceLabel}>Locked</Text>
|
||||
<Text className={classes.balanceValue}>
|
||||
<Currency
|
||||
value={Number(availableBalances?.getLockedBalance() ?? 0)}
|
||||
/>
|
||||
<Currency value={balances?.lockedBalance || 0} />
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
@ -81,7 +81,7 @@ exports[`molecules::WalletBalance > renders after loading data 1`] = `
|
||||
<div
|
||||
class="mantine-Text-root mantine-1pfxwhx"
|
||||
>
|
||||
(USD 7,800.00)
|
||||
(USD 4,500.00)
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@ -106,7 +106,7 @@ exports[`molecules::WalletBalance > renders after loading data 1`] = `
|
||||
<div
|
||||
class="mantine-Text-root mantine-14d5cdm"
|
||||
>
|
||||
26.00
|
||||
15.00
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@ -197,12 +197,92 @@ exports[`molecules::WalletBalance > renders loading state 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-lfk3cq"
|
||||
class="mantine-Stack-root mantine-1kb6t4k"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-1152338"
|
||||
class="mantine-Group-root mantine-6y1794"
|
||||
>
|
||||
Loading...
|
||||
<div
|
||||
class="mantine-Text-root mantine-Group-child mantine-q5labh"
|
||||
>
|
||||
15.00
|
||||
</div>
|
||||
<svg
|
||||
class="mantine-Group-child mantine-qg5oag"
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 7 4"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M2.91916 3.69319C3.23963 4.04927 3.76166 4.04657 4.07971 3.69319L6.82329 0.644746C7.14377 0.288661 7.01636 0 6.53811 0H0.460749C-0.0172283 0 -0.14248 0.291357 0.17557 0.644746L2.91916 3.69319Z"
|
||||
fill="#111111"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-1pfxwhx"
|
||||
>
|
||||
(USD 4,500.00)
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="mantine-1avyp1d"
|
||||
style="box-sizing: border-box; display: none; height: 0px; overflow: hidden;"
|
||||
>
|
||||
<div
|
||||
style="opacity: 0; transition: opacity 200ms ease;"
|
||||
>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-lfk3cq"
|
||||
>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-1kb6t4k"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-kaqdcf"
|
||||
>
|
||||
Total
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-14d5cdm"
|
||||
>
|
||||
15.00
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-1kb6t4k"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-kaqdcf"
|
||||
>
|
||||
Reserved
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-14d5cdm"
|
||||
>
|
||||
14.00
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-1kb6t4k"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-kaqdcf"
|
||||
>
|
||||
Locked
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-14d5cdm"
|
||||
>
|
||||
12.00
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,90 @@
|
||||
// =============================================================================
|
||||
// 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 { WalletTransactions } from "./WalletTransactions";
|
||||
import type { TWalletTransaction } from "./_types";
|
||||
import { WalletTransactionType } from "./_types";
|
||||
|
||||
export default {
|
||||
title: "organisms/WalletTransactions",
|
||||
component: WalletTransactions,
|
||||
} as ComponentMeta<typeof WalletTransactions>;
|
||||
|
||||
const Template: ComponentStory<typeof WalletTransactions> = () => {
|
||||
return <WalletTransactions data={data} />;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {};
|
||||
|
||||
const data = [
|
||||
{
|
||||
type: WalletTransactionType.Sent,
|
||||
timestamp: Date.now(),
|
||||
amount: 17.275849365201,
|
||||
foreignAmount: 4108.5,
|
||||
amountCurrency: "XMR",
|
||||
foreignAmountCurrency: "EUR",
|
||||
transactionId:
|
||||
"a1b848fdf7fb77f1dae266331d23c522db267ced63566a6e35800421c988d9f1",
|
||||
incomingAddresses: [
|
||||
"7631e90afdb723b1a798b39bfc5ec942i5d0e155dfa993f536827c7f9699740a",
|
||||
],
|
||||
destinationAddresses: [
|
||||
"7631e90afdb723b1a798b39bfc5ec942i5d0e155dfa993f536827c7f9699740a",
|
||||
],
|
||||
height: 2482937,
|
||||
fee: 0.000005096,
|
||||
},
|
||||
{
|
||||
type: WalletTransactionType.Received,
|
||||
timestamp: Date.now(),
|
||||
amount: 17.275849365201,
|
||||
foreignAmount: 4108.5,
|
||||
amountCurrency: "XMR",
|
||||
foreignAmountCurrency: "EUR",
|
||||
transactionId:
|
||||
"a1b848fdf7fb77f1dae266331d23c522db267ced63566a6e35800421c988d9f1",
|
||||
incomingAddresses: [
|
||||
"7631e90afdb723b1a798b39bfc5ec942i5d0e155dfa993f536827c7f9699740a",
|
||||
],
|
||||
destinationAddresses: [
|
||||
"7631e90afdb723b1a798b39bfc5ec942i5d0e155dfa993f536827c7f9699740a",
|
||||
],
|
||||
height: 2482937,
|
||||
fee: 0.000005096,
|
||||
},
|
||||
{
|
||||
type: WalletTransactionType.Sent,
|
||||
timestamp: Date.now(),
|
||||
amount: 17.275849365201,
|
||||
foreignAmount: 4108.5,
|
||||
amountCurrency: "XMR",
|
||||
foreignAmountCurrency: "EUR",
|
||||
transactionId:
|
||||
"a1b848fdf7fb77f1dae266331d23c522db267ced63566a6e35800421c988d9f1",
|
||||
incomingAddresses: [
|
||||
"7631e90afdb723b1a798b39bfc5ec942i5d0e155dfa993f536827c7f9699740a",
|
||||
],
|
||||
destinationAddresses: [
|
||||
"7631e90afdb723b1a798b39bfc5ec942i5d0e155dfa993f536827c7f9699740a",
|
||||
],
|
||||
height: 2482937,
|
||||
fee: 0.000005096,
|
||||
},
|
||||
] as TWalletTransaction[];
|
@ -0,0 +1,77 @@
|
||||
// =============================================================================
|
||||
// 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 { createTable } from "@tanstack/react-table";
|
||||
import { Table } from "@molecules/Table";
|
||||
import { createStyles } from "@mantine/core";
|
||||
import {
|
||||
WalletTransactionnSignCell,
|
||||
WalletTransactionAmountCell,
|
||||
} from "./WalletTransactionsCells";
|
||||
import { WalletTransactionRowExpanded } from "./WalletTransactionsRowExpanded";
|
||||
import type { TWalletTransaction } from "./_types";
|
||||
|
||||
const table = createTable().setRowType<TWalletTransaction>();
|
||||
|
||||
const columns = [
|
||||
table.createDataColumn("type", {
|
||||
id: "type",
|
||||
header: "Type",
|
||||
cell: ({ row }) => <WalletTransactionnSignCell row={row.original} />,
|
||||
}),
|
||||
table.createDataColumn("amount", {
|
||||
id: "transaction",
|
||||
header: "Amount",
|
||||
cell: ({ row }) => <WalletTransactionAmountCell row={row.original} />,
|
||||
size: 400,
|
||||
}),
|
||||
];
|
||||
|
||||
interface WalletTransactionsProps {
|
||||
data: Array<TWalletTransaction>;
|
||||
}
|
||||
|
||||
export function WalletTransactions({ data }: WalletTransactionsProps) {
|
||||
const { classes } = useStyles();
|
||||
|
||||
return (
|
||||
<Table
|
||||
table={table}
|
||||
columns={columns}
|
||||
data={data}
|
||||
showHeader={false}
|
||||
tableWrap={{
|
||||
verticalSpacing: "md",
|
||||
className: classes.root,
|
||||
}}
|
||||
rowSubComponent={WalletTransactionRowExpanded}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createStyles(() => ({
|
||||
root: {
|
||||
"tbody tr td:first-child": {
|
||||
paddingLeft: 0,
|
||||
},
|
||||
"tbody tr td:last-child": {
|
||||
paddingRight: 0,
|
||||
},
|
||||
"tbody tr": {
|
||||
cursor: "pointer",
|
||||
},
|
||||
},
|
||||
}));
|
@ -0,0 +1,98 @@
|
||||
// =============================================================================
|
||||
// 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 { FormattedDate, FormattedTime, useIntl } from "react-intl";
|
||||
import { Box, Group, Stack, Text, useMantineTheme } from "@mantine/core";
|
||||
import { ReactComponent as ArrowNorth } from "@assets/arrow-north.svg";
|
||||
import { ReactComponent as ArrowWest } from "@assets/arrow-west.svg";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { Currency } from "@atoms/Currency";
|
||||
import { CircleIcon } from "@atoms/CircleIcon/CircleIcon";
|
||||
import type { TWalletTransaction } from "./_types";
|
||||
import { WalletTransactionType } from "./_types";
|
||||
|
||||
export function WalletTransactionnSignCell({
|
||||
row,
|
||||
}: {
|
||||
row?: TWalletTransaction;
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
const theme = useMantineTheme();
|
||||
|
||||
return (
|
||||
<Group>
|
||||
<CircleIcon>
|
||||
{WalletTransactionType.Sent === row?.type ? (
|
||||
<ArrowNorth color={theme.colors.blue[5]} width={18} height={18} />
|
||||
) : (
|
||||
<ArrowWest color={theme.colors.green[4]} width={18} height={18} />
|
||||
)}
|
||||
</CircleIcon>
|
||||
|
||||
<Box>
|
||||
<Text weight="bold">
|
||||
{row?.type === WalletTransactionType.Sent
|
||||
? formatMessage({
|
||||
id: LangKeys.WalletDetailSent,
|
||||
defaultMessage: "Sent",
|
||||
})
|
||||
: formatMessage({
|
||||
id: LangKeys.WalletDetailReceived,
|
||||
defaultMessage: "Received",
|
||||
})}
|
||||
</Text>
|
||||
|
||||
<Group spacing="xs">
|
||||
<Text size="sm" color="gray">
|
||||
<FormattedTime value={row?.timestamp} />
|
||||
</Text>
|
||||
|
||||
<Text size="sm" color="gray">
|
||||
<FormattedDate
|
||||
value={row?.timestamp}
|
||||
year="numeric"
|
||||
month="long"
|
||||
day="2-digit"
|
||||
/>
|
||||
</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
export function WalletTransactionAmountCell({
|
||||
row,
|
||||
}: {
|
||||
row?: TWalletTransaction;
|
||||
}) {
|
||||
return (
|
||||
<Stack spacing={0} sx={{ textAlign: "right" }}>
|
||||
<Text weight="bold">
|
||||
<Currency value={row?.amount || 0} currencyCode={row?.amountCurrency} />
|
||||
</Text>
|
||||
|
||||
{row?.foreignAmount && (
|
||||
<Text size="sm" color="gray">
|
||||
<Currency
|
||||
value={row?.foreignAmount}
|
||||
currencyCode={row?.foreignAmountCurrency}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
// =============================================================================
|
||||
// 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 { useIntl } from "react-intl";
|
||||
import { Box, createStyles, Grid } from "@mantine/core";
|
||||
import type { Row } from "@tanstack/react-table";
|
||||
import { DetailItem } from "@atoms/DetailItem";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { Currency } from "@atoms/Currency";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function WalletTransactionRowExpanded({ row }: { row: Row<any> }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { classes } = useRowExpanded();
|
||||
|
||||
return (
|
||||
<Box className={classes.root}>
|
||||
<Grid>
|
||||
<Grid.Col span={9}>
|
||||
<DetailItem
|
||||
label={formatMessage({
|
||||
id: LangKeys.WalletDetailTransactionId,
|
||||
defaultMessage: "Transaction ID",
|
||||
})}
|
||||
classNames={{
|
||||
label: classes.label,
|
||||
content: classes.detailContent,
|
||||
}}
|
||||
mb="lg"
|
||||
>
|
||||
{row.original.transactionId}
|
||||
</DetailItem>
|
||||
|
||||
{row.original?.destinationAddresses?.map((address: string) => (
|
||||
<DetailItem
|
||||
key={address}
|
||||
label={formatMessage({
|
||||
id: LangKeys.WalletDetailDestinationAddress,
|
||||
defaultMessage: "Transaction Key",
|
||||
})}
|
||||
classNames={{
|
||||
label: classes.label,
|
||||
content: classes.detailContent,
|
||||
}}
|
||||
mb="lg"
|
||||
>
|
||||
{address}
|
||||
</DetailItem>
|
||||
))}
|
||||
{row.original?.incomingAddresses?.map((address: string) => (
|
||||
<DetailItem
|
||||
key={address}
|
||||
label={formatMessage({
|
||||
id: LangKeys.WalletDetailIncomingAddress,
|
||||
defaultMessage: "Incoming Address",
|
||||
})}
|
||||
classNames={{
|
||||
label: classes.label,
|
||||
content: classes.detailContent,
|
||||
}}
|
||||
mb="lg"
|
||||
>
|
||||
{address}
|
||||
</DetailItem>
|
||||
))}
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={3}>
|
||||
<DetailItem
|
||||
label={formatMessage({
|
||||
id: LangKeys.WalletDetailFee,
|
||||
defaultMessage: "Fee",
|
||||
})}
|
||||
ml="auto"
|
||||
textAlign="right"
|
||||
classNames={{
|
||||
label: classes.label,
|
||||
content: classes.detailContent,
|
||||
}}
|
||||
mb="lg"
|
||||
>
|
||||
<Currency value={row.original.fee} />
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem
|
||||
label={formatMessage({
|
||||
id: LangKeys.WalletDetailHeight,
|
||||
defaultMessage: "Height",
|
||||
})}
|
||||
ml="auto"
|
||||
textAlign="right"
|
||||
classNames={{
|
||||
label: classes.label,
|
||||
content: classes.detailContent,
|
||||
}}
|
||||
mb="lg"
|
||||
>
|
||||
<Currency value={row.original.height} />
|
||||
</DetailItem>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const useRowExpanded = createStyles((theme) => ({
|
||||
root: {
|
||||
paddingLeft: 50,
|
||||
paddingTop: theme.spacing.sm,
|
||||
paddingBottom: theme.spacing.sm,
|
||||
},
|
||||
label: {
|
||||
fontSize: 10,
|
||||
},
|
||||
detailContent: {
|
||||
wordBreak: "break-all",
|
||||
},
|
||||
}));
|
@ -0,0 +1,39 @@
|
||||
// =============================================================================
|
||||
// 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 WalletTransactionType {
|
||||
Sent = "Sent",
|
||||
Received = "Received",
|
||||
}
|
||||
|
||||
export interface TWalletTransaction {
|
||||
type: WalletTransactionType;
|
||||
timestamp: number | Date;
|
||||
|
||||
amount: number;
|
||||
amountCurrency: string;
|
||||
|
||||
foreignAmount?: number;
|
||||
foreignAmountCurrency?: string;
|
||||
|
||||
transactionId: string;
|
||||
|
||||
destinationAddresses?: Array<string>;
|
||||
incomingAddresses?: Array<string>;
|
||||
|
||||
fee: number;
|
||||
height: number;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./WalletTransactions";
|
@ -0,0 +1,44 @@
|
||||
// =============================================================================
|
||||
// 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 { MoneroBalance } from "./MoneroBalance";
|
||||
|
||||
export default {
|
||||
title: "molecules/MoneroBalance",
|
||||
component: MoneroBalance,
|
||||
} as ComponentMeta<typeof MoneroBalance>;
|
||||
|
||||
const Template: ComponentStory<typeof MoneroBalance> = () => {
|
||||
return (
|
||||
<MoneroBalance>
|
||||
<MoneroBalance.Detail label="AVAILABLE BALANCE">
|
||||
14.048212174412
|
||||
</MoneroBalance.Detail>
|
||||
|
||||
<MoneroBalance.Detail label="AVAILABLE BALANCE">
|
||||
14.048212174412
|
||||
</MoneroBalance.Detail>
|
||||
|
||||
<MoneroBalance.Detail label="AVAILABLE BALANCE">
|
||||
14.048212174412
|
||||
</MoneroBalance.Detail>
|
||||
</MoneroBalance>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {};
|
@ -0,0 +1,57 @@
|
||||
// =============================================================================
|
||||
// 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 { MoneroBalance } from "./MoneroBalance";
|
||||
|
||||
describe("organisms::MoneroBalance", () => {
|
||||
it("renders without exploding.", () => {
|
||||
const { asFragment, unmount } = render(
|
||||
<MoneroBalance>
|
||||
<MoneroBalance.Detail label="Balance">10.20</MoneroBalance.Detail>
|
||||
</MoneroBalance>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders children content.", () => {
|
||||
const { unmount } = render(<MoneroBalance>Content here...</MoneroBalance>);
|
||||
|
||||
expect(screen.queryByText("Content here...")).toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders monero balance detail", () => {
|
||||
const { unmount } = render(
|
||||
<MoneroBalance>
|
||||
<MoneroBalance.Detail label="Balance label">10.20</MoneroBalance.Detail>
|
||||
</MoneroBalance>
|
||||
);
|
||||
expect(screen.queryByText("Balance label")).toBeInTheDocument();
|
||||
expect(screen.queryByText("10.20")).toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("display Monero logo", () => {
|
||||
const { unmount, container } = render(
|
||||
<MoneroBalance>Content here...</MoneroBalance>
|
||||
);
|
||||
expect(container.querySelector("svg#monero-icon")).toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
});
|
@ -0,0 +1,65 @@
|
||||
// =============================================================================
|
||||
// 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 { Group, createStyles } from "@mantine/core";
|
||||
import { ReactComponent as MoneroIcon } from "@assets/monero.svg";
|
||||
import { DetailItem } from "@atoms/DetailItem";
|
||||
|
||||
interface MoneroBalanceProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
export function MoneroBalance({ children }: MoneroBalanceProps) {
|
||||
const { classes } = useStyles();
|
||||
|
||||
return (
|
||||
<Group className={classes.root} spacing={0}>
|
||||
<MoneroIcon className={classes.moneroIcon} />
|
||||
<Group className={classes.content}>{children}</Group>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
interface MoneroBalanceDetail {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
MoneroBalance.Detail = MoneroBalanceDetail;
|
||||
|
||||
function MoneroBalanceDetail({ ...props }: MoneroBalanceDetail) {
|
||||
return <DetailItem {...props} />;
|
||||
}
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
root: {
|
||||
backgroundColor: theme.white,
|
||||
border: `1px solid ${theme.colors.gray[3]}`,
|
||||
borderRadius: theme.radius.md,
|
||||
paddingTop: theme.spacing.md,
|
||||
paddingBottom: theme.spacing.md,
|
||||
paddingLeft: theme.spacing.md,
|
||||
paddingRight: theme.spacing.md,
|
||||
},
|
||||
moneroIcon: {
|
||||
height: 32,
|
||||
marginLeft: theme.spacing.xs,
|
||||
marginRight: theme.spacing.xl,
|
||||
width: 32,
|
||||
},
|
||||
content: {
|
||||
gap: theme.spacing.xl * 1.5,
|
||||
},
|
||||
}));
|
@ -0,0 +1,61 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`organisms::MoneroBalance > renders without exploding. 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mantine-Group-root mantine-3iq866"
|
||||
>
|
||||
<svg
|
||||
class="mantine-Group-child mantine-154defe"
|
||||
fill="none"
|
||||
height="1em"
|
||||
id="monero-icon"
|
||||
viewBox="0 0 32 32"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#clip0_2924_33103)"
|
||||
>
|
||||
<path
|
||||
d="M15.9979 0C7.16414 0 -0.0112325 7.17316 1.32008e-05 15.9979C0.00222827 17.7635 0.283798 19.4618 0.812262 21.0505H5.5992V7.59219L15.9979 17.9897L26.3961 7.59219V21.0507H31.1841C31.7133 19.4622 31.9934 17.7638 31.9966 15.9982C32.0117 7.16422 24.8326 0.00212965 15.9979 0.00212965V0Z"
|
||||
fill="#111111"
|
||||
/>
|
||||
<path
|
||||
d="M13.6084 20.3801L9.0703 15.8423V24.3109H5.60074L2.32678 24.3115C5.13506 28.9184 10.2103 32 15.9998 32C21.7893 32 26.8648 28.9177 29.6736 24.3108H22.9283V15.8423L18.39 20.3801L15.9993 22.7705L13.6086 20.3801H13.6084Z"
|
||||
fill="#111111"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="clip0_2924_33103"
|
||||
>
|
||||
<rect
|
||||
fill="white"
|
||||
height="32"
|
||||
width="32"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
<div
|
||||
class="mantine-Group-root mantine-Group-child mantine-1gdejmd"
|
||||
>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-2d5onn"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-label mantine-1mkytp"
|
||||
>
|
||||
Balance
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-content mantine-17mq3ni"
|
||||
>
|
||||
10.20
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./MoneroBalance";
|
@ -0,0 +1,83 @@
|
||||
// =============================================================================
|
||||
// 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 { FC } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { useBalances } from "@hooks/haveno/useBalances";
|
||||
import { MoneroBalance } from "@organisms/MoneroBalance";
|
||||
import { MyWalletMeneroBalanceSkeleton } from "./MyWalletMeneroBalanceSkeleton";
|
||||
import { Currency } from "@atoms/Currency";
|
||||
|
||||
export function MyWalletMoneroBalanceContent() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { isLoading: isBalancesLoading, data: balanceInfo } = useBalances();
|
||||
|
||||
if (isBalancesLoading || !balanceInfo) {
|
||||
return <>Loading ...</>;
|
||||
}
|
||||
return (
|
||||
<MoneroBalance>
|
||||
<MoneroBalance.Detail
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletMoneroAvaliableBalance,
|
||||
defaultMessage: "Avaliable Balance",
|
||||
})}
|
||||
data-testid="avaliable-balance"
|
||||
>
|
||||
<Currency value={balanceInfo.balance} />
|
||||
</MoneroBalance.Detail>
|
||||
|
||||
<MoneroBalance.Detail
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletMoneroReserveredFunds,
|
||||
defaultMessage: "Reservered Funds",
|
||||
})}
|
||||
data-testid="reserverd-funds"
|
||||
>
|
||||
<Currency value={balanceInfo.reservedOfferBalance || 0} />
|
||||
</MoneroBalance.Detail>
|
||||
|
||||
<MoneroBalance.Detail
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletMoneroLockedFunds,
|
||||
defaultMessage: "Locked Funds",
|
||||
})}
|
||||
data-testid="locked-funds"
|
||||
>
|
||||
<Currency value={balanceInfo.lockedBalance || 0} />
|
||||
</MoneroBalance.Detail>
|
||||
</MoneroBalance>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyWalletMoneroBalance() {
|
||||
return (
|
||||
<MyWalletMoneroBalanceBoot>
|
||||
<MyWalletMoneroBalanceContent />
|
||||
</MyWalletMoneroBalanceBoot>
|
||||
);
|
||||
}
|
||||
|
||||
export const MyWalletMoneroBalanceBoot: FC = ({ children }): JSX.Element => {
|
||||
const { isLoading: isBalancesLoading } = useBalances();
|
||||
|
||||
return isBalancesLoading ? (
|
||||
<MyWalletMeneroBalanceSkeleton />
|
||||
) : (
|
||||
<>{children}</>
|
||||
);
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
// =============================================================================
|
||||
// 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 { useIntl } from "react-intl";
|
||||
import { Skeleton } from "@mantine/core";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { MoneroBalance } from "@organisms/MoneroBalance";
|
||||
|
||||
export function MyWalletMeneroBalanceSkeleton() {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<MoneroBalance>
|
||||
<MoneroBalance.Detail
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletMoneroAvaliableBalance,
|
||||
defaultMessage: "Avaliable Balance",
|
||||
})}
|
||||
>
|
||||
<Skeleton height={8} radius="xl" mt={6} />
|
||||
</MoneroBalance.Detail>
|
||||
|
||||
<MoneroBalance.Detail
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletMoneroReserveredFunds,
|
||||
defaultMessage: "Reservered Funds",
|
||||
})}
|
||||
>
|
||||
<Skeleton height={8} radius="xl" mt={6} />
|
||||
</MoneroBalance.Detail>
|
||||
|
||||
<MoneroBalance.Detail
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletMoneroLockedFunds,
|
||||
defaultMessage: "Locked Funds",
|
||||
})}
|
||||
>
|
||||
<Skeleton height={8} radius="xl" mt={6} />
|
||||
</MoneroBalance.Detail>
|
||||
</MoneroBalance>
|
||||
);
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
// =============================================================================
|
||||
// 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 { MyWalletMoneroBalance } from "./MyWalletMeneroBalance";
|
||||
|
||||
export default {
|
||||
title: "organisms/MyWalletMoneroBalance",
|
||||
component: MyWalletMoneroBalance,
|
||||
} as ComponentMeta<typeof MyWalletMoneroBalance>;
|
||||
|
||||
const Template: ComponentStory<typeof MyWalletMoneroBalance> = () => {
|
||||
return <MyWalletMoneroBalance />;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {};
|
@ -0,0 +1,85 @@
|
||||
// =============================================================================
|
||||
// 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, vi, beforeAll } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { MyWalletMoneroBalance } from "./MyWalletMeneroBalance";
|
||||
import { AppProviders } from "@atoms/AppProviders";
|
||||
|
||||
describe("organisms::MyWalletMoneroBalance", () => {
|
||||
beforeAll(() => {
|
||||
vi.mock("@hooks/haveno/useBalances", () => ({
|
||||
useBalances: () => ({
|
||||
isLoading: false,
|
||||
isSuccess: true,
|
||||
data: {
|
||||
balance: MoneroBalance.balance,
|
||||
lockedBalance: MoneroBalance.lockedBalance,
|
||||
reservedOfferBalance: MoneroBalance.reserverdBalance,
|
||||
reservedTradeBalance: MoneroBalance.reserverdBalance,
|
||||
unlockedBalance: MoneroBalance.unlockedBalance,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders without exploding", () => {
|
||||
const { asFragment, unmount } = render(
|
||||
<AppProviders>
|
||||
<MyWalletMoneroBalance />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("contains avaliable balance, reservered funds and locked funds details", () => {
|
||||
const { unmount } = render(
|
||||
<AppProviders>
|
||||
<MyWalletMoneroBalance />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(screen.queryByTestId("avaliable-balance")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("locked-funds")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("reserverd-funds")).toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("contains avaliable balance, reservered funds and locked funds details", () => {
|
||||
const { unmount } = render(
|
||||
<AppProviders>
|
||||
<MyWalletMoneroBalance />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(screen.queryByTestId("avaliable-balance")).toHaveTextContent(
|
||||
"835,120.34017"
|
||||
);
|
||||
expect(screen.queryByTestId("reserverd-funds")).toHaveTextContent(
|
||||
"74,610.1236"
|
||||
);
|
||||
expect(screen.queryByTestId("locked-funds")).toHaveTextContent(
|
||||
"90,371.161239"
|
||||
);
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
const MoneroBalance = {
|
||||
balance: 835120.34017,
|
||||
reserverdBalance: 74610.1236,
|
||||
lockedBalance: 90371.161239,
|
||||
unlockedBalance: 0,
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
// =============================================================================
|
||||
// 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 { MyWalletMeneroBalanceSkeleton } from "./MyWalletMeneroBalanceSkeleton";
|
||||
|
||||
export default {
|
||||
title: "organisms/MyWalletMeneroBalanceSkeleton",
|
||||
component: MyWalletMeneroBalanceSkeleton,
|
||||
} as ComponentMeta<typeof MyWalletMeneroBalanceSkeleton>;
|
||||
|
||||
const Template: ComponentStory<typeof MyWalletMeneroBalanceSkeleton> = () => {
|
||||
return <MyWalletMeneroBalanceSkeleton />;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {};
|
@ -0,0 +1,31 @@
|
||||
// =============================================================================
|
||||
// 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 } from "@testing-library/react";
|
||||
import { MyWalletMeneroBalanceSkeleton } from "./MyWalletMeneroBalanceSkeleton";
|
||||
import { AppProviders } from "@atoms/AppProviders";
|
||||
|
||||
describe("organisms::MyWalletMeneroBalanceSkeleton", () => {
|
||||
it("renders without exploding", () => {
|
||||
const { asFragment } = render(
|
||||
<AppProviders>
|
||||
<MyWalletMeneroBalanceSkeleton />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,92 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`organisms::MyWalletMoneroBalance > renders without exploding 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mantine-Group-root mantine-3iq866"
|
||||
>
|
||||
<svg
|
||||
class="mantine-Group-child mantine-154defe"
|
||||
fill="none"
|
||||
height="1em"
|
||||
id="monero-icon"
|
||||
viewBox="0 0 32 32"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#clip0_2924_33103)"
|
||||
>
|
||||
<path
|
||||
d="M15.9979 0C7.16414 0 -0.0112325 7.17316 1.32008e-05 15.9979C0.00222827 17.7635 0.283798 19.4618 0.812262 21.0505H5.5992V7.59219L15.9979 17.9897L26.3961 7.59219V21.0507H31.1841C31.7133 19.4622 31.9934 17.7638 31.9966 15.9982C32.0117 7.16422 24.8326 0.00212965 15.9979 0.00212965V0Z"
|
||||
fill="#111111"
|
||||
/>
|
||||
<path
|
||||
d="M13.6084 20.3801L9.0703 15.8423V24.3109H5.60074L2.32678 24.3115C5.13506 28.9184 10.2103 32 15.9998 32C21.7893 32 26.8648 28.9177 29.6736 24.3108H22.9283V15.8423L18.39 20.3801L15.9993 22.7705L13.6086 20.3801H13.6084Z"
|
||||
fill="#111111"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="clip0_2924_33103"
|
||||
>
|
||||
<rect
|
||||
fill="white"
|
||||
height="32"
|
||||
width="32"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
<div
|
||||
class="mantine-Group-root mantine-Group-child mantine-1gdejmd"
|
||||
>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-2d5onn"
|
||||
data-testid="avaliable-balance"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
|
||||
>
|
||||
Avaliable Balance
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"
|
||||
>
|
||||
835,120.34017
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-2d5onn"
|
||||
data-testid="reserverd-funds"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
|
||||
>
|
||||
Reservered Funds
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"
|
||||
>
|
||||
74,610.1236
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-2d5onn"
|
||||
data-testid="locked-funds"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
|
||||
>
|
||||
Locked Funds
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"
|
||||
>
|
||||
90,371.161239
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,95 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`organisms::MyWalletMeneroBalanceSkeleton > renders without exploding 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mantine-Group-root mantine-3iq866"
|
||||
>
|
||||
<svg
|
||||
class="mantine-Group-child mantine-154defe"
|
||||
fill="none"
|
||||
height="1em"
|
||||
id="monero-icon"
|
||||
viewBox="0 0 32 32"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
clip-path="url(#clip0_2924_33103)"
|
||||
>
|
||||
<path
|
||||
d="M15.9979 0C7.16414 0 -0.0112325 7.17316 1.32008e-05 15.9979C0.00222827 17.7635 0.283798 19.4618 0.812262 21.0505H5.5992V7.59219L15.9979 17.9897L26.3961 7.59219V21.0507H31.1841C31.7133 19.4622 31.9934 17.7638 31.9966 15.9982C32.0117 7.16422 24.8326 0.00212965 15.9979 0.00212965V0Z"
|
||||
fill="#111111"
|
||||
/>
|
||||
<path
|
||||
d="M13.6084 20.3801L9.0703 15.8423V24.3109H5.60074L2.32678 24.3115C5.13506 28.9184 10.2103 32 15.9998 32C21.7893 32 26.8648 28.9177 29.6736 24.3108H22.9283V15.8423L18.39 20.3801L15.9993 22.7705L13.6086 20.3801H13.6084Z"
|
||||
fill="#111111"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clippath
|
||||
id="clip0_2924_33103"
|
||||
>
|
||||
<rect
|
||||
fill="white"
|
||||
height="32"
|
||||
width="32"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
</svg>
|
||||
<div
|
||||
class="mantine-Group-root mantine-Group-child mantine-1gdejmd"
|
||||
>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-2d5onn"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
|
||||
>
|
||||
Avaliable 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"
|
||||
>
|
||||
Reservered Funds
|
||||
</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"
|
||||
>
|
||||
Locked Funds
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"
|
||||
>
|
||||
<div
|
||||
class="mantine-Skeleton-root mantine-Skeleton-visible mantine-12edv24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./MyWalletMeneroBalance";
|
@ -0,0 +1,52 @@
|
||||
// =============================================================================
|
||||
// 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, vi, beforeAll } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { AppProviders } from "@atoms/AppProviders";
|
||||
import { MyWalletPrimaryAddress } from "./MyWalletPrimaryAddress";
|
||||
|
||||
describe("organisms::MyWalletPrimaryAddress", () => {
|
||||
beforeAll(() => {
|
||||
vi.mock("@hooks/haveno/useXmrPrimaryAddress", () => ({
|
||||
useXmrPrimaryAddress: () => ({
|
||||
isLoading: false,
|
||||
isSuccess: true,
|
||||
data: "HAVENOADDRESS",
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders without exploding", () => {
|
||||
const { asFragment, unmount } = render(
|
||||
<AppProviders>
|
||||
<MyWalletPrimaryAddress />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders xmr primary address from the daemon.", () => {
|
||||
const { unmount } = render(
|
||||
<AppProviders>
|
||||
<MyWalletPrimaryAddress />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(screen.queryByText("HAVENOADDRESS")).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
});
|
@ -0,0 +1,65 @@
|
||||
// =============================================================================
|
||||
// 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 { useIntl } from "react-intl";
|
||||
import {
|
||||
AddressCard,
|
||||
AddressCardSkeleton,
|
||||
} from "@molecules/AddressCard/AddressCard";
|
||||
import { useXmrPrimaryAddress } from "@hooks/haveno/useXmrPrimaryAddress";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
|
||||
export function MyWalletPrimaryAddressContent() {
|
||||
const { isLoading, data: xmrAddress } = useXmrPrimaryAddress();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
if (isLoading || !xmrAddress) return <>Loading...</>;
|
||||
|
||||
return (
|
||||
<AddressCard
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletBalancePrimaryAddress,
|
||||
defaultMessage: "Primary Address",
|
||||
})}
|
||||
address={xmrAddress}
|
||||
primary={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyWalletPrimaryAddressSkeleton() {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<AddressCardSkeleton
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletBalancePrimaryAddress,
|
||||
defaultMessage: "Primary Address",
|
||||
})}
|
||||
primary={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyWalletPrimaryAddress() {
|
||||
const { isLoading } = useXmrPrimaryAddress();
|
||||
|
||||
return isLoading ? (
|
||||
<MyWalletPrimaryAddressSkeleton />
|
||||
) : (
|
||||
<MyWalletPrimaryAddressContent />
|
||||
);
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`organisms::MyWalletPrimaryAddress > renders without exploding 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mantine-Group-root mantine-sfnj26"
|
||||
>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-DetailItem-root mantine-Group-child mantine-1a4fzbd"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-label mantine-10gsq91"
|
||||
>
|
||||
Primary Address
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-DetailItem-content mantine-oat2gy"
|
||||
>
|
||||
<div
|
||||
class="mantine-Group-root mantine-1i932k4"
|
||||
>
|
||||
<div
|
||||
class="mantine-Group-child mantine-2w2ufd"
|
||||
>
|
||||
HAVENOADDRESS
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Group-root mantine-Group-child mantine-ze53qg"
|
||||
>
|
||||
<a
|
||||
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-16zvpw1"
|
||||
>
|
||||
Copy
|
||||
</a>
|
||||
<a
|
||||
class="mantine-Text-root mantine-Anchor-root mantine-Group-child mantine-16zvpw1"
|
||||
>
|
||||
QR
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`organisms::MyWalletPrimaryAddress > renders xmr primary address from the daemon. 1`] = `
|
||||
<div
|
||||
class="mantine-Group-child mantine-2w2ufd"
|
||||
>
|
||||
HAVENOADDRESS
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./MyWalletPrimaryAddress";
|
@ -0,0 +1,30 @@
|
||||
// =============================================================================
|
||||
// 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 { MyWalletReceive } from "./MyWalletReceive";
|
||||
|
||||
export default {
|
||||
title: "organisms/MyWalletReceive",
|
||||
component: MyWalletReceive,
|
||||
} as ComponentMeta<typeof MyWalletReceive>;
|
||||
|
||||
const Template: ComponentStory<typeof MyWalletReceive> = () => {
|
||||
return <MyWalletReceive />;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {};
|
@ -0,0 +1,84 @@
|
||||
// =============================================================================
|
||||
// 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 { useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Box, Button, Group, Stack, Text } from "@mantine/core";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { Heading } from "@atoms/Typography";
|
||||
import { AddressCard } from "@molecules/AddressCard/AddressCard";
|
||||
import { useSetXmrNewSubaddress } from "@hooks/haveno/useSetXmrNewSubaddress";
|
||||
import { getActiveReceiveAddresses, saveReceiveAddresss } from "./_utils";
|
||||
|
||||
export function MyWalletReceive() {
|
||||
const { mutateAsync: setXmrNewSubaddress, isLoading: isSetXmrLoading } =
|
||||
useSetXmrNewSubaddress();
|
||||
const [addresses, setAddresses] = useState<Array<string>>(
|
||||
getActiveReceiveAddresses() || []
|
||||
);
|
||||
|
||||
const handleGenerateAddressBtn = () => {
|
||||
setXmrNewSubaddress().then((newSubaddress: string) => {
|
||||
saveReceiveAddresss(newSubaddress);
|
||||
|
||||
const cachedSubaddress = getActiveReceiveAddresses();
|
||||
setAddresses(cachedSubaddress);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Box>
|
||||
<Heading
|
||||
order={4}
|
||||
stringId={LangKeys.MyWalletReceiveTitle}
|
||||
mb="sm"
|
||||
mt="lg"
|
||||
>
|
||||
Your Address
|
||||
</Heading>
|
||||
|
||||
{addresses.length === 0 && (
|
||||
<Text mt="xs" color="gray">
|
||||
<FormattedMessage
|
||||
id={LangKeys.MyWalletReceiveNoAddressesMsg}
|
||||
defaultMessage={`You don't have generated address, please generate one.`}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
<Stack>
|
||||
{addresses.map((address) => (
|
||||
<AddressCard key={address} address={address} />
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
<Group position="right" mt="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="dark"
|
||||
size="md"
|
||||
type="submit"
|
||||
loading={isSetXmrLoading}
|
||||
onClick={handleGenerateAddressBtn}
|
||||
>
|
||||
+{" "}
|
||||
<FormattedMessage
|
||||
id={LangKeys.MyWalletGenerateAddressBtn}
|
||||
defaultMessage="Generate a new sub address"
|
||||
/>
|
||||
</Button>
|
||||
</Group>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
// =============================================================================
|
||||
// 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 { isEmpty } from "lodash";
|
||||
|
||||
const LOCAL_STORAGE_KEY = "mywallet-received-addresses";
|
||||
|
||||
export const getActiveReceiveAddresses = (): Array<string> => {
|
||||
const storedAddressesString = sessionStorage.getItem(LOCAL_STORAGE_KEY);
|
||||
const storedAddresses = JSON.parse(storedAddressesString || "[]");
|
||||
|
||||
return !isEmpty(storedAddresses) ? storedAddresses : [];
|
||||
};
|
||||
|
||||
export const saveReceiveAddresss = (address: string) => {
|
||||
const activeAddress = getActiveReceiveAddresses();
|
||||
|
||||
sessionStorage.setItem(
|
||||
LOCAL_STORAGE_KEY,
|
||||
JSON.stringify([...activeAddress, address])
|
||||
);
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./MyWalletReceive";
|
@ -0,0 +1,30 @@
|
||||
// =============================================================================
|
||||
// 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 { MyWalletSendForm } from "./MyWalletSendForm";
|
||||
|
||||
export default {
|
||||
title: "organisms/MyWalletSendForm",
|
||||
component: MyWalletSendForm,
|
||||
} as ComponentMeta<typeof MyWalletSendForm>;
|
||||
|
||||
const Template: ComponentStory<typeof MyWalletSendForm> = () => {
|
||||
return <MyWalletSendForm />;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {};
|
@ -0,0 +1,137 @@
|
||||
// =============================================================================
|
||||
// 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 { joiResolver, useForm } from "@mantine/form";
|
||||
import { Group, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { TextInput } from "@atoms/TextInput";
|
||||
import type { MyWalletSendFormValues } from "./_hooks";
|
||||
import { useMyWalletSendFormValidation } from "./_hooks";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { Button } from "@atoms/Buttons";
|
||||
import { useSetXmrSend } from "@hooks/haveno/useSetXmrSend";
|
||||
|
||||
export function MyWalletSendForm() {
|
||||
const { formatMessage } = useIntl();
|
||||
const modals = useModals();
|
||||
|
||||
const { mutateAsync: setXmrSend, isLoading: isXmrSendLoading } =
|
||||
useSetXmrSend();
|
||||
const validation = useMyWalletSendFormValidation();
|
||||
|
||||
const form = useForm<MyWalletSendFormValues>({
|
||||
initialValues: {
|
||||
amount: "",
|
||||
address: "",
|
||||
paymentId: "",
|
||||
},
|
||||
validate: joiResolver(validation),
|
||||
});
|
||||
|
||||
const handleFormSubmit = (values: MyWalletSendFormValues) => {
|
||||
setXmrSend(values).then((hash: string) => {
|
||||
const modalId = modals.openModal({
|
||||
title: formatMessage({
|
||||
id: LangKeys.MyWalletSendSuccessModalTitle,
|
||||
defaultMessage: "Fund are sent!",
|
||||
}),
|
||||
children: (
|
||||
<>
|
||||
<Text color="gray">
|
||||
<FormattedMessage
|
||||
id={LangKeys.MyWalletSendSuccessModalMsg}
|
||||
defaultMessage="You’ve sent {amount} XMR to: {hash}"
|
||||
values={{
|
||||
amount: values.amount,
|
||||
hash,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
|
||||
<Button onClick={() => modals.closeModal(modalId)} mt="md">
|
||||
<FormattedMessage
|
||||
id={LangKeys.MyWalletSendBackToWallet}
|
||||
defaultMessage="Back to Wallet"
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
),
|
||||
});
|
||||
form.reset();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(handleFormSubmit)}>
|
||||
<Stack spacing="xl">
|
||||
<SimpleGrid cols={2}>
|
||||
<TextInput
|
||||
id="amount"
|
||||
type="number"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.MyWalletSendFieldAmount}
|
||||
defaultMessage="Amount"
|
||||
/>
|
||||
}
|
||||
rightSection={
|
||||
<Text mr="xl" weight={500} color="gray">
|
||||
XMR
|
||||
</Text>
|
||||
}
|
||||
{...form.getInputProps("amount")}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
|
||||
<TextInput
|
||||
id="address"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.MyWalletSendFieldAddress}
|
||||
defaultMessage="Address"
|
||||
/>
|
||||
}
|
||||
placeholder={formatMessage({
|
||||
id: LangKeys.MyWalletSendFieldAddressPlaceholder,
|
||||
defaultMessage: "Paste in address here...",
|
||||
})}
|
||||
{...form.getInputProps("address")}
|
||||
/>
|
||||
<TextInput
|
||||
id="paymentId"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.MyWalletSendFieldPaymentId}
|
||||
defaultMessage="Payment ID"
|
||||
/>
|
||||
}
|
||||
placeholder={formatMessage({
|
||||
id: LangKeys.MyWalletSendFieldPaymentIdPlaceholder,
|
||||
defaultMessage: "Paste in address here...",
|
||||
})}
|
||||
{...form.getInputProps("paymentId")}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Group position="right" mt="xl">
|
||||
<Button size="md" type="submit" loading={isXmrSendLoading}>
|
||||
<FormattedMessage id={LangKeys.Save} defaultMessage="Save" />
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
);
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
// =============================================================================
|
||||
// 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 * as Joi from "joi";
|
||||
|
||||
export interface MyWalletSendFormValues {
|
||||
amount: string;
|
||||
address: string;
|
||||
paymentId: string;
|
||||
}
|
||||
|
||||
export function useMyWalletSendFormValidation() {
|
||||
return Joi.object<MyWalletSendFormValues>({
|
||||
amount: Joi.number().required(),
|
||||
address: Joi.string().required(),
|
||||
paymentId: Joi.string().optional().empty(""),
|
||||
});
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./MyWalletSendForm";
|
@ -0,0 +1,82 @@
|
||||
// =============================================================================
|
||||
// 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, vi, beforeAll } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { AppProviders } from "@atoms/AppProviders";
|
||||
import { MyWalletTransactions } from "./MyWalletTransactions";
|
||||
|
||||
describe("organisms::MyWalletMoneroBalance", () => {
|
||||
beforeAll(() => {
|
||||
vi.mock("@hooks/haveno/useXmrTxs", () => ({
|
||||
useXmrTxs: () => ({
|
||||
isLoading: false,
|
||||
isSuccess: true,
|
||||
data: [
|
||||
{
|
||||
timestamp: 1653593643913,
|
||||
height: "1.334423",
|
||||
fee: "3.33",
|
||||
isConfirmed: true,
|
||||
isLocked: false,
|
||||
hash: "HASHADDRESS",
|
||||
incomingTransfersList: [
|
||||
{
|
||||
amount: "10000",
|
||||
accountIndex: 1,
|
||||
subaddressIndex: 1,
|
||||
address: "INCOMINGADDRESS",
|
||||
numSuggestedConfirmations: 100,
|
||||
},
|
||||
],
|
||||
outgoingTransfer: {},
|
||||
metadata: "",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders without exploding.", () => {
|
||||
const { asFragment, unmount } = render(
|
||||
<AppProviders>
|
||||
<MyWalletTransactions />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders date detail correctly.", () => {
|
||||
const { unmount } = render(
|
||||
<AppProviders>
|
||||
<MyWalletTransactions />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(screen.queryByText("May 26, 2022")).toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders time detail correctly.", () => {
|
||||
const { unmount } = render(
|
||||
<AppProviders>
|
||||
<MyWalletTransactions />
|
||||
</AppProviders>
|
||||
);
|
||||
expect(screen.queryByText("7:34 PM")).toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
// =============================================================================
|
||||
// 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 { FC } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { Group, Loader } from "@mantine/core";
|
||||
import { useXmrTxs } from "@hooks/haveno/useXmrTxs";
|
||||
import { WalletTransactions } from "@molecules/WalletTransactions";
|
||||
import { transfromXmrTxs } from "./_utils";
|
||||
|
||||
export function MyWalletTransactionsTable() {
|
||||
const { data: xmrTxs } = useXmrTxs();
|
||||
|
||||
const transactions = useMemo(() => transfromXmrTxs(xmrTxs || []), [xmrTxs]);
|
||||
|
||||
return xmrTxs ? <WalletTransactions data={transactions} /> : null;
|
||||
}
|
||||
|
||||
const MyWalletTransactionsBoot: FC = ({ children }) => {
|
||||
const { isLoading: isXmrTxsLoading } = useXmrTxs();
|
||||
|
||||
return isXmrTxsLoading ? (
|
||||
<Group position="center" pt="lg" pb="lg">
|
||||
<Loader color="gray" size="sm" />
|
||||
</Group>
|
||||
) : (
|
||||
<>{children}</>
|
||||
);
|
||||
};
|
||||
|
||||
export function MyWalletTransactions() {
|
||||
return (
|
||||
<MyWalletTransactionsBoot>
|
||||
<MyWalletTransactionsTable />
|
||||
</MyWalletTransactionsBoot>
|
||||
);
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`organisms::MyWalletMoneroBalance > renders without exploding. 1`] = `
|
||||
<DocumentFragment>
|
||||
<table
|
||||
class="mantine-Table-root mantine-16jrhzw"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
style="width: 150px;"
|
||||
>
|
||||
<div
|
||||
class="mantine-Group-root mantine-6y1794"
|
||||
>
|
||||
<div
|
||||
class="mantine-CircleIcon-root mantine-Group-child mantine-lh1mzd"
|
||||
>
|
||||
<svg
|
||||
color="#75B377"
|
||||
fill="none"
|
||||
height="18"
|
||||
viewBox="0 0 14 16"
|
||||
width="18"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.83401 14.9637L12.733 10.4695C13.1844 10.0554 13.1844 9.36518 12.733 8.95105C12.2815 8.53691 11.5291 8.53691 11.0777 8.95105L8.16841 11.6046L8.16841 1.78804C8.16841 1.18984 7.65009 0.714355 6.99802 0.714355C6.34594 0.714355 5.82762 1.18984 5.82762 1.78804L5.82762 11.6046L2.93507 8.95105C2.48363 8.53691 1.73124 8.53691 1.2798 8.95105C1.04572 9.16578 0.928677 9.44187 0.928677 9.71796C0.928677 9.99405 1.04572 10.2701 1.2798 10.4849L6.16202 14.9637C6.37938 15.1631 6.68034 15.2858 6.99801 15.2858C7.31569 15.2858 7.61665 15.1784 7.83401 14.9637Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Group-child mantine-1oprzqz"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-3v9dod"
|
||||
>
|
||||
Received
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Group-root mantine-bhsibn"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-Group-child mantine-e2rvui"
|
||||
>
|
||||
7:34 PM
|
||||
</div>
|
||||
<div
|
||||
class="mantine-Text-root mantine-Group-child mantine-e2rvui"
|
||||
>
|
||||
May 26, 2022
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
style="width: 400px;"
|
||||
>
|
||||
<div
|
||||
class="mantine-Stack-root mantine-1yjxaby"
|
||||
>
|
||||
<div
|
||||
class="mantine-Text-root mantine-3v9dod"
|
||||
>
|
||||
XMR 10,000.00
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,49 @@
|
||||
// =============================================================================
|
||||
// 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 { isEmpty } from "lodash";
|
||||
import type { XmrTx } from "haveno-ts";
|
||||
import type { TWalletTransaction } from "@molecules/WalletTransactions/_types";
|
||||
import { WalletTransactionType } from "@molecules/WalletTransactions/_types";
|
||||
|
||||
export const transfromXmrTxs = (
|
||||
xmrTxs: Array<XmrTx.AsObject>
|
||||
): Array<TWalletTransaction> => {
|
||||
return xmrTxs.map(transfromXmrTx);
|
||||
};
|
||||
|
||||
const transfromXmrTx = (xmrTx: XmrTx.AsObject): TWalletTransaction => ({
|
||||
type: isEmpty(xmrTx.incomingTransfersList)
|
||||
? WalletTransactionType.Sent
|
||||
: WalletTransactionType.Received,
|
||||
|
||||
fee: parseFloat(xmrTx.fee),
|
||||
height: xmrTx.height,
|
||||
|
||||
amount: !isEmpty(xmrTx.outgoingTransfer)
|
||||
? parseFloat(xmrTx.outgoingTransfer?.amount || "")
|
||||
: parseFloat(xmrTx.incomingTransfersList[0]?.amount),
|
||||
|
||||
amountCurrency: "XMR",
|
||||
transactionId: xmrTx.hash,
|
||||
|
||||
timestamp: xmrTx.timestamp,
|
||||
|
||||
incomingAddresses: xmrTx.incomingTransfersList?.map((addr) => addr.address),
|
||||
destinationAddresses: xmrTx.outgoingTransfer?.destinationsList?.map(
|
||||
(addr) => addr.address
|
||||
),
|
||||
});
|
@ -32,4 +32,5 @@ const Template: ComponentStory<typeof Sidebar> = () => {
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {};
|
||||
|
@ -274,7 +274,7 @@ exports[`molecules::Sidebar > renders without exploding 1`] = `
|
||||
class="mantine-Group-child mantine-1mmj0t1"
|
||||
/>
|
||||
<div
|
||||
class="mantine-Text-root mantine-Group-child mantine-1mga3um"
|
||||
class="mantine-Text-root mantine-Group-child mantine-6ffw9j"
|
||||
>
|
||||
Not Synced
|
||||
</div>
|
||||
|
@ -53,6 +53,42 @@ export enum LangKeys {
|
||||
AccountWalletTitle = "account.wallet.title",
|
||||
AccountWalletDesc = "account.wallet.desc",
|
||||
AccountWalletPassword = "account.wallet.field.password",
|
||||
AccountCardCopyBtn = "accountCard.copyButton",
|
||||
AccountCardCopiedBtn = "accountCard.copiedButton",
|
||||
AccountCardQRBtn = "accountCard.qrButton",
|
||||
AddressCardCopyBtn = "accountCard.copyButton",
|
||||
AddressCardCopiedBtn = "accountCard.copiedButton",
|
||||
AddressCardQRBtn = "accountCard.qrButton",
|
||||
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",
|
||||
MyWalletMoneroLockedFunds = "myWallet.monero.lockedFunds",
|
||||
MyWalletTabTransactions = "myWallet.transactionsTab",
|
||||
MyWalletTabSend = "myWallet.sendTab",
|
||||
MyWalletTabReceive = "myWallet.receive",
|
||||
MyWalletGenerateAddressBtn = "myWallet.receive.generateAddrBtn",
|
||||
MyWalletReceiveNoAddressesMsg = "myWallet.receive.noAddressesMsg",
|
||||
MyWalletSendSuccessNotif = "myWallet.send.successNotification",
|
||||
WalletDetailSent = "myWallet.detail.sent",
|
||||
WalletDetailReceived = "myWallet.detail.received",
|
||||
WalletDetailTransactionId = "myWallet.detail.transactionId",
|
||||
WalletDetailFee = "myWallet.detail.fee",
|
||||
WalletDetailDestinationAddress = "myWallet.detail.destinationAddress",
|
||||
WalletDetailIncomingAddress = "myWallet.detail.incomingAddress",
|
||||
WalletDetailHeight = "myWallet.detail.height",
|
||||
WalletDetailReceiptAddress = "myWallet.detail.receiptAddress",
|
||||
MyWalletBalancePrimaryAddress = "myWallet.balance.primaryAddress",
|
||||
MyWalletSendBackToWallet = "myWallet.sendForm.backToWalletBtn",
|
||||
MyWalletSendSuccessModalTitle = "myWallet.sendSuccessModal.title",
|
||||
MyWalletSendSuccessModalMsg = "myWallet.sendSuccessModal.message",
|
||||
MyWalletQRModalPrimaryAddress = "myWallet.qrModal.primaryAddress",
|
||||
MyWalletQRModalReturnBtn = "myWallet.qrModal.returnBtn",
|
||||
MyWalletQRModalDownloadQRBtn = "myWallet.qrModal.downloadBtn",
|
||||
AccountBackupDownloadTitle = "account.backup.download.title",
|
||||
AccountBackupDownloadDesc = "account.backup.download.desc",
|
||||
AccountBackupDownloadBtn = "account.backup.download.btn",
|
||||
|
@ -58,10 +58,49 @@ const LangPackEN: { [key in LangKeys]: string } = {
|
||||
[LangKeys.AccountSecurityFieldRepeatPasswordMatchMsg]:
|
||||
"Passwords don't match",
|
||||
[LangKeys.CreatePassword]: "Create password",
|
||||
|
||||
[LangKeys.AccountWalletTitle]: "Your wallet details",
|
||||
[LangKeys.AccountWalletDesc]:
|
||||
"The Haveno wallet is permanently connected to your account. Solely saving your seed phrase is not enough to recover your account, you need to download a backup of your account, which you can download via the backup section.",
|
||||
[LangKeys.AccountWalletPassword]: "Password",
|
||||
[LangKeys.AccountCardCopyBtn]: "Copy",
|
||||
[LangKeys.AccountCardCopiedBtn]: "Copied",
|
||||
[LangKeys.AccountCardQRBtn]: "QR",
|
||||
[LangKeys.AddressCardCopyBtn]: "Copy",
|
||||
[LangKeys.AddressCardCopiedBtn]: "Copied",
|
||||
[LangKeys.AddressCardQRBtn]: "QR",
|
||||
[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.MyWalletMoneroLockedFunds]: "Locked Funds",
|
||||
[LangKeys.MyWalletTabTransactions]: "Transactions",
|
||||
[LangKeys.MyWalletTabSend]: "Send",
|
||||
[LangKeys.MyWalletTabReceive]: "Receive",
|
||||
[LangKeys.MyWalletGenerateAddressBtn]: "Generate a new sub address",
|
||||
[LangKeys.MyWalletReceiveNoAddressesMsg]:
|
||||
"You don't have generated address, please generate one.",
|
||||
[LangKeys.MyWalletSendSuccessNotif]:
|
||||
"The XMR transaction has been sent successfully.",
|
||||
[LangKeys.WalletDetailSent]: "Sent",
|
||||
[LangKeys.WalletDetailReceived]: "Received",
|
||||
[LangKeys.WalletDetailTransactionId]: "Transaction ID",
|
||||
[LangKeys.WalletDetailFee]: "Fee",
|
||||
[LangKeys.WalletDetailDestinationAddress]: "Destination Address",
|
||||
[LangKeys.WalletDetailIncomingAddress]: "Incoming Address",
|
||||
[LangKeys.WalletDetailHeight]: "Height",
|
||||
[LangKeys.WalletDetailReceiptAddress]: "Receipt Address",
|
||||
[LangKeys.MyWalletBalancePrimaryAddress]: "Primary Address",
|
||||
[LangKeys.MyWalletSendBackToWallet]: "Back to Wallet",
|
||||
[LangKeys.MyWalletSendSuccessModalTitle]: "Fund are sent!",
|
||||
[LangKeys.MyWalletSendSuccessModalMsg]: "",
|
||||
[LangKeys.MyWalletReceiveTitle]: "Your Address",
|
||||
[LangKeys.MyWalletQRModalPrimaryAddress]: "Primary Address",
|
||||
[LangKeys.MyWalletQRModalReturnBtn]: "Return",
|
||||
[LangKeys.MyWalletQRModalDownloadQRBtn]: "Download QR",
|
||||
[LangKeys.AccountBackupDownloadTitle]: "Download your backup file",
|
||||
[LangKeys.AccountBackupDownloadDesc]:
|
||||
"To be able to restore your Haveno account you need to create a backup file of your account. Keep it somewhere safe.",
|
||||
|
@ -63,6 +63,44 @@ const LangPackES: { [key in LangKeys]: string } = {
|
||||
[LangKeys.AccountWalletDesc]:
|
||||
"La billetera Haveno está permanentemente conectada a su cuenta. Solo guardar su frase inicial no es suficiente para recuperar su cuenta, necesita descargar una copia de seguridad de su cuenta, que puede descargar a través de la sección de copia de seguridad.",
|
||||
[LangKeys.AccountWalletPassword]: "contraseña",
|
||||
[LangKeys.AccountCardCopyBtn]: "Dupdo",
|
||||
[LangKeys.AccountCardCopiedBtn]: "Copiada",
|
||||
[LangKeys.AccountCardQRBtn]: "QR",
|
||||
[LangKeys.AddressCardCopyBtn]: "Dupdo",
|
||||
[LangKeys.AddressCardCopiedBtn]: "Copiada",
|
||||
[LangKeys.AddressCardQRBtn]: "QR",
|
||||
[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.MyWalletMoneroLockedFunds]: "Fondos bloqueados",
|
||||
[LangKeys.MyWalletTabTransactions]: "Transactions",
|
||||
[LangKeys.MyWalletTabSend]: "Enviar",
|
||||
[LangKeys.MyWalletTabReceive]: "Recibir",
|
||||
[LangKeys.MyWalletGenerateAddressBtn]: "Generar una nueva dirección",
|
||||
[LangKeys.MyWalletReceiveNoAddressesMsg]:
|
||||
"No ha generado una dirección, por favor genere una.",
|
||||
[LangKeys.MyWalletSendSuccessNotif]:
|
||||
"La transacción XMR se ha enviado con éxito.",
|
||||
[LangKeys.WalletDetailSent]: "Sent",
|
||||
[LangKeys.WalletDetailReceived]: "Received",
|
||||
[LangKeys.WalletDetailTransactionId]: "ID de transacción",
|
||||
[LangKeys.WalletDetailFee]: "Tarifa",
|
||||
[LangKeys.WalletDetailDestinationAddress]: "Dirección de destino",
|
||||
[LangKeys.WalletDetailIncomingAddress]: "Dirección entrante",
|
||||
[LangKeys.WalletDetailHeight]: "Altura",
|
||||
[LangKeys.WalletDetailReceiptAddress]: "Dirección de recibo",
|
||||
[LangKeys.MyWalletBalancePrimaryAddress]: "Dirección primaria",
|
||||
[LangKeys.MyWalletSendBackToWallet]: "Volver a Monedero",
|
||||
[LangKeys.MyWalletSendSuccessModalTitle]: "¡Se envían fondos!",
|
||||
[LangKeys.MyWalletSendSuccessModalMsg]: "You’ve sent {amount} XMR to: {hash}",
|
||||
[LangKeys.MyWalletReceiveTitle]: "Su dirección",
|
||||
[LangKeys.MyWalletQRModalPrimaryAddress]: "Dirección primaria",
|
||||
[LangKeys.MyWalletQRModalReturnBtn]: "Devolver",
|
||||
[LangKeys.MyWalletQRModalDownloadQRBtn]: "Descargar código QR",
|
||||
[LangKeys.AccountBackupDownloadTitle]:
|
||||
"Descarga tu archivo de copia de seguridad",
|
||||
[LangKeys.AccountBackupDownloadDesc]:
|
||||
|
@ -27,6 +27,8 @@ export enum QueryKeys {
|
||||
PrimaryAddress = "Haveno.PrimaryAddress",
|
||||
SyncStatus = "Haveno.SyncStatus",
|
||||
XmrSeed = "Haveno.XmrSeed",
|
||||
XmrPrimaryAddress = "Haveno.XmrPrimaryAddress",
|
||||
XmrTxs = "Haveno.XmrTransactions",
|
||||
|
||||
// Storage
|
||||
StorageAccountInfo = "Storage.AccountInfo",
|
||||
|
@ -21,6 +21,7 @@ export const ROUTES = {
|
||||
Welcome: "/onboarding/welcome",
|
||||
CreateAccount: "/onboarding/create-account",
|
||||
RestoreBackup: "/onboarding/restore-backup",
|
||||
MyWallet: "/my-wallet",
|
||||
|
||||
// Account routes
|
||||
PaymentAccounts: "/account/payment-accounts",
|
||||
|
@ -15,13 +15,30 @@
|
||||
// =============================================================================
|
||||
|
||||
import { useQuery } from "react-query";
|
||||
import type { XmrBalanceInfo } from "haveno-ts";
|
||||
import { QueryKeys } from "@constants/query-keys";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
|
||||
interface BalanceInfo {
|
||||
balance: number;
|
||||
unlockedBalance: number;
|
||||
lockedBalance: number;
|
||||
reservedOfferBalance: number;
|
||||
reservedTradeBalance: number;
|
||||
}
|
||||
|
||||
export function useBalances() {
|
||||
const client = useHavenoClient();
|
||||
return useQuery<XmrBalanceInfo, Error>(QueryKeys.Balances, async () =>
|
||||
client.getBalances()
|
||||
);
|
||||
|
||||
return useQuery<BalanceInfo, Error>(QueryKeys.Balances, async () => {
|
||||
const xmrBalances = await client.getBalances();
|
||||
const balances = xmrBalances.toObject();
|
||||
|
||||
return {
|
||||
balance: parseFloat(balances.balance),
|
||||
unlockedBalance: parseFloat(balances.unlockedBalance),
|
||||
lockedBalance: parseFloat(balances.lockedBalance),
|
||||
reservedOfferBalance: parseFloat(balances.reservedOfferBalance),
|
||||
reservedTradeBalance: parseFloat(balances.reservedTradeBalance),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
// =============================================================================
|
||||
// Copyright 2022 Haveno
|
||||
//
|
||||
|
26
packages/renderer/src/hooks/haveno/useSetXmrNewSubaddress.ts
Normal file
26
packages/renderer/src/hooks/haveno/useSetXmrNewSubaddress.ts
Normal file
@ -0,0 +1,26 @@
|
||||
// =============================================================================
|
||||
// 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";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
|
||||
export function useSetXmrNewSubaddress() {
|
||||
const client = useHavenoClient();
|
||||
|
||||
return useMutation(async () => {
|
||||
return client.getXmrNewSubaddress();
|
||||
});
|
||||
}
|
58
packages/renderer/src/hooks/haveno/useSetXmrSend.ts
Normal file
58
packages/renderer/src/hooks/haveno/useSetXmrSend.ts
Normal file
@ -0,0 +1,58 @@
|
||||
// =============================================================================
|
||||
// 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, useQueryClient } from "react-query";
|
||||
import { XmrDestination } from "haveno-ts";
|
||||
import { showNotification } from "@mantine/notifications";
|
||||
import { QueryKeys } from "@constants/query-keys";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
|
||||
interface SetXmrSendVariables {
|
||||
address: string;
|
||||
amount: string;
|
||||
paymentId?: string;
|
||||
}
|
||||
|
||||
export function useSetXmrSend() {
|
||||
const client = useHavenoClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
async (variables: SetXmrSendVariables) => {
|
||||
const xmrDest = new XmrDestination()
|
||||
.setAddress(variables.address)
|
||||
.setAmount(variables.amount);
|
||||
|
||||
const tx = await client.createXmrTx([xmrDest]);
|
||||
|
||||
return client.relayXmrTx(tx.getMetadata());
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(QueryKeys.XmrTxs);
|
||||
queryClient.invalidateQueries(QueryKeys.Balances);
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
console.dir(err);
|
||||
showNotification({
|
||||
color: "red",
|
||||
message: err.message || "",
|
||||
title: "Something went wrong",
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
27
packages/renderer/src/hooks/haveno/useXmrPrimaryAddress.ts
Normal file
27
packages/renderer/src/hooks/haveno/useXmrPrimaryAddress.ts
Normal file
@ -0,0 +1,27 @@
|
||||
// =============================================================================
|
||||
// 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 { QueryKeys } from "@constants/query-keys";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
|
||||
export const useXmrPrimaryAddress = () => {
|
||||
const client = useHavenoClient();
|
||||
|
||||
return useQuery<string>(QueryKeys.XmrPrimaryAddress, async () =>
|
||||
client.getXmrPrimaryAddress()
|
||||
);
|
||||
};
|
30
packages/renderer/src/hooks/haveno/useXmrTxs.ts
Normal file
30
packages/renderer/src/hooks/haveno/useXmrTxs.ts
Normal file
@ -0,0 +1,30 @@
|
||||
// =============================================================================
|
||||
// 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 { XmrTx } from "haveno-ts";
|
||||
import { useQuery } from "react-query";
|
||||
import { QueryKeys } from "@constants/query-keys";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
|
||||
export const useXmrTxs = () => {
|
||||
const client = useHavenoClient();
|
||||
|
||||
return useQuery<Array<XmrTx.AsObject>>(QueryKeys.XmrTxs, async () => {
|
||||
const txs = await client.getXmrTxs();
|
||||
|
||||
return txs?.map((tx) => tx.toObject());
|
||||
});
|
||||
};
|
@ -71,7 +71,7 @@ export function StartStopDaemon() {
|
||||
}),
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch((err: Error) => {
|
||||
console.dir(err);
|
||||
showNotification({
|
||||
color: "red",
|
||||
|
37
packages/renderer/src/pages/MyWallet/MyWallet.stories.tsx
Normal file
37
packages/renderer/src/pages/MyWallet/MyWallet.stories.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
// =============================================================================
|
||||
// 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 { Stack } from "@mantine/core";
|
||||
import type { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
|
||||
import { MyWallet } from "./MyWallet";
|
||||
|
||||
export default {
|
||||
title: "pages/MyWallet",
|
||||
component: MyWallet,
|
||||
} as ComponentMeta<typeof MyWallet>;
|
||||
|
||||
const Template: ComponentStory<typeof MyWallet> = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<MyWallet />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {};
|
80
packages/renderer/src/pages/MyWallet/MyWallet.tsx
Normal file
80
packages/renderer/src/pages/MyWallet/MyWallet.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
// =============================================================================
|
||||
// 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 { useIntl } from "react-intl";
|
||||
import { Container, createStyles, Stack } from "@mantine/core";
|
||||
import { Tabs } from "@atoms/Tabs";
|
||||
import { MyWalletMoneroBalance } from "@organisms/MyWalletMoneroBalance";
|
||||
import { MyWalletPrimaryAddress } from "@organisms/MyWalletPrimaryAddress";
|
||||
import { MyWalletSendForm } from "@organisms/MyWalletSendForm";
|
||||
import { MyWalletReceive } from "@organisms/MyWalletReceive";
|
||||
import { NavbarLayout } from "@templates/NavbarLayout";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { MyWalletTransactions } from "@organisms/MyWalletTransactions/MyWalletTransactions";
|
||||
|
||||
export function MyWallet() {
|
||||
const { classes } = useStyles();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<NavbarLayout>
|
||||
<Container size="md" mt="xl" className={classes.container}>
|
||||
<Stack spacing="lg">
|
||||
<MyWalletMoneroBalance />
|
||||
<MyWalletPrimaryAddress />
|
||||
</Stack>
|
||||
|
||||
<Tabs className={classes.tabsRoot} pb="xl">
|
||||
<Tabs.Tab
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletTabTransactions,
|
||||
defaultMessage: "Transactions",
|
||||
})}
|
||||
>
|
||||
<MyWalletTransactions />
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletTabSend,
|
||||
defaultMessage: "Send",
|
||||
})}
|
||||
>
|
||||
<MyWalletSendForm />
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab
|
||||
label={formatMessage({
|
||||
id: LangKeys.MyWalletTabReceive,
|
||||
defaultMessage: "Receive",
|
||||
})}
|
||||
>
|
||||
<MyWalletReceive />
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
</Container>
|
||||
</NavbarLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createStyles(() => ({
|
||||
tabsRoot: {
|
||||
marginTop: "3rem",
|
||||
},
|
||||
container: {
|
||||
width: "100%",
|
||||
},
|
||||
}));
|
17
packages/renderer/src/pages/MyWallet/index.ts
Normal file
17
packages/renderer/src/pages/MyWallet/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// =============================================================================
|
||||
// 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 * from "./MyWallet";
|
@ -33,7 +33,8 @@
|
||||
"types/**/*.d.ts",
|
||||
"../../types/**/*.d.ts",
|
||||
"../preload/contracts.d.ts",
|
||||
"../../tests/setup-tests.ts"
|
||||
"../../tests/setup-tests.ts",
|
||||
"../../tests/global-setup.ts"
|
||||
],
|
||||
"exclude": ["**/*.spec.ts", "**/*.test.ts"]
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import viteConfig from "./vite.config";
|
||||
*/
|
||||
const config = mergeConfig(viteConfig, {
|
||||
test: {
|
||||
globalSetup: ["./tests/global-setup.ts"],
|
||||
setupFiles: ["../../tests/setup-tests.ts"],
|
||||
environment: "jsdom",
|
||||
include: ["./src/**/*.{test,spec}.{ts,tsx}"],
|
||||
|
19
tests/global-setup.ts
Normal file
19
tests/global-setup.ts
Normal 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 default function () {
|
||||
process.env.TZ = "UTC";
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user