diff --git a/src/domains/profile/components/SelectRecipient/SelectRecipient.stories.tsx b/src/domains/profile/components/SelectRecipient/SelectRecipient.stories.tsx new file mode 100644 index 0000000000..637cb14ceb --- /dev/null +++ b/src/domains/profile/components/SelectRecipient/SelectRecipient.stories.tsx @@ -0,0 +1,30 @@ +import { contacts } from "domains/contact/data"; +import React from "react"; + +import { SelectRecipient } from "./SelectRecipient"; + +export default { title: "Domains / Profile / Components / Select Recipient" }; + +export const Default = () => { + return ( +
+
+ +
+
+ +
+
+ +
+
+
Selected address
+ +
+
+
Selected address (disabled)
+ +
+
+ ); +}; diff --git a/src/domains/profile/components/SelectRecipient/SelectRecipient.test.tsx b/src/domains/profile/components/SelectRecipient/SelectRecipient.test.tsx new file mode 100644 index 0000000000..ccf500da42 --- /dev/null +++ b/src/domains/profile/components/SelectRecipient/SelectRecipient.test.tsx @@ -0,0 +1,134 @@ +/* eslint-disable @typescript-eslint/require-await */ +import { contacts } from "domains/contact/data"; +import React from "react"; +import { act, fireEvent, render, waitFor } from "testing-library"; + +import { SelectRecipient } from "./SelectRecipient"; + +describe("SelectRecipient", () => { + it("should render empty", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("should render disabled", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("should render invalid", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("should render with preselected address", () => { + const { container } = render( + , + ); + expect(container).toMatchSnapshot(); + }); + + it("should open and close contacts modal", () => { + const { getByTestId } = render( + , + ); + + expect(() => getByTestId("modal__inner")).toThrow(/Unable to find an element by/); + + act(() => { + fireEvent.click(getByTestId("SelectRecipient__select-contact")); + }); + + expect(getByTestId("modal__inner")).toBeTruthy(); + + act(() => { + fireEvent.click(getByTestId("modal__close-btn")); + }); + + waitFor(() => expect(getByTestId("modal__inner")).toBeFalsy()); + }); + + it("should select address from contacts modal", () => { + const { getByTestId, getAllByTestId } = render( + , + ); + + expect(() => getByTestId("modal__inner")).toThrow(/Unable to find an element by/); + + act(() => { + fireEvent.click(getByTestId("SelectRecipient__select-contact")); + }); + + expect(getByTestId("modal__inner")).toBeTruthy(); + + const firstAddress = getAllByTestId("ContactListItem__one-option-button-0")[0]; + + act(() => { + fireEvent.click(firstAddress); + }); + + waitFor(() => { + expect(getByTestId("modal__inner").toThrow(/Unable to find an element by/)); + + const selectedAddressValue = contacts[0]?.addresses()[0]?.address; + expect(getByTestId("SelectRecipient__input")).toHaveValue(selectedAddressValue); + }); + }); + + it("should not open contacts modal if disabled", () => { + const { getByTestId } = render( + , + ); + + expect(() => getByTestId("modal__inner")).toThrow(/Unable to find an element by/); + + act(() => { + fireEvent.click(getByTestId("SelectRecipient__select-contact")); + }); + + expect(() => getByTestId("modal__inner")).toThrow(/Unable to find an element by/); + }); + + it("should call onChange prop when entered address in input", async () => { + const fn = jest.fn(); + const { getByTestId } = render(); + const address = "bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT"; + const recipientInputField = getByTestId("SelectRecipient__input"); + + await act(async () => { + fireEvent.change(recipientInputField, { target: { value: address } }); + }); + + expect(fn).toBeCalledWith(address); + }); + + it("should call onChange prop if provided", () => { + const fn = jest.fn(); + const { getByTestId, getAllByTestId } = render( + , + ); + + expect(() => getByTestId("modal__inner")).toThrow(/Unable to find an element by/); + + act(() => { + fireEvent.click(getByTestId("SelectRecipient__select-contact")); + }); + + expect(getByTestId("modal__inner")).toBeTruthy(); + + const firstAddress = getAllByTestId("ContactListItem__one-option-button-0")[0]; + + act(() => { + fireEvent.click(firstAddress); + }); + + waitFor(() => { + expect(getByTestId("modal__inner").toThrow(/Unable to find an element by/)); + + const selectedAddressValue = contacts[0]?.addresses()[0]?.address; + expect(getByTestId("SelectRecipient__input")).toHaveValue(selectedAddressValue); + + expect(fn).toBeCalledWith(selectedAddressValue); + }); + }); +}); diff --git a/src/domains/profile/components/SelectRecipient/SelectRecipient.tsx b/src/domains/profile/components/SelectRecipient/SelectRecipient.tsx new file mode 100644 index 0000000000..1a19b1a462 --- /dev/null +++ b/src/domains/profile/components/SelectRecipient/SelectRecipient.tsx @@ -0,0 +1,108 @@ +import { Avatar } from "app/components/Avatar"; +import { Circle } from "app/components/Circle"; +import { useFormField } from "app/components/Form/useFormField"; +import { Icon } from "app/components/Icon"; +import { Input } from "app/components/Input"; +import { SearchContact } from "domains/contact/components/SearchContact"; +import React, { useEffect, useState } from "react"; + +type SelectRecipientProps = { + address?: string; + contacts: any[]; + disabled?: boolean; + isInvalid?: boolean; + contactSearchTitle?: string; + contactSearchDescription?: string; + selectActionLabel?: string; + onChange?: (address: string) => void; +} & React.InputHTMLAttributes; + +const ProfileAvatar = ({ address }: any) => { + if (!address) return ; + return ; +}; + +export const SelectRecipient = React.forwardRef( + ( + { + contactSearchTitle, + contactSearchDescription, + selectActionLabel, + address, + contacts, + disabled, + isInvalid, + onChange, + }: SelectRecipientProps, + ref, + ) => { + const [isContactSearchOpen, setIsContactSearchOpen] = useState(false); + const [selectedAddress, setSelectedAddress] = useState(address); + useEffect(() => setSelectedAddress(address), [address]); + + const fieldContext = useFormField(); + const isInvalidField = fieldContext?.isInvalid || isInvalid; + + const onSelectProfile = (address: any) => { + setSelectedAddress(address); + setIsContactSearchOpen(false); + onChange?.(address); + }; + + const openContacts = () => { + if (disabled) return; + setIsContactSearchOpen(true); + }; + + const onInputChange = (value: string) => { + setSelectedAddress(value); + onChange?.(value); + }; + + return ( +
+
+
+ +
+ onInputChange?.(ev.target.value)} + disabled={disabled} + isInvalid={isInvalidField} + /> + +
+ +
+
+ + onSelectProfile(address)} + onClose={() => setIsContactSearchOpen(false)} + /> +
+ ); + }, +); + +SelectRecipient.defaultProps = { + contactSearchTitle: "Recipient search", + contactSearchDescription: "Find and select the recipient from your contacts", + selectActionLabel: "Select", +}; + +SelectRecipient.displayName = "SelectRecipient"; diff --git a/src/domains/profile/components/SelectRecipient/__snapshots__/SelectRecipient.test.tsx.snap b/src/domains/profile/components/SelectRecipient/__snapshots__/SelectRecipient.test.tsx.snap new file mode 100644 index 0000000000..b5449c2505 --- /dev/null +++ b/src/domains/profile/components/SelectRecipient/__snapshots__/SelectRecipient.test.tsx.snap @@ -0,0 +1,170 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SelectRecipient should render disabled 1`] = ` +
+
+
+
+
+
+ +
+
+ + user.svg + +
+
+
+
+
+`; + +exports[`SelectRecipient should render empty 1`] = ` +
+
+
+
+
+
+ +
+
+ + user.svg + +
+
+
+
+
+`; + +exports[`SelectRecipient should render invalid 1`] = ` +
+
+
+
+
+
+ +
+
+ + user.svg + +
+
+
+
+
+`; + +exports[`SelectRecipient should render with preselected address 1`] = ` +
+
+
+
+
+
+ bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT" + title="bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT" + /> +
+
+
+ +
+
+ + user.svg + +
+
+
+
+
+`; diff --git a/src/domains/profile/components/SelectRecipient/index.ts b/src/domains/profile/components/SelectRecipient/index.ts new file mode 100644 index 0000000000..13aeb6380f --- /dev/null +++ b/src/domains/profile/components/SelectRecipient/index.ts @@ -0,0 +1 @@ +export * from "./SelectRecipient";