diff --git a/packages/renderer/src/Routes.tsx b/packages/renderer/src/Routes.tsx index 2e94f5c..1b53503 100644 --- a/packages/renderer/src/Routes.tsx +++ b/packages/renderer/src/Routes.tsx @@ -39,7 +39,14 @@ export function AppRoutes() { } /> } /> } /> - } /> + + + + } + /> { + children: ReactNode; + positive?: boolean; + negative?: boolean; +} + +export function AmountChange(props: AmountChangeProps) { + const { children, positive = false, negative = false } = props; + const { classes } = useStyles({ + positive, + negative, + }); + return {children}; +} + +interface AmountChangeStyleProps { + positive: boolean; + negative: boolean; +} + +const useStyles = createStyles( + (theme, { positive, negative }: AmountChangeStyleProps) => ({ + root: { + color: negative + ? theme.colors.red[6] + : positive + ? theme.colors.green[6] + : undefined, + }, + }) +); diff --git a/packages/renderer/src/components/atoms/TextInput/TextInput.tsx b/packages/renderer/src/components/atoms/TextInput/TextInput.tsx index c3c9593..56c7141 100644 --- a/packages/renderer/src/components/atoms/TextInput/TextInput.tsx +++ b/packages/renderer/src/components/atoms/TextInput/TextInput.tsx @@ -14,8 +14,15 @@ // limitations under the License. // ============================================================================= -import { createStyles, TextInput as MTextInput } from "@mantine/core"; -import type { TextInputProps as MTextInputProps } from "@mantine/core"; +import type { + TextInputProps as MTextInputProps, + NumberInputProps as MNumberInputProps, +} from "@mantine/core"; +import { + createStyles, + TextInput as MTextInput, + NumberInput as MNumberInput, +} from "@mantine/core"; interface TextInputProps extends MTextInputProps { id: string; @@ -24,9 +31,21 @@ interface TextInputProps extends MTextInputProps { export function TextInput(props: TextInputProps) { const { id, ...rest } = props; const { classes } = useStyles(); + return ; } +interface NumberInputProps extends MNumberInputProps { + id: string; +} + +export function NumberInput(props: NumberInputProps) { + const { id, ...rest } = props; + const { classes } = useStyles(); + + return ; +} + const useStyles = createStyles((theme) => ({ label: { fontSize: "0.875rem", diff --git a/packages/renderer/src/components/atoms/ToggleButton/ToggleButton.stories.tsx b/packages/renderer/src/components/atoms/ToggleButton/ToggleButton.stories.tsx new file mode 100644 index 0000000..1d3ade6 --- /dev/null +++ b/packages/renderer/src/components/atoms/ToggleButton/ToggleButton.stories.tsx @@ -0,0 +1,33 @@ +// ============================================================================= +// 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 { ToggleButton } from "./ToggleButton"; + +export default { + title: "atoms/ToggleButton", + component: ToggleButton, +} as ComponentMeta; + +const Template: ComponentStory = (args) => { + return ; +}; + +export const Default = Template.bind({}); + +Default.args = { + labels: ["Sell XMR", "Buy XMR"], +}; diff --git a/packages/renderer/src/components/atoms/ToggleButton/ToggleButton.test.tsx b/packages/renderer/src/components/atoms/ToggleButton/ToggleButton.test.tsx new file mode 100644 index 0000000..74712c3 --- /dev/null +++ b/packages/renderer/src/components/atoms/ToggleButton/ToggleButton.test.tsx @@ -0,0 +1,38 @@ +// ============================================================================= +// 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 { ToggleButton } from "./ToggleButton"; + +describe("atoms::ToggleButton", () => { + it("renders without exploding", () => { + const { asFragment, unmount } = render( + + ); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); + + it("renders all tabs", () => { + const { unmount } = render( + + ); + expect(screen.queryByText("Sell XMR")).toBeInTheDocument(); + expect(screen.queryByText("Buy XMR")).toBeInTheDocument(); + unmount(); + }); +}); diff --git a/packages/renderer/src/components/atoms/ToggleButton/ToggleButton.tsx b/packages/renderer/src/components/atoms/ToggleButton/ToggleButton.tsx new file mode 100644 index 0000000..b3cc40a --- /dev/null +++ b/packages/renderer/src/components/atoms/ToggleButton/ToggleButton.tsx @@ -0,0 +1,97 @@ +// ============================================================================= +// 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, Tabs } from "@mantine/core"; + +interface ToggleButtonProps { + labels: Array; + onChange?: (selectedIndex: number) => void; + active?: number; +} + +export function ToggleButton({ labels, onChange, active }: ToggleButtonProps) { + const { classes } = useStyles(); + + const handleChange = (tabIndex: number) => { + onChange && onChange(tabIndex); + }; + return ( + + {labels.map((label, index) => ( + + ))} + + ); +} + +const useStyles = createStyles((theme) => ({ + tabControl: { + backgroundColor: + theme.colorScheme === "dark" + ? theme.colors.dark[6] + : theme.colors.gray[0], + border: `0 solid ${ + theme.colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[2] + }`, + borderBottomWidth: 1, + borderTopWidth: 1, + color: + theme.colorScheme === "dark" + ? theme.colors.dark[0] + : theme.colors.gray[9], + fontSize: theme.fontSizes.md, + fontWeight: 500, + padding: `${theme.spacing.lg}px ${theme.spacing.md}px`, + + "&:not(:first-of-type)": { + borderLeft: 0, + }, + "&:first-of-type": { + borderTopLeftRadius: theme.radius.md, + borderBottomLeftRadius: theme.radius.md, + borderLeftWidth: 1, + }, + "&:last-of-type": { + borderTopRightRadius: theme.radius.md, + borderBottomRightRadius: theme.radius.md, + borderRightWidth: 1, + }, + }, + tabActive: { + color: theme.white, + position: "relative", + + "&:before": { + backgroundColor: theme.colors.blue[6], + borderRadius: theme.radius.md, + bottom: -1, + content: `""`, + left: -1, + position: "absolute", + right: -1, + top: -1, + }, + }, + tabInner: { + position: "relative", + zIndex: 1, + }, +})); diff --git a/packages/renderer/src/components/atoms/ToggleButton/__snapshots__/ToggleButton.test.tsx.snap b/packages/renderer/src/components/atoms/ToggleButton/__snapshots__/ToggleButton.test.tsx.snap new file mode 100644 index 0000000..5d24e49 --- /dev/null +++ b/packages/renderer/src/components/atoms/ToggleButton/__snapshots__/ToggleButton.test.tsx.snap @@ -0,0 +1,54 @@ +// Vitest Snapshot v1 + +exports[`atoms::ToggleButton > renders without exploding 1`] = ` + +
+
+
+ + +
+
+
+
+`; diff --git a/packages/renderer/src/components/molecules/Table/EditableTable.stories.tsx b/packages/renderer/src/components/molecules/Table/EditableTable.stories.tsx new file mode 100644 index 0000000..1abe387 --- /dev/null +++ b/packages/renderer/src/components/molecules/Table/EditableTable.stories.tsx @@ -0,0 +1,154 @@ +// ============================================================================= +// 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, useEffect } from "react"; +import type { ColumnDef } from "@tanstack/react-table"; +import { createTable } from "@tanstack/react-table"; +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import { Table } from "./Table"; + +export default { + title: "atoms/Table/EditableTable", + component: Table, +} as ComponentMeta; + +const Template: ComponentStory = () => { + return ( + { + console.log(values); + }} + /> + ); +}; + +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().setTableMetaType<{ + updateData: (rowIndex: number, columnId: string, value: unknown) => void; +}>(); +type TableGenerics = typeof table.generics; + +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: () => Last Name, + 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: () => Visits, + 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 = [ + { + 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, + }, +]; + +const defaultColumn: Partial> = { + cell: ({ getValue, row: { index }, column: { id }, instance }) => { + const initialValue = getValue(); + const [value, setValue] = useState(initialValue); + + const onBlur = () => { + instance.options.meta?.updateData(index, id, value); + }; + + useEffect(() => { + setValue(initialValue); + }, [initialValue]); + + return ( + setValue(e.target.value)} + onBlur={onBlur} + /> + ); + }, +}; diff --git a/packages/renderer/src/components/molecules/Table/Table.tsx b/packages/renderer/src/components/molecules/Table/Table.tsx index 50b54d2..48b4e55 100644 --- a/packages/renderer/src/components/molecules/Table/Table.tsx +++ b/packages/renderer/src/components/molecules/Table/Table.tsx @@ -26,10 +26,20 @@ import { TableProvider } from "./use-table-context"; import { TableHeader } from "./TableHeader"; import { TableBody } from "./TableBody"; import { useStyles } from "./Table.style"; +import { updateTableCell } from "./_utils"; export function Table(props: TableProps) { const { classes, cx } = useStyles(); - const { table, columns, data, tableWrap, variant, state } = props; + const { + table, + columns, + data, + tableWrap, + variant, + onEditableDataChange, + defaultColumn, + state, + } = props; const tableInstance = useTableInstance(table, { data, @@ -37,6 +47,17 @@ export function Table(props: TableProps) { state, getCoreRowModel: getCoreRowModel(), getExpandedRowModel: getExpandedRowModel(), + meta: { + updateData: (rowIndex: number, columnId: string, value: unknown) => { + const newData = updateTableCell(data, rowIndex, columnId, value); + onEditableDataChange && onEditableDataChange(newData); + }, + }, + ...(defaultColumn + ? { + defaultColumn, + } + : {}), }); return ( diff --git a/packages/renderer/src/components/molecules/Table/TableBody.tsx b/packages/renderer/src/components/molecules/Table/TableBody.tsx index 620a36c..6459d94 100644 --- a/packages/renderer/src/components/molecules/Table/TableBody.tsx +++ b/packages/renderer/src/components/molecules/Table/TableBody.tsx @@ -20,7 +20,7 @@ import { useTableContext } from "./use-table-context"; export function TableBody() { const { table, - props: { rowSubComponent }, + props: { rowSubComponent, onRowClick }, } = useTableContext(); return ( @@ -31,6 +31,7 @@ export function TableBody() { key={row.id} onClick={() => { row.toggleExpanded(); + onRowClick && onRowClick(row); }} > {row.getVisibleCells().map((cell) => ( diff --git a/packages/renderer/src/components/molecules/Table/_types.tsx b/packages/renderer/src/components/molecules/Table/_types.tsx index cfff245..33d943b 100644 --- a/packages/renderer/src/components/molecules/Table/_types.tsx +++ b/packages/renderer/src/components/molecules/Table/_types.tsx @@ -18,11 +18,13 @@ import type { ColumnDef, Row, TableState } from "@tanstack/react-table"; import type { TableProps as MTableProps } from "@mantine/core"; +// TODO: Add type or generic export interface TableProps { columns: Array>; table: any; data: Array; state?: Partial; + defaultColumn?: any; showHeader?: boolean; showFooter?: boolean; @@ -31,6 +33,11 @@ export interface TableProps { tableWrap?: MTableProps; variant?: TableVariant; + + onEditableDataChange?: (v: Array) => void; + + pointerRow?: boolean; + onRowClick?: (column: any) => void; } export enum TableVariant { diff --git a/packages/renderer/src/components/molecules/Table/_utils.ts b/packages/renderer/src/components/molecules/Table/_utils.ts new file mode 100644 index 0000000..89fdde2 --- /dev/null +++ b/packages/renderer/src/components/molecules/Table/_utils.ts @@ -0,0 +1,34 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +export const updateTableCell = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: Array, + rowIndex: number, + columnId: string, + value: unknown +) => { + return data.map((row, index) => { + if (index === rowIndex) { + return { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ...data[rowIndex]!, + [columnId]: value, + }; + } + return row; + }); +}; diff --git a/packages/renderer/src/components/molecules/Table/cells/CheckboxCell.stories.tsx b/packages/renderer/src/components/molecules/Table/cells/CheckboxCell.stories.tsx new file mode 100644 index 0000000..e80b9a7 --- /dev/null +++ b/packages/renderer/src/components/molecules/Table/cells/CheckboxCell.stories.tsx @@ -0,0 +1,128 @@ +// ============================================================================= +// 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"; +import { CheckboxCell } from "./CheckboxCell"; + +export default { + title: "atoms/Table/CheckboxCell", + component: Table, +} as ComponentMeta; + +const Template: ComponentStory = (args) => { + return ( +
{ + console.log(values); + }} + /> + ); +}; + +export const Default = Template.bind({}); + +interface Person { + firstName: string; + lastName: string; + age: number; + visits: number; + status: string; + progress: number; +} + +const table = createTable().setRowType().setTableMetaType<{ + updateData: (rowIndex: number, columnId: string, value: unknown) => void; +}>(); + +const columns = [ + table.createGroup({ + header: "Name", + footer: (props) => props.column.id, + columns: [ + table.createDataColumn("firstName", { + cell: (params) => , + footer: (props) => props.column.id, + }), + table.createDataColumn((row) => row.lastName, { + id: "lastName", + cell: (info) => info.getValue(), + header: () => Last Name, + 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: () => Visits, + 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, + }), + ], + }), + ], + }), +]; + +Default.args = { + data: [ + { + 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, + }, + ] as Array, +}; diff --git a/packages/renderer/src/components/molecules/Table/cells/CheckboxCell.tsx b/packages/renderer/src/components/molecules/Table/cells/CheckboxCell.tsx new file mode 100644 index 0000000..ae767f5 --- /dev/null +++ b/packages/renderer/src/components/molecules/Table/cells/CheckboxCell.tsx @@ -0,0 +1,59 @@ +// ============================================================================= +// 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 { CheckboxProps } from "@mantine/core"; +import { Checkbox } from "@mantine/core"; +import type { Cell, Column, Row, TableInstance } from "@tanstack/react-table"; +import { useState, useEffect } from "react"; + +// TODO: Add type or generic +interface CheckboxCellProps { + instance: TableInstance; + row: Row; + column: Column; + cell: Cell; + getValue: () => any; + checkboxProps?: CheckboxProps; +} + +export const CheckboxCell = ({ + getValue, + row: { index }, + column: { id }, + instance, + checkboxProps, +}: CheckboxCellProps) => { + const initialValue = getValue(); + const [value, setValue] = useState(initialValue); + + const onBlur = () => { + instance.options.meta?.updateData(index, id, value); + }; + useEffect(() => { + setValue(initialValue); + }, [initialValue]); + + return ( + setValue(e.target.checked)} + onBlur={onBlur} + {...checkboxProps} + /> + ); +}; diff --git a/packages/renderer/src/components/molecules/Table/cells/index.ts b/packages/renderer/src/components/molecules/Table/cells/index.ts new file mode 100644 index 0000000..6d2bead --- /dev/null +++ b/packages/renderer/src/components/molecules/Table/cells/index.ts @@ -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 "./CheckboxCell"; diff --git a/packages/renderer/src/components/molecules/Table/index.ts b/packages/renderer/src/components/molecules/Table/index.ts index fd5838c..0486aac 100644 --- a/packages/renderer/src/components/molecules/Table/index.ts +++ b/packages/renderer/src/components/molecules/Table/index.ts @@ -14,4 +14,6 @@ // limitations under the License. // ============================================================================= +export * from "./_types"; export * from "./Table"; +export * from "./cells"; diff --git a/packages/renderer/src/components/molecules/Table/use-table-context/index.tsx b/packages/renderer/src/components/molecules/Table/use-table-context/index.tsx index bcc7ca4..a1c4f74 100644 --- a/packages/renderer/src/components/molecules/Table/use-table-context/index.tsx +++ b/packages/renderer/src/components/molecules/Table/use-table-context/index.tsx @@ -20,6 +20,7 @@ import type { TableInstance } from "@tanstack/react-table"; import type { TableProps } from "../_types"; interface TableContextValue { + // TODO: Add type or generic table: TableInstance; props: TableProps; } diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterAccountsForm/MarketOffersFilterAccountsForm.stories.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterAccountsForm/MarketOffersFilterAccountsForm.stories.tsx new file mode 100644 index 0000000..1c104f4 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterAccountsForm/MarketOffersFilterAccountsForm.stories.tsx @@ -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 { MarketOffersFilterAccountsForm } from "./MarketOffersFilterAccountsForm"; + +export default { + title: "organisms/MarketOffersFilterAccountsForm", + component: MarketOffersFilterAccountsForm, +} as ComponentMeta; + +const Template: ComponentStory = () => { + return ; +}; + +export const Default = Template.bind({}); +Default.args = {}; diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterAccountsForm/MarketOffersFilterAccountsForm.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterAccountsForm/MarketOffersFilterAccountsForm.tsx new file mode 100644 index 0000000..b92ed98 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterAccountsForm/MarketOffersFilterAccountsForm.tsx @@ -0,0 +1,197 @@ +// ============================================================================= +// 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 } from "react-intl"; +import { Grid, Text, Checkbox, createStyles, Group } from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { Button, TextButton } from "@atoms/Buttons"; +import { NumberInput } from "@atoms/TextInput"; +import { LangKeys } from "@constants/lang"; +import { useOffersFilterState } from "@src/state/offersFilter"; +import { transformToForm } from "@src/utils/misc"; + +interface MarketOffersFilterAccountsFormProps { + onSubmit?: (values: MarketOffersFilterAccountsForm) => void; +} + +export function MarketOffersFilterAccountsForm({ + onSubmit, +}: MarketOffersFilterAccountsFormProps) { + const { classes } = useStyles(); + const [offersState, setOffersState] = useOffersFilterState(); + + const form = useForm({ + initialValues: { + ...initialValues, + // We only care about the fields in the form and remove other fields. + // Previously unfilled optional values come as null, so remove those as well. + ...transformToForm(offersState, initialValues), + }, + }); + + const handleClearFilter = () => { + form.setValues({ ...initialValues }); + }; + return ( +
{ + setOffersState((oldFilter) => ({ + ...oldFilter, + ...values, + })); + onSubmit && onSubmit(values); + })} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + rightSectionWidth={50} + /> + + + + + + + + + + + + + + + + + + } + rightSectionWidth={65} + /> + + + + + + + + + + + ); +} + +interface MarketOffersFilterAccountsForm { + signedAccounts: boolean; + minimumTradesAmount?: number | null; + minimumAccountAge?: number | null; +} + +const useStyles = createStyles((theme) => ({ + footer: { + paddingTop: theme.spacing.xl, + paddingLeft: theme.spacing.xl, + paddingRight: theme.spacing.xl, + borderTop: `1px solid ${theme.colors.gray[1]}`, + marginTop: theme.spacing.xl, + marginLeft: theme.spacing.xl * -1, + marginRight: theme.spacing.xl * -1, + }, + clearFilterBtn: { + fontSize: theme.spacing.lg, + }, +})); + +const initialValues = { + signedAccounts: false, + minimumAccountAge: undefined, + minimumTradesAmount: undefined, +}; diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterAccountsForm/index.ts b/packages/renderer/src/components/organisms/MarketOffersFilterAccountsForm/index.ts new file mode 100644 index 0000000..826b689 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterAccountsForm/index.ts @@ -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 "./MarketOffersFilterAccountsForm"; diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterAmountForm/MarketOffersFilterAmountForm.stories.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterAmountForm/MarketOffersFilterAmountForm.stories.tsx new file mode 100644 index 0000000..5bde3d3 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterAmountForm/MarketOffersFilterAmountForm.stories.tsx @@ -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 { MarketOffersFilterAmountForm } from "./MarketOffersFilterAmountForm"; + +export default { + title: "organisms/MarketOffersFilterAmountForm", + component: MarketOffersFilterAmountForm, +} as ComponentMeta; + +const Template: ComponentStory = () => { + return ; +}; + +export const Default = Template.bind({}); +Default.args = {}; diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterAmountForm/MarketOffersFilterAmountForm.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterAmountForm/MarketOffersFilterAmountForm.tsx new file mode 100644 index 0000000..c0bd0bd --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterAmountForm/MarketOffersFilterAmountForm.tsx @@ -0,0 +1,188 @@ +// ============================================================================= +// 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 } from "react-intl"; +import { createStyles, Grid, Group, Text } from "@mantine/core"; +import { useForm } from "@mantine/hooks"; +import { NumberInput } from "@atoms/TextInput"; +import { Button, TextButton } from "@atoms/Buttons"; +import { useOffersFilterState } from "@src/state/offersFilter"; +import { transformToForm } from "@utils/misc"; +import { LangKeys } from "@constants/lang"; + +interface MarketOffersFilterAmountFormProps { + onSubmit?: (values: MarketOffersFilterAmountFormValues) => void; +} + +export function MarketOffersFilterAmountForm({ + onSubmit, +}: MarketOffersFilterAmountFormProps) { + const { classes } = useStyles(); + const [offersState, setOffersState] = useOffersFilterState(); + + const form = useForm({ + initialValues: { + ...initialValues, + // We only care about the fields in the form and remove other fields. + // Previously unfilled optional values come as null, so remove those as well. + ...transformToForm(offersState, initialValues), + }, + }); + + const handleCreateFilter = () => { + form.setValues({ ...initialValues }); + }; + + return ( +
{ + setOffersState((oldFilter) => ({ + ...oldFilter, + ...values, + })); + onSubmit && onSubmit(values); + })} + > + + + + + + + + + + + + + EUR + + } + rightSectionWidth={45} + mb="lg" + {...form.getInputProps("minimumBaseCurrencyAmount")} + /> + + XMR + + } + rightSectionWidth={45} + {...form.getInputProps("minimumCryptoAmount")} + /> + + + + + + + + + + + + {" "} + + + XMR + + } + rightSectionWidth={45} + mb="lg" + /> + + EUR + + } + rightSectionWidth={45} + /> + + + + + + + + + + + ); +} + +const useStyles = createStyles((theme) => ({ + footer: { + borderTop: `1px solid ${theme.colors.gray[1]}`, + marginLeft: theme.spacing.xl * -1, + marginRight: theme.spacing.xl * -1, + marginTop: theme.spacing.xl, + paddingLeft: theme.spacing.xl, + paddingRight: theme.spacing.xl, + paddingTop: theme.spacing.xl, + }, + clearFilterBtn: { + fontSize: theme.spacing.lg, + }, +})); + +interface MarketOffersFilterAmountFormValues { + minimumCryptoAmount?: number | null; + minimumBaseCurrencyAmount?: number | null; + maximumCryptoAmount?: number | null; + maximumBaseCurrencyAmount?: number | null; +} + +const initialValues = { + minimumCryptoAmount: undefined, + minimumBaseCurrencyAmount: undefined, + maximumCryptoAmount: undefined, + maximumBaseCurrencyAmount: undefined, +}; diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterAmountForm/index.ts b/packages/renderer/src/components/organisms/MarketOffersFilterAmountForm/index.ts new file mode 100644 index 0000000..fb92b2c --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterAmountForm/index.ts @@ -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 "./MarketOffersFilterAmountForm"; diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterBar/MarketOffersFilterBar.stories.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterBar/MarketOffersFilterBar.stories.tsx new file mode 100644 index 0000000..492234e --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterBar/MarketOffersFilterBar.stories.tsx @@ -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 { MarketOffersFilterBar } from "."; + +export default { + title: "organisms/MarketOffersFilterBar", + component: MarketOffersFilterBar, +} as ComponentMeta; + +const Template: ComponentStory = () => { + return ; +}; + +export const Default = Template.bind({}); +Default.args = {}; diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterBar/MarketOffersFilterBar.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterBar/MarketOffersFilterBar.tsx new file mode 100644 index 0000000..41b27e7 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterBar/MarketOffersFilterBar.tsx @@ -0,0 +1,200 @@ +// ============================================================================= +// 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 { Divider, Group, createStyles, Text, Box } from "@mantine/core"; +import { FormattedMessage, useIntl } from "react-intl"; +import { isEmpty } from "lodash"; +import { + useMarketOffersPairModal, + useMarketOffersPaymentMethods, + useMarketOffersAmountModal, + useMarketOffersAccountModal, +} from "./hooks"; +import { MarketOffersFilterButton } from "./MarketOffersFilterButton"; +import { + useAccountDetailsLabel, + useMarketOffersFilterAmountLabel, +} from "./_hooks"; +import { LangKeys } from "@constants/lang"; +import { ReactComponent as BtcIcon } from "@assets/btc.svg"; +import { useOffersFilterState } from "@src/state/offersFilter"; +import { ToggleButton } from "@atoms/ToggleButton/ToggleButton"; + +export function MarketOffersFilterBar() { + const { formatMessage } = useIntl(); + const { classes } = useStyles(); + const [offersFilter, setOffersFilter] = useOffersFilterState(); + + // Market offers pair filter modal. + const marketOffersPairModal = useMarketOffersPairModal(); + + // Market offers payment methods filter modal. + const marketOffersPaymentMethodsModal = useMarketOffersPaymentMethods(); + + // Market offers account filter modal. + const marketOffersAccountModal = useMarketOffersAccountModal(); + + // Market offers amount filter modal. + const marketOffersAmountModal = useMarketOffersAmountModal(); + + const accountDetailsLabel = useAccountDetailsLabel(); + const filterAmountLabel = useMarketOffersFilterAmountLabel(); + + // Handles the buy/sell switch change. + const handleBuySellSwitch = (tabIndex: number) => { + setOffersFilter((filter) => ({ + ...filter, + direction: tabIndex === 0 ? "sell" : "buy", + })); + }; + const handleParisBtnClick = () => { + marketOffersPairModal.openModal(); + }; + const handlePaymentMethodsBtnClick = () => { + marketOffersPaymentMethodsModal.openModal(); + }; + const handleAccountBtnClick = () => { + marketOffersAccountModal.openModal(); + }; + const handleAmountBtnClick = () => { + marketOffersAmountModal.openModal(); + }; + + return ( + + + + + + + + + + + + {!isEmpty(offersFilter.assetCode) ? ( + offersFilter.assetCode?.toUpperCase() + ) : ( + + )} + + + + + + {filterAmountLabel || ( + + )} + + + + + + + + {accountDetailsLabel || ( + + )} + + + + + + + + + + + + + + + ); +} + +const useStyles = createStyles((theme) => ({ + root: { + background: theme.white, + borderBottom: `1px solid ${theme.colors.gray[3]}`, + minHeight: 84, + padding: "18px 22px", + }, + divider: { + marginBottom: "auto", + marginTop: "auto", + height: 28, + }, +})); diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterBar/MarketOffersFilterButton.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterBar/MarketOffersFilterButton.tsx new file mode 100644 index 0000000..7ba10c5 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterBar/MarketOffersFilterButton.tsx @@ -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 type { ButtonProps as MButtonProps, ButtonVariant } from "@mantine/core"; +import { Button, Box, createStyles } from "@mantine/core"; + +export interface ButtonProps extends MButtonProps<"button"> { + active?: boolean; + bubbleText?: string; +} + +interface MarketOffersFilterButtonStyleProps { + active: boolean; + variant: ButtonVariant; +} + +export function MarketOffersFilterButton(props: ButtonProps) { + const { + bubbleText, + className, + active, + variant = "outline", + classNames, + ...others + } = props; + + const { cx, classes } = useStyles( + { + active: active || false, + variant: variant, + }, + { + name: "MarketOffersFilterButton", + classNames, + } + ); + + return ( + + ); +} + +const useStyles = createStyles( + (theme, { active }: MarketOffersFilterButtonStyleProps) => ({ + root: { + paddingLeft: theme.spacing.sm, + paddingRight: theme.spacing.sm, + position: "relative", + }, + outline: { + borderColor: active ? "#111" : "#E8E7EC", + borderWidth: active ? 2 : 1, + color: "#111", + }, + bubbleText: { + backgroundColor: "#111", + borderRadius: 15, + boxShadow: "0 0 0 2px #fff", + color: "#fff", + position: "absolute", + padding: "2px 4px", + top: -5, + right: -5, + fontSize: 10, + lineHeight: 1, + minWidth: 15, + }, + }) +); diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterBar/_hooks.ts b/packages/renderer/src/components/organisms/MarketOffersFilterBar/_hooks.ts new file mode 100644 index 0000000..b398133 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterBar/_hooks.ts @@ -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 { useIntl } from "react-intl"; +import { LangKeys } from "@constants/lang"; +import { useOffersFilterState } from "@src/state/offersFilter"; + +/** + * Retrieve the active label of account details button. + * @returns {string} + */ +export function useAccountDetailsLabel() { + const { formatMessage } = useIntl(); + const [offersFilterState] = useOffersFilterState(); + + return [ + [ + formatMessage({ + id: LangKeys.MarketOffersSigned, + defaultMessage: "Signed", + }), + offersFilterState.signedAccounts, + ], + [ + formatMessage( + { + id: LangKeys.MarketOffersTradesAmount, + defaultMessage: ">{value} days", + }, + { value: offersFilterState.minimumTradesAmount } + ), + offersFilterState.minimumTradesAmount, + ], + [ + formatMessage( + { + id: LangKeys.MarketOffersDaysAge, + defaultMessage: ">{value} days", + }, + { + value: offersFilterState.minimumAccountAge, + } + ), + offersFilterState.minimumAccountAge, + ], + ] + .filter((option) => option[1]) + .map((option) => option[0]) + .join(", "); +} + +/** + * Retrieve the active label of amount button. + * @returns {string} + */ +export function useMarketOffersFilterAmountLabel() { + const [offersFilterState] = useOffersFilterState(); + + if ( + !offersFilterState.minimumBaseCurrencyAmount && + !offersFilterState.maximumBaseCurrencyAmount + ) { + return ""; + } + const fromAmount = offersFilterState.minimumBaseCurrencyAmount || "~"; + const toAmount = offersFilterState.maximumBaseCurrencyAmount || "~"; + + return `${fromAmount} - ${toAmount} XMR`; +} diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/index.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/index.tsx new file mode 100644 index 0000000..6a71e0a --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/index.tsx @@ -0,0 +1,20 @@ +// ============================================================================= +// 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 "./useMarketOffersPairModal"; +export * from "./useMarketOffersPaymentMethods"; +export * from "./useMarketOffersAccountModal"; +export * from "./useMarketOffersAmountModal"; diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersAccountModal.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersAccountModal.tsx new file mode 100644 index 0000000..975f2bc --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersAccountModal.tsx @@ -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. +// ============================================================================= + +import { useModals } from "@mantine/modals"; +import { MarketOffersFilterAccountsForm } from "@organisms/MarketOffersFilterAccountsForm"; + +export function useMarketOffersAccountModal() { + const modals = useModals(); + + return { + openModal: () => { + const modalId = modals.openModal({ + title: "Amount", + children: ( + { + modals.closeModal(modalId); + }} + /> + ), + size: "lg", + withCloseButton: true, + }); + }, + }; +} diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersAmountModal.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersAmountModal.tsx new file mode 100644 index 0000000..931bbaf --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersAmountModal.tsx @@ -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. +// ============================================================================= + +import { useModals } from "@mantine/modals"; +import { MarketOffersFilterAmountForm } from "@organisms/MarketOffersFilterAmountForm"; + +export function useMarketOffersAmountModal() { + const modals = useModals(); + + return { + openModal: () => { + const modalId = modals.openModal({ + title: "Amount", + children: ( + { + modals.closeModal(modalId); + }} + /> + ), + size: "lg", + withCloseButton: false, + }); + }, + }; +} diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersPairModal.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersPairModal.tsx new file mode 100644 index 0000000..4bf0229 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersPairModal.tsx @@ -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 { createStyles } from "@mantine/core"; +import { useModals } from "@mantine/modals"; +import { MarketOffersTradingPair } from "@organisms/MarketOffersTradingPair"; + +export function useMarketOffersPairModal() { + const modals = useModals(); + const { classes } = useStyles(); + + return { + openModal: () => { + const modalId = modals.openModal({ + title: "Select trading pair", + children: ( + { + modals.closeModal(modalId); + }} + /> + ), + withCloseButton: true, + size: 570, + classNames: classes, + }); + }, + }; +} + +const useStyles = createStyles((theme) => ({ + title: { + fontSize: theme.fontSizes.md, + fontWeight: 600, + }, + header: { + marginBottom: 10, + marginTop: -10, + }, + body: { + marginLeft: theme.spacing.sm * -2, + marginRight: theme.spacing.sm * -2, + marginBottom: theme.spacing.sm * -2, + }, +})); diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersPaymentMethods.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersPaymentMethods.tsx new file mode 100644 index 0000000..f544860 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterBar/hooks/useMarketOffersPaymentMethods.tsx @@ -0,0 +1,53 @@ +// ============================================================================= +// 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 } from "@mantine/core"; +import { useModals } from "@mantine/modals"; +import { MarketOffersFilterPaymentMethods } from "@organisms/MarketOffersFilterPaymentMethods"; + +export function useMarketOffersPaymentMethods() { + const modals = useModals(); + const { classes } = useStyles(); + + return { + openModal: () => { + modals.openModal({ + title: "Filter on payment methods", + children: , + size: 970, + withCloseButton: true, + classNames: classes, + }); + }, + }; +} + +const useStyles = createStyles((theme) => ({ + root: { + padding: "0 !important", + }, + modal: { + padding: "0 !important", + }, + title: { + fontSize: theme.fontSizes.md, + fontWeight: 600, + }, + header: { + padding: "12px 20px", + margin: 0, + }, +})); diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterBar/index.ts b/packages/renderer/src/components/organisms/MarketOffersFilterBar/index.ts new file mode 100644 index 0000000..03ae94d --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterBar/index.ts @@ -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 "./MarketOffersFilterBar"; diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterPaymentMethods/MarketOffersFilterPaymentMethods.tsx b/packages/renderer/src/components/organisms/MarketOffersFilterPaymentMethods/MarketOffersFilterPaymentMethods.tsx new file mode 100644 index 0000000..3030323 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterPaymentMethods/MarketOffersFilterPaymentMethods.tsx @@ -0,0 +1,71 @@ +// ============================================================================= +// 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 { includes } from "lodash"; +import type { FC } from "react"; +import { useMemo } from "react"; +import type { TMarketOfferPaymentMethod } from "@organisms/MarketOffersPaymentMethodsTable"; +import { MarketOffersPaymentMethodsTable } from "@organisms/MarketOffersPaymentMethodsTable"; +import { useOffersFilterState } from "@src/state/offersFilter"; +import { usePaymentMethods } from "@hooks/haveno/usePaymentMethods"; + +export function MarketOffersFilterPaymentMethodsLoaded() { + const { data: paymentMethods } = usePaymentMethods(); + const [filter, setFilter] = useOffersFilterState(); + + const tableData = useMemo( + () => + paymentMethods?.map((item) => ({ + ...item, + methodChecked: includes(filter.paymentMethods, item.methodKey), + })), + [paymentMethods] + ); + + const handleEditableDataChange = ( + newData: Array + ) => { + setFilter((oldQuery) => ({ + ...oldQuery, + paymentMethods: newData + .filter((payment) => payment.methodChecked) + .map((payment) => payment.methodKey), + })); + }; + if (!tableData) { + return null; + } + return ( + + ); +} + +const MarketOffersFilterPaymentMethodsBoot: FC = ({ children }) => { + const { isLoading } = usePaymentMethods(); + + return isLoading ? <>Loading : <>{children}; +}; + +export function MarketOffersFilterPaymentMethods() { + return ( + + + + ); +} diff --git a/packages/renderer/src/components/organisms/MarketOffersFilterPaymentMethods/index.ts b/packages/renderer/src/components/organisms/MarketOffersFilterPaymentMethods/index.ts new file mode 100644 index 0000000..c3507cd --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersFilterPaymentMethods/index.ts @@ -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 "./MarketOffersFilterPaymentMethods"; diff --git a/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTable.stories.tsx b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTable.stories.tsx new file mode 100644 index 0000000..6191a90 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTable.stories.tsx @@ -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 { ComponentStory, ComponentMeta } from "@storybook/react"; +import type { TMarketOfferPaymentMethod } from "./_types"; +import { MarketOffersPaymentMethodsTable } from "."; + +export default { + title: "organisms/MarketOffersPaymentMethodsTable", + component: MarketOffersPaymentMethodsTable, +} as ComponentMeta; + +const Template: ComponentStory = ( + args +) => { + return ; +}; + +export const Default = Template.bind({}); + +Default.args = { + data: [ + { + methodName: "Celpay", + methodKey: "celpay", + rateTradeLimit: 20, + rateTradeLimitCurrency: "XMR", + info: "USA", + }, + { + methodName: "ACH", + methodKey: "ach", + rateTradeLimit: 20, + rateTradeLimitCurrency: "XMR", + info: "Global (AUS, TRY, USD)", + }, + { + methodName: "Cash by mail", + methodKey: "cash-by-mail", + rateTradeLimit: 20, + rateTradeLimitCurrency: "XMR", + info: "Spain", + }, + { + methodName: "Domestic Wire Transfer", + methodKey: "domestic-wire-transfer", + rateTradeLimit: 20, + rateTradeLimitCurrency: "XMR", + info: "Global", + }, + ] as Array, +}; diff --git a/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTable.test.tsx b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTable.test.tsx new file mode 100644 index 0000000..a9aba22 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTable.test.tsx @@ -0,0 +1,95 @@ +// ============================================================================= +// 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 type { TMarketOfferPaymentMethod } from "./_types"; +import { MarketOffersPaymentMethodsTable } from "."; +import { AppProviders } from "@atoms/AppProviders"; + +describe("molecules::MarketOffersPaymentMethodsTable", () => { + it("renders without exploding", () => { + const { asFragment, unmount } = render( + + + + ); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); + + it("renders all columns", () => { + const { unmount } = render( + + + + ); + expect(screen.queryByText("Method")).toBeInTheDocument(); + expect(screen.queryByText("Rate Trade Limit")).toBeInTheDocument(); + expect(screen.queryByText("Info")).toBeInTheDocument(); + unmount(); + }); + + it("renders cells of method name ", () => { + const { unmount } = render( + + + + ); + expect(screen.queryByText("Celpay")).toBeInTheDocument(); + expect(screen.queryByText("ACH")).toBeInTheDocument(); + unmount(); + }); + + it("renders cells of rate trade limit ", () => { + const { unmount } = render( + + + + ); + expect(screen.queryByText("20 XMR")).toBeInTheDocument(); + expect(screen.queryByText("40 XMR")).toBeInTheDocument(); + unmount(); + }); + + it("renders cells of rate trade limit ", () => { + const { unmount } = render( + + + + ); + expect(screen.queryByText("USA")).toBeInTheDocument(); + expect(screen.queryByText("Global (AUS, TRY, USD)")).toBeInTheDocument(); + unmount(); + }); +}); + +const data = [ + { + methodName: "Celpay", + methodKey: "celpay", + rateTradeLimit: 20, + rateTradeLimitCurrency: "XMR", + info: "USA", + }, + { + methodName: "ACH", + methodKey: "ach", + rateTradeLimit: 40, + rateTradeLimitCurrency: "XMR", + info: "Global (AUS, TRY, USD)", + }, +] as Array; diff --git a/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTable.tsx b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTable.tsx new file mode 100644 index 0000000..c4c77a0 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTable.tsx @@ -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 { createTable } from "@tanstack/react-table"; +import { createStyles } from "@mantine/core"; +import { useIntl } from "react-intl"; +import { + MarketOffersPaymentMethodsInfo, + MarketOffersPaymentMethodsLimit, +} from "./MarketOffersPaymentMethodsTableCells"; +import type { TMarketOfferPaymentMethod } from "./_types"; +import type { TableProps } from "@molecules/Table"; +import { CheckboxCell, Table } from "@molecules/Table"; +import { LangKeys } from "@constants/lang"; + +const table = createTable().setRowType(); + +interface MarketOffersPaymentMethodsTableProps extends Partial { + data: Array; +} + +export function MarketOffersPaymentMethodsTable({ + data, + ...rest +}: MarketOffersPaymentMethodsTableProps) { + const { classes } = useStyles(); + const columns = useMarketOffersPaymentMethodsColumns(); + + return ( +
+ ); +} + +const useMarketOffersPaymentMethodsColumns = () => { + const { formatMessage } = useIntl(); + + return [ + table.createDataColumn("methodChecked", { + id: "methodChecked", + header: " ", + cell: (props) => ( + + ), + size: 30, + }), + table.createDataColumn("methodName", { + id: "methodName", + header: formatMessage({ + id: LangKeys.MarketPaymentMethodColMethodName, + defaultMessage: "Method", + }), + size: 300, + }), + table.createDataColumn("rateTradeLimit", { + id: "rateTradeLimit", + header: formatMessage({ + id: LangKeys.MarketPaymentMethodColRateTradeLimit, + defaultMessage: "Rate Trade Limit", + }), + size: 400, + cell: ({ row }) => ( + + ), + }), + table.createDataColumn("info", { + id: "info", + header: formatMessage({ + id: LangKeys.MarketPaymentMethodColInfo, + defaultMessage: "Info", + }), + size: 400, + cell: ({ row }) => , + meta: { textAlign: "right" }, + }), + ]; +}; + +const useStyles = createStyles((theme) => ({ + root: { + thead: { + tr: { + th: { + color: theme.colors.gray[9], + fontSize: theme.fontSizes.xs, + paddingBottom: 8, + paddingTop: 8, + textTransform: "uppercase", + + "&:first-of-type": { + paddingLeft: theme.spacing.xl, + }, + "&:last-of-type": { + paddingRight: theme.spacing.xl, + }, + }, + }, + }, + tbody: { + tr: { + td: { + borderBottom: 0, + fontSize: theme.fontSizes.md, + + "&:first-of-type": { + paddingLeft: theme.spacing.xl, + }, + "&:last-of-type": { + paddingRight: theme.spacing.xl, + }, + }, + }, + }, + }, +})); diff --git a/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTableCells.tsx b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTableCells.tsx new file mode 100644 index 0000000..65412f4 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/MarketOffersPaymentMethodsTableCells.tsx @@ -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 { Text } from "@mantine/core"; +import type { TMarketOfferPaymentMethod } from "./_types"; +import { Currency } from "@atoms/Currency"; + +export function MarketOffersPaymentMethodsLimit({ + row, +}: { + row?: TMarketOfferPaymentMethod; +}) { + return ( + + {" "} + {row?.rateTradeLimitCurrency} + + ); +} + +export function MarketOffersPaymentMethodsInfo({ + row, +}: { + row?: TMarketOfferPaymentMethod; +}) { + return ( + + {row?.info} + + ); +} diff --git a/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/__snapshots__/MarketOffersPaymentMethodsTable.test.tsx.snap b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/__snapshots__/MarketOffersPaymentMethodsTable.test.tsx.snap new file mode 100644 index 0000000..7323786 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/__snapshots__/MarketOffersPaymentMethodsTable.test.tsx.snap @@ -0,0 +1,150 @@ +// Vitest Snapshot v1 + +exports[`molecules::MarketOffersPaymentMethodsTable > renders without exploding 1`] = ` + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + Method + + Rate Trade Limit + + Info +
+
+
+ + + + +
+
+
+ Celpay + +
+ 20 XMR +
+
+
+ USA +
+
+
+
+ + + + +
+
+
+ ACH + +
+ 40 XMR +
+
+
+ Global (AUS, TRY, USD) +
+
+ +`; diff --git a/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/_types.ts b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/_types.ts new file mode 100644 index 0000000..913d3a5 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/_types.ts @@ -0,0 +1,24 @@ +// ============================================================================= +// 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 interface TMarketOfferPaymentMethod { + methodChecked?: boolean; + methodName: string; + methodKey: string; + rateTradeLimit: number; + rateTradeLimitCurrency: string; + info: string; +} diff --git a/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/index.ts b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/index.ts new file mode 100644 index 0000000..e6169b0 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersPaymentMethodsTable/index.ts @@ -0,0 +1,18 @@ +// ============================================================================= +// 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 "./MarketOffersPaymentMethodsTable"; +export type { TMarketOfferPaymentMethod } from "./_types"; diff --git a/packages/renderer/src/components/organisms/MarketOffersTradingPair/MarketOffersTradingPair.tsx b/packages/renderer/src/components/organisms/MarketOffersTradingPair/MarketOffersTradingPair.tsx new file mode 100644 index 0000000..e7f1182 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersTradingPair/MarketOffersTradingPair.tsx @@ -0,0 +1,76 @@ +// ============================================================================= +// 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 { useCallback } from "react"; +import type { FC } from "react"; +import type { Row } from "@tanstack/react-table"; +import type { TMarketTradingPairTable } from "@organisms/MarketOffersTradingPairTable"; +import { MarketOffersTradingPairTable } from "@organisms/MarketOffersTradingPairTable"; +import { useOffersFilterState } from "@src/state/offersFilter"; +import { useMarketsPairs } from "@hooks/haveno/useMarketPairs"; + +interface MarketOffersTradingPairProps { + onSubmit?: (row: Row) => void; +} + +export function MarketOffersTradingPair({ + onSubmit, +}: MarketOffersTradingPairProps) { + return ( + + + + ); +} + +interface MarketOffersTradingPairLoadedProps { + onSubmit?: (row: Row) => void; +} + +function MarketOffersTradingPairLoaded({ + onSubmit, +}: MarketOffersTradingPairLoadedProps) { + const { data: marketsPairs } = useMarketsPairs(); + const [, setOffersState] = useOffersFilterState(); + + const handleRowClick = useCallback( + (row: Row) => { + // Sync the selected pair to atom global filter state. + setOffersState((oldFilter) => ({ + ...oldFilter, + assetCode: row.original?.fromPair || "", + })); + onSubmit && onSubmit(row); + }, + [onSubmit] + ); + + if (!marketsPairs) { + return null; + } + return ( + + ); +} + +const MarketOffersTradingPairBoot: FC = ({ children }) => { + const { isLoading } = useMarketsPairs(); + + return isLoading ? <>Loading : <>{children}; +}; diff --git a/packages/renderer/src/components/organisms/MarketOffersTradingPair/index.ts b/packages/renderer/src/components/organisms/MarketOffersTradingPair/index.ts new file mode 100644 index 0000000..1c6b9b1 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersTradingPair/index.ts @@ -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 "./MarketOffersTradingPair"; diff --git a/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTable.stories.tsx b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTable.stories.tsx new file mode 100644 index 0000000..e8a2c7f --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTable.stories.tsx @@ -0,0 +1,69 @@ +// ============================================================================= +// 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 { MarketOffersTradingPairTable } from "./MarketOffersTradingPairTable"; +import type { TMarketOffersTradingPair } from "./_types"; + +export default { + title: "organisms/MarketOffersTradingPairTable", + component: MarketOffersTradingPairTable, +} as ComponentMeta; + +const Template: ComponentStory = ( + args +) => { + return ; +}; + +export const Default = Template.bind({}); + +Default.args = { + data: [ + { + fromPair: "EUR", + toPair: "XMR", + lastPrice: 101.122, + lastPriceCurrency: "EUR", + dayChangeRate: 12.12, + dayChangeVolume: 1222.123, + }, + { + fromPair: "EUR", + toPair: "XMR", + lastPrice: 101.122, + lastPriceCurrency: "EUR", + dayChangeRate: 12.12, + dayChangeVolume: 1222.123, + }, + { + fromPair: "EUR", + toPair: "XMR", + lastPrice: 101.122, + lastPriceCurrency: "EUR", + dayChangeRate: 12.12, + dayChangeVolume: 1222.123, + }, + { + fromPair: "EUR", + toPair: "XMR", + lastPrice: 101.122, + lastPriceCurrency: "EUR", + dayChangeRate: 12.12, + dayChangeVolume: 1222.123, + }, + ] as Array, +}; diff --git a/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTable.test.tsx b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTable.test.tsx new file mode 100644 index 0000000..105b2e2 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTable.test.tsx @@ -0,0 +1,87 @@ +// ============================================================================= +// 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 type { TMarketOffersTradingPair } from "./_types"; +import { MarketOffersTradingPairTable } from "./MarketOffersTradingPairTable"; +import { AppProviders } from "@atoms/AppProviders"; + +describe("molecules::MarketoffersTradingPairTable", () => { + it("renders without exploding", () => { + const { asFragment, unmount } = render( + + + + ); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); + + it("renders all columns", () => { + const { unmount } = render( + + + + ); + expect(screen.queryByText("Pair")).toBeInTheDocument(); + expect(screen.queryByText("Last Price")).toBeInTheDocument(); + expect(screen.queryByText("24th Change")).toBeInTheDocument(); + expect(screen.queryByText("24th Vol")).toBeInTheDocument(); + unmount(); + }); + + it("renders Pair cells", () => { + const { unmount } = render( + + + + ); + expect(screen.queryByText("EUR/XMR")).toBeInTheDocument(); + expect(screen.queryByText("USD/XMR")).toBeInTheDocument(); + unmount(); + }); + + it("renders Day Change Volume cells", () => { + const { unmount } = render( + + + + ); + expect(screen.queryByText("4,233.123")).toBeInTheDocument(); + expect(screen.queryByText("1,222.123")).toBeInTheDocument(); + unmount(); + }); +}); + +const data = [ + { + fromPair: "EUR", + toPair: "XMR", + lastPrice: 101.12, + lastPriceCurrency: "EUR", + dayChangeRate: 12.12, + dayChangeVolume: 4233.123, + }, + { + fromPair: "USD", + toPair: "XMR", + lastPrice: 105.12, + lastPriceCurrency: "EUR", + dayChangeRate: 83.12, + dayChangeVolume: 1222.123, + }, +] as Array; diff --git a/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTable.tsx b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTable.tsx new file mode 100644 index 0000000..bd8bfb2 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTable.tsx @@ -0,0 +1,143 @@ +// ============================================================================= +// 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 { createStyles } from "@mantine/core"; +import type { TMarketOffersTradingPair } from "./_types"; +import { marketTradingPairTable } from "./_types"; +import { + MarketOfferPair24thChange, + MarketOfferPairLastPriceCell, + MarketOfferPair24thChangeVolume, +} from "./MarketOffersTradingPairTableCells"; +import { pairColumnAccessor } from "./_utils"; +import type { TableProps } from "@molecules/Table"; +import { Table } from "@molecules/Table"; +import { LangKeys } from "@constants/lang"; + +export interface MarketOffersTradingPairTableProps extends Partial { + data: Array; +} + +export function MarketOffersTradingPairTable({ + data, + ...rest +}: MarketOffersTradingPairTableProps) { + const { classes } = useStyles(); + const columns = useMarketTradingPairsColumns(); + + return ( + + ); +} + +const useMarketTradingPairsColumns = () => { + const { formatMessage } = useIntl(); + + return [ + marketTradingPairTable.createDataColumn(pairColumnAccessor, { + id: "pair", + header: formatMessage({ + id: LangKeys.MarketTradingPairColPair, + defaultMessage: "Pair", + }), + size: 400, + }), + marketTradingPairTable.createDataColumn("lastPrice", { + id: "lastPrice", + header: formatMessage({ + id: LangKeys.MarketTradingPairColLastPrice, + defaultMessage: "Last Price", + }), + size: 400, + cell: ({ row }) => , + }), + marketTradingPairTable.createDataColumn("dayChangeRate", { + id: "dayChangeRate", + header: formatMessage({ + id: LangKeys.MarketTradingPairColDayChange, + defaultMessage: "24th Change", + }), + size: 400, + cell: () => , + meta: { textAlign: "right" }, + }), + marketTradingPairTable.createDataColumn("dayChangeVolume", { + id: "dayChangeVolume", + header: formatMessage({ + id: LangKeys.MarketTradingPairColDayChangeVolume, + defaultMessage: "24h Vol", + }), + size: 400, + cell: ({ row }) => ( + + ), + meta: { textAlign: "right" }, + }), + ]; +}; + +const useStyles = createStyles((theme) => ({ + root: { + paddingTop: 20, + paddingBottom: 0, + + thead: { + tr: { + th: { + color: theme.colors.gray[9], + fontSize: theme.fontSizes.xs, + paddingTop: 8, + paddingBottom: 8, + textTransform: "uppercase", + + "&:first-of-type": { + paddingLeft: theme.spacing.xl, + }, + "&:last-of-type": { + paddingRight: theme.spacing.xl, + }, + }, + }, + }, + tbody: { + tr: { + td: { + borderBottomColor: "transparent", + fontSize: theme.spacing.sm * 1.168, + fontWeight: 600, + + "&:first-of-type": { + paddingLeft: theme.spacing.xl, + }, + "&:last-of-type": { + paddingRight: theme.spacing.xl, + }, + }, + }, + }, + }, +})); diff --git a/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTableCells.tsx b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTableCells.tsx new file mode 100644 index 0000000..59a1ced --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/MarketOffersTradingPairTableCells.tsx @@ -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 { Box, Group } from "@mantine/core"; +import type { TMarketOffersTradingPair } from "./_types"; +import { AmountChange } from "@atoms/AmountChange/AmountChange"; +import { Currency } from "@atoms/Currency"; + +export function MarketOfferPairLastPriceCell({ + row, +}: { + row?: TMarketOffersTradingPair; +}) { + return ( + + {row?.lastPriceCurrency} + + + + + ); +} + +export function MarketOfferPair24thChange() { + return +3,5%; +} + +export function MarketOfferPair24thChangeVolume({ + row, +}: { + row?: TMarketOffersTradingPair; +}) { + return ( + + ); +} diff --git a/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/__snapshots__/MarketOffersTradingPairTable.test.tsx.snap b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/__snapshots__/MarketOffersTradingPairTable.test.tsx.snap new file mode 100644 index 0000000..389e035 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/__snapshots__/MarketOffersTradingPairTable.test.tsx.snap @@ -0,0 +1,118 @@ +// Vitest Snapshot v1 + +exports[`molecules::MarketoffersTradingPairTable > renders without exploding 1`] = ` + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ Pair + + Last Price + + 24th Change + + 24th Vol +
+ EUR/XMR + +
+
+ EUR +
+
+ 101.12 +
+
+
+
+ +3,5% +
+
+ 4,233.123 +
+ USD/XMR + +
+
+ EUR +
+
+ 105.12 +
+
+
+
+ +3,5% +
+
+ 1,222.123 +
+ +`; diff --git a/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/_types.ts b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/_types.ts new file mode 100644 index 0000000..bd9a29f --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/_types.ts @@ -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 { createTable } from "@tanstack/react-table"; + +export interface TMarketOffersTradingPair { + fromPair: string; + toPair: string; + lastPrice: number; + lastPriceCurrency: string; + dayChangeRate: number; + dayChangeVolume: number; +} + +export const marketTradingPairTable = + createTable().setRowType(); + +export type TMarketTradingPairTable = typeof marketTradingPairTable.generics; diff --git a/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/_utils.ts b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/_utils.ts new file mode 100644 index 0000000..9204a1b --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/_utils.ts @@ -0,0 +1,20 @@ +// ============================================================================= +// 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 { TMarketOffersTradingPair } from "./_types"; + +export const pairColumnAccessor = (row: TMarketOffersTradingPair): string => + `${row.fromPair}/${row.toPair}`; diff --git a/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/index.ts b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/index.ts new file mode 100644 index 0000000..d94cd17 --- /dev/null +++ b/packages/renderer/src/components/organisms/MarketOffersTradingPairTable/index.ts @@ -0,0 +1,21 @@ +// ============================================================================= +// 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 "./MarketOffersTradingPairTable"; +export type { + TMarketOffersTradingPair, + TMarketTradingPairTable, +} from "./_types"; diff --git a/packages/renderer/src/components/organisms/Navbar/_constants.tsx b/packages/renderer/src/components/organisms/Navbar/_constants.tsx index 88e3564..c55daaa 100644 --- a/packages/renderer/src/components/organisms/Navbar/_constants.tsx +++ b/packages/renderer/src/components/organisms/Navbar/_constants.tsx @@ -33,7 +33,7 @@ export const NAV_LINKS = [ { icon: , label: "Markets", - link: "/markets", + route: "/markets", }, { icon: , diff --git a/packages/renderer/src/constants/lang/LangKeys.ts b/packages/renderer/src/constants/lang/LangKeys.ts index 6570b3c..e72c99b 100644 --- a/packages/renderer/src/constants/lang/LangKeys.ts +++ b/packages/renderer/src/constants/lang/LangKeys.ts @@ -99,11 +99,53 @@ export enum LangKeys { AccountBackupRestoreBtn = "account.backup.restore.btn", AccountBackupDownloadSuccessNotif = "account.backup.download.successNotification", AccountBackupRestoreSuccessNotif = "account.backup.restore.successNotification", - MarketsOffersColumnPrice = "marketsOffers.columnPrice", MarketsOffersColumnAmount = "marketsOffers.columnAmount", MarketsOffersColumnCost = "marketsOffers.columnCost", MarketsOffersColumnPaymentMethod = "marketsOffers.columnPaymentMethod", MarketsOffersColumnAccountAge = "marketsOffers.columnAccountAge", MarketsOffersColumnAccountTrades = "marketsOffers.columnAccountTypes", + MarketsTransactionsColumnPrice = "marketsTransactions.columnPrice", + MarketsTransactionsColumnAmount = "marketsTransactions.columnAmount", + MarketsTransactionsColumnCost = "marketsTransactions.columnCost", + MarketsTransactionsColumnPaymentMethod = "marketsTransactions.columnPaymentMethod", + MarketsTransactionsColumnAccountAge = "marketsTransactions.columnAccountAge", + MarketsTransactionsColumnAccountTrades = "marketsTransactions.columnAccountTypes", + MarketsTransactionsCashByMail = "marketsTransactions.cashByMail", + MarketOffersAmount = "marketOffers.filter.amount", + MarketOffersWith = "marketOffers.filter.with", + MarketOffersSwitchSell = "marketOffers.filter.switchSellBuy.sell", + MarketOffersSwitchBuy = "marketOffers.filter.switchSellBuy.buy", + MarketOffersPaymentMethod = "marketOffers.filter.paymentMethod", + MarketOffersAccountDetails = "marketOffers.filter.accountDetails", + MarketOffersShowMarketDepth = "marketOffers.filter.showMarketDepth", + MarketOffersHideMarketDepth = "marketOffers.filter.hideMarketDepth", + MarketOffersCreateOffer = "marketOffers.filter.createOffer", + MarketOffersCurrency = "marketOffers.filter.currency", + MarketOffersSigned = "marketOffers.filter.signed", + MarketOffersTradesAmount = "marketOffers.filter.tradesAmount", + MarketOffersDaysAge = "marketOffers.filter.daysAge", + MarketPaymentMethodColMethodName = "marketOffers.filter.paymentMethodColMethodName", + MarketPaymentMethodColRateTradeLimit = "marketOffers.filter.paymentMethodColRateTradeLimit", + MarketPaymentMethodColInfo = "marketOffers.filter.paymentMethodColInfo", + MarketTradingPairColPair = "marketOffers.tradingPairFilter.pairColumn", + MarketTradingPairColLastPrice = "marketOffers.tradingPairFilter.lastPriceColumn", + MarketTradingPairColDayChange = "marketOffers.tradingPairFilter.dayChangeColumn", + MarketTradingPairColDayChangeVolume = "marketOffers.tradingPairFilter.dayChangeVolumColumn", + MarketFilterAccountLabelSignedAccounts = "marketFilters.accountFilter.labelSignedAccounts", + MarketFilterAccountDescSignedAccounts = "marketFilters.accountFilter.descSignedAccounts", + MarketFilterAccountLabelMinAccountAge = "marketFilters.accountFilter.labelMinAccountAge", + MarketFilterAccountDescMinAccountAge = "marketFilters.accountFilter.descMinAccountAge", + MarketFilterAccountLabelAmountTrades = "marketFilters.accountFilter.labelAmountTrades", + MarketFilterAccountDescAmountTrades = "marketFilters.accountFilter.descAmountTrades", + MarketFilterAccountTrades = "marketFilters.accountFilter.trades", + MarketFilterAccountDays = "marketFilters.accountFilter.days", + MarketFilterAccountClearFiltersBtn = "marketFilters.accountFilter.clearFiltersBtn", + MarketFilterAccountSaveBtn = "marketFilters.accountFilter.saveBtn", + MarketAmountFilterFieldMinAmountTrades = "MarketFilterFieldMinAmountTrades", + MarketAmountFilterFieldMinAmountTradesDesc = "MarketFilterFieldMinAmountTradesDesc", + MarketAmountFilterFieldMaxAmount = "MarketFilterFieldMaxAmount", + MarketAmountFilterFieldMaxAmountDesc = "MarketFilterFieldMaxAmountDesc", + MarketAmountFilterAmountClearFiltersBtn = "MarketFilterAmountClearFiltersBtn", + MarketAmountFilterAmountSaveBtn = "MarketFilterAmountSaveBtn", } diff --git a/packages/renderer/src/constants/lang/en.ts b/packages/renderer/src/constants/lang/en.ts index 873bc32..917b7cb 100644 --- a/packages/renderer/src/constants/lang/en.ts +++ b/packages/renderer/src/constants/lang/en.ts @@ -122,6 +122,54 @@ const LangPackEN: { [key in LangKeys]: string } = { [LangKeys.MarketsOffersColumnAccountAge]: "Account Age", [LangKeys.MarketsOffersColumnAccountTrades]: "Account Trades", [LangKeys.MarketsOffersColumnPaymentMethod]: "Payment Method", + [LangKeys.MarketsTransactionsColumnPrice]: "Price", + [LangKeys.MarketsTransactionsColumnAmount]: "Amount", + [LangKeys.MarketsTransactionsColumnCost]: "Costs", + [LangKeys.MarketsTransactionsColumnAccountAge]: "Account Age", + [LangKeys.MarketsTransactionsColumnAccountTrades]: "Account Trades", + [LangKeys.MarketsTransactionsColumnPaymentMethod]: "Payment Method", + [LangKeys.MarketsTransactionsCashByMail]: "Cash by mail", + [LangKeys.MarketOffersAmount]: "Amount", + [LangKeys.MarketOffersWith]: "with", + [LangKeys.MarketOffersSwitchSell]: "Sell {currency}", + [LangKeys.MarketOffersSwitchBuy]: "Buy {currency}", + [LangKeys.MarketOffersPaymentMethod]: "Payment method", + [LangKeys.MarketOffersAccountDetails]: "Account details", + [LangKeys.MarketOffersShowMarketDepth]: "Show market depth", + [LangKeys.MarketOffersHideMarketDepth]: "Hide market depth", + [LangKeys.MarketOffersCreateOffer]: "Create offer", + [LangKeys.MarketOffersCurrency]: "Currency", + [LangKeys.MarketOffersSigned]: "Signed", + [LangKeys.MarketOffersTradesAmount]: ">{value} trades", + [LangKeys.MarketOffersDaysAge]: ">{value} days", + [LangKeys.MarketPaymentMethodColMethodName]: "Method", + [LangKeys.MarketPaymentMethodColRateTradeLimit]: "Rate Trade Limit", + [LangKeys.MarketPaymentMethodColInfo]: "Info", + [LangKeys.MarketTradingPairColPair]: "Pair", + [LangKeys.MarketTradingPairColLastPrice]: "Last Price", + [LangKeys.MarketTradingPairColDayChange]: "24th Change", + [LangKeys.MarketTradingPairColDayChangeVolume]: "24th Vol", + [LangKeys.MarketFilterAccountLabelSignedAccounts]: "Signed accounts", + [LangKeys.MarketFilterAccountDescSignedAccounts]: + "Only show accounts that have been signed. Please be aware that new accounts need to get the chance to get signed.", + [LangKeys.MarketFilterAccountLabelMinAccountAge]: "Minimum account age", + [LangKeys.MarketFilterAccountDescMinAccountAge]: + "Only show trade offers with a minimum account age.", + [LangKeys.MarketFilterAccountLabelAmountTrades]: "Minimum amount of trades", + [LangKeys.MarketFilterAccountDescAmountTrades]: + "Only show trade offers from accounts with a minimum amount of completed trades", + [LangKeys.MarketFilterAccountTrades]: "Trades", + [LangKeys.MarketFilterAccountDays]: "Days", + [LangKeys.MarketFilterAccountClearFiltersBtn]: "Clear filters", + [LangKeys.MarketFilterAccountSaveBtn]: "Save filters", + [LangKeys.MarketAmountFilterFieldMinAmountTrades]: "Minimum amount of trades", + [LangKeys.MarketAmountFilterFieldMinAmountTradesDesc]: + "Set the minimum amount you want to buy.", + [LangKeys.MarketAmountFilterFieldMaxAmount]: "Maximum amount", + [LangKeys.MarketAmountFilterFieldMaxAmountDesc]: + "Set the maximum amount you want to buy.", + [LangKeys.MarketAmountFilterAmountClearFiltersBtn]: "Clear filters", + [LangKeys.MarketAmountFilterAmountSaveBtn]: "Save filters", }; export default LangPackEN; diff --git a/packages/renderer/src/constants/lang/es.ts b/packages/renderer/src/constants/lang/es.ts index e31bb1f..5357275 100644 --- a/packages/renderer/src/constants/lang/es.ts +++ b/packages/renderer/src/constants/lang/es.ts @@ -125,6 +125,56 @@ const LangPackES: { [key in LangKeys]: string } = { [LangKeys.MarketsOffersColumnAccountAge]: "Edad de la cuenta", [LangKeys.MarketsOffersColumnAccountTrades]: "Operaciones de cuenta", [LangKeys.MarketsOffersColumnPaymentMethod]: "Método de pago", + [LangKeys.MarketsTransactionsColumnPrice]: "Precio", + [LangKeys.MarketsTransactionsColumnAmount]: "Monto", + [LangKeys.MarketsTransactionsColumnCost]: "Costos", + [LangKeys.MarketsTransactionsColumnAccountAge]: "Edad de la cuenta", + [LangKeys.MarketsTransactionsColumnAccountTrades]: "Operaciones de cuenta", + [LangKeys.MarketsTransactionsColumnPaymentMethod]: "Método de pago", + [LangKeys.MarketsTransactionsCashByMail]: "Cash by mail", + [LangKeys.MarketOffersAmount]: "Monto", + [LangKeys.MarketOffersWith]: "con", + [LangKeys.MarketOffersSwitchSell]: "Vender {currency}", + [LangKeys.MarketOffersSwitchBuy]: "Comprar {currency}", + [LangKeys.MarketOffersPaymentMethod]: "Método de pago", + [LangKeys.MarketOffersAccountDetails]: "Detalles de la cuenta", + [LangKeys.MarketOffersShowMarketDepth]: "Mostrar profundidad de mercado", + [LangKeys.MarketOffersHideMarketDepth]: "Ocultar profundidad de mercado", + [LangKeys.MarketOffersCreateOffer]: "Crear oferta", + [LangKeys.MarketOffersCurrency]: "Divisa", + [LangKeys.MarketOffersSigned]: "Firmada", + [LangKeys.MarketOffersTradesAmount]: ">{value} vientos alisios", + [LangKeys.MarketOffersDaysAge]: ">{value} días", + [LangKeys.MarketPaymentMethodColMethodName]: "Método", + [LangKeys.MarketPaymentMethodColRateTradeLimit]: "Límite comercial de tasa", + [LangKeys.MarketPaymentMethodColInfo]: "Información", + [LangKeys.MarketTradingPairColPair]: "Par", + [LangKeys.MarketTradingPairColLastPrice]: "Last Price", + [LangKeys.MarketTradingPairColDayChange]: "Cambio 24", + [LangKeys.MarketTradingPairColDayChangeVolume]: "Vol. 24", + [LangKeys.MarketFilterAccountLabelSignedAccounts]: "cuentas firmadas", + [LangKeys.MarketFilterAccountDescSignedAccounts]: + "Solo mostrar cuentas que hayan sido firmadas. Tenga en cuenta que las cuentas nuevas deben tener la oportunidad de ser firmadas.", + [LangKeys.MarketFilterAccountLabelMinAccountAge]: "Edad mínima de la cuenta", + [LangKeys.MarketFilterAccountDescMinAccountAge]: + "Mostrar solo ofertas comerciales con una edad mínima de cuenta.", + [LangKeys.MarketFilterAccountLabelAmountTrades]: + "Cantidad mínima de operaciones", + [LangKeys.MarketFilterAccountDescAmountTrades]: + "Mostrar solo ofertas comerciales de cuentas con una cantidad mínima de operaciones completadas", + [LangKeys.MarketFilterAccountTrades]: "Vientos alisios", + [LangKeys.MarketFilterAccountDays]: "Días", + [LangKeys.MarketFilterAccountClearFiltersBtn]: "Borrar filtros", + [LangKeys.MarketFilterAccountSaveBtn]: "Guardar filtros", + [LangKeys.MarketAmountFilterFieldMinAmountTrades]: + "Cantidad mínima de operaciones", + [LangKeys.MarketAmountFilterFieldMinAmountTradesDesc]: + "Establece la cantidad mínima que deseas comprar.", + [LangKeys.MarketAmountFilterFieldMaxAmount]: "Importe máximo", + [LangKeys.MarketAmountFilterFieldMaxAmountDesc]: + "Establece la cantidad máxima que deseas comprar.", + [LangKeys.MarketAmountFilterAmountClearFiltersBtn]: "Borrar filtros", + [LangKeys.MarketAmountFilterAmountSaveBtn]: "Guardar filtros", }; export default LangPackES; diff --git a/packages/renderer/src/constants/query-keys.ts b/packages/renderer/src/constants/query-keys.ts index 29ee9ce..32dc52b 100644 --- a/packages/renderer/src/constants/query-keys.ts +++ b/packages/renderer/src/constants/query-keys.ts @@ -31,6 +31,8 @@ export enum QueryKeys { XmrTxs = "Haveno.XmrTransactions", MarketsOffers = "Haveno.MarketsOffers", MyOffers = "Haveno.MyOffers", + PaymentMethods = "Haveno.PaymentMethods", + MarketsPairs = "Haveno.MarketsPairs", // Storage StorageAccountInfo = "Storage.AccountInfo", diff --git a/packages/renderer/src/hooks/haveno/useMarketPairs.ts b/packages/renderer/src/hooks/haveno/useMarketPairs.ts new file mode 100644 index 0000000..9355153 --- /dev/null +++ b/packages/renderer/src/hooks/haveno/useMarketPairs.ts @@ -0,0 +1,70 @@ +// ============================================================================= +// 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"; + +interface MarketsPairsData { + fromPair: string; + toPair: string; + lastPrice: number; + lastPriceCurrency: string; + dayChangeRate: number; + dayChangeVolume: number; +} + +export function useMarketsPairs() { + return useQuery, Error>( + [QueryKeys.MarketsPairs], + // TODO: replace with actual implementation once haveno-ts is feature complete. + () => Promise.resolve(data) + ); +} + +const data = [ + { + fromPair: "XMR", + toPair: "USD", + lastPrice: 246.23, + lastPriceCurrency: "EUR", + dayChangeRate: 0.2, + dayChangeVolume: 0.2, + }, + { + fromPair: "XMR", + toPair: "USD", + lastPrice: 246.23, + lastPriceCurrency: "EUR", + dayChangeRate: 0.2, + dayChangeVolume: 0.2, + }, + { + fromPair: "XMR", + toPair: "USD", + lastPrice: 246.23, + lastPriceCurrency: "EUR", + dayChangeRate: 0.2, + dayChangeVolume: 0.2, + }, + { + fromPair: "XMR", + toPair: "USD", + lastPrice: 246.23, + lastPriceCurrency: "EUR", + dayChangeRate: 0.2, + dayChangeVolume: 0.2, + }, +] as Array; diff --git a/packages/renderer/src/hooks/haveno/usePaymentMethods.ts b/packages/renderer/src/hooks/haveno/usePaymentMethods.ts new file mode 100644 index 0000000..5663e12 --- /dev/null +++ b/packages/renderer/src/hooks/haveno/usePaymentMethods.ts @@ -0,0 +1,69 @@ +// ============================================================================= +// 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"; + +interface PaymentMethodsQuery { + assetCode?: string; +} + +interface PaymentMethodsValues { + methodName: string; + methodKey: string; + rateTradeLimit: number; + rateTradeLimitCurrency: string; + info: string; +} + +export function usePaymentMethods(query?: PaymentMethodsQuery) { + return useQuery, Error>( + [QueryKeys.PaymentAccounts, query], + // TODO: replace with actual implementation once haveno-ts is feature complete. + () => Promise.resolve(data) + ); +} + +const data = [ + { + methodName: "Celpay", + methodKey: "celpay", + rateTradeLimit: 20, + rateTradeLimitCurrency: "XMR", + info: "Global", + }, + { + methodName: "Celpay", + methodKey: "celpay2", + rateTradeLimit: 20, + rateTradeLimitCurrency: "XMR", + info: "Global", + }, + { + methodName: "Celpay", + methodKey: "celpay3", + rateTradeLimit: 20, + rateTradeLimitCurrency: "XMR", + info: "Global", + }, + { + methodName: "Celpay", + methodKey: "celpay4", + rateTradeLimit: 20, + rateTradeLimitCurrency: "XMR", + info: "Global", + }, +] as Array; diff --git a/packages/renderer/src/pages/Markets/MarketsOffers.tsx b/packages/renderer/src/pages/Markets/MarketsOffers.tsx index d84b3cb..eaf5a38 100644 --- a/packages/renderer/src/pages/Markets/MarketsOffers.tsx +++ b/packages/renderer/src/pages/Markets/MarketsOffers.tsx @@ -14,16 +14,20 @@ // limitations under the License. // ============================================================================= -import { createStyles } from "@mantine/core"; +import { createStyles, Stack } from "@mantine/core"; import { NavbarLayout } from "@templates/NavbarLayout"; import { MarketsOffers } from "@organisms/MarketsOffers"; +import { MarketOffersFilterBar } from "@organisms/MarketOffersFilterBar"; export function MarketsOffersPage() { const { classes } = useStyles(); return ( - + + + + ); } @@ -32,4 +36,7 @@ const useStyles = createStyles(() => ({ contentArea: { padding: 0, }, + innerContent: { + width: "100%", + }, })); diff --git a/packages/renderer/src/pages/Markets/MarketsTransactions.tsx b/packages/renderer/src/pages/Markets/MarketsTransactions.tsx new file mode 100644 index 0000000..cd2fff7 --- /dev/null +++ b/packages/renderer/src/pages/Markets/MarketsTransactions.tsx @@ -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 { createStyles, Stack } from "@mantine/core"; +import { NavbarLayout } from "@templates/NavbarLayout"; +import { MarketOffersFilterBar } from "@organisms/MarketOffersFilterBar"; + +export function MarketsTransactionsPage() { + const { classes } = useStyles(); + + return ( + + + + + + ); +} + +const useStyles = createStyles(() => ({ + contentArea: { + padding: 0, + }, + innerContentArea: { + width: "100%", + }, +})); diff --git a/packages/renderer/src/state/offersFilter.ts b/packages/renderer/src/state/offersFilter.ts new file mode 100644 index 0000000..551b51d --- /dev/null +++ b/packages/renderer/src/state/offersFilter.ts @@ -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 { + atom, + useRecoilState, + useRecoilValue, + useSetRecoilState, +} from "recoil"; + +export type OfferFilter = { + direction: string; + assetCode: string; + minimumCryptoAmount?: number | null; + maximumCryptoAmount?: number | null; + minimumBaseCurrencyAmount?: number | null; + maximumBaseCurrencyAmount?: number | null; + paymentMethods?: Array; + signedAccounts?: boolean; + minimumAccountAge?: number | null; + minimumTradesAmount?: number | null; +}; +const defaultValue = { + direction: "sell", + assetCode: "BTC", +}; +export const offersFilterAtom = atom({ + key: "offersFilter", + default: defaultValue, +}); + +export const useGetOffersFilterState = () => + useRecoilValue(offersFilterAtom); + +export const useSetOffersFilterState = () => + useSetRecoilState(offersFilterAtom); + +export const useOffersFilterState = () => + useRecoilState(offersFilterAtom); diff --git a/packages/renderer/src/utils/misc.ts b/packages/renderer/src/utils/misc.ts new file mode 100644 index 0000000..d467abf --- /dev/null +++ b/packages/renderer/src/utils/misc.ts @@ -0,0 +1,36 @@ +// ============================================================================= +// 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 _ from "lodash"; + +/** + * Retrieves the form fields and removes fields that out of form from + * the given intial values, and removes previously unfilled optional values + * come as `null` as well. + * + * @param {Record} param - Form values. + * @param {Record} initialValues - Form initial values. + * @return {Record} + */ +export const transformToForm = ( + obj: Record, + initialValues: Record +): Record => { + return _.pickBy( + obj, + (val, key) => val !== null && Object.keys(initialValues).includes(key) + ); +};