From 03642273afd34dbb715cfb68695a80e2d203ee05 Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Wed, 23 Oct 2024 15:51:30 +0200 Subject: [PATCH] feat(llm): validate stellar memo on recipient step --- .../src/families/stellar/MemoTagInput.tsx | 22 +++++++++++++------ .../features/MemoTag/hooks/useMemoTagInput.ts | 6 +++-- .../src/newArch/features/MemoTag/types.ts | 2 +- .../screens/SendFunds/02-SelectRecipient.tsx | 6 ++++- .../src/families/stellar/bridge/logic.ts | 1 + 5 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 libs/ledger-live-common/src/families/stellar/bridge/logic.ts diff --git a/apps/ledger-live-mobile/src/families/stellar/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/stellar/MemoTagInput.tsx index 227725be5b60..6c58457e5910 100644 --- a/apps/ledger-live-mobile/src/families/stellar/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/stellar/MemoTagInput.tsx @@ -1,9 +1,13 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; -import type { Transaction as StellarTransaction } from "@ledgerhq/live-common/families/stellar/types"; -import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; +import { isMemoValid } from "@ledgerhq/live-common/families/stellar/bridge/logic"; +import { + StellarWrongMemoFormat, + type Transaction as StellarTransaction, +} from "@ledgerhq/live-common/families/stellar/types"; import { AnimatedInputSelect } from "@ledgerhq/native-ui"; +import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { MemoTypeDrawer, MEMO_TYPES } from "./MemoTypeDrawer"; export default ({ onChange }: MemoTagInputProps) => { @@ -13,23 +17,27 @@ export default ({ onChange }: MemoTagInputProps) => { const [memoValue, setMemoValue] = React.useState(""); const [isOpen, setIsOpen] = useState(false); + const handleChange = (type: MemoType, value: string) => { + const error = isMemoValid(type, value) ? undefined : new StellarWrongMemoFormat(); + const patch = { memoType: type, memoValue: value }; + onChange({ value, patch, error }); + }; + const handleChangeType = (type: MemoType) => { const value = type === "NO_MEMO" ? "" : memoValue; + handleChange(type, value); - setIsOpen(false); setMemoType(type); if (value !== memoValue) setMemoValue(value); - - onChange({ value, patch: { memoType: type, memoValue: value } }); + setIsOpen(false); }; const handleChangeValue = (value: string) => { const type = memoType === "NO_MEMO" && value ? "MEMO_TEXT" : memoType; + handleChange(type, value); setMemoValue(value); if (type !== memoType) setMemoType(type); - - onChange({ value, patch: { memoType: type, memoValue: value } }); }; return ( diff --git a/apps/ledger-live-mobile/src/newArch/features/MemoTag/hooks/useMemoTagInput.ts b/apps/ledger-live-mobile/src/newArch/features/MemoTag/hooks/useMemoTagInput.ts index 5e4daf1b278e..17c816609869 100644 --- a/apps/ledger-live-mobile/src/newArch/features/MemoTag/hooks/useMemoTagInput.ts +++ b/apps/ledger-live-mobile/src/newArch/features/MemoTag/hooks/useMemoTagInput.ts @@ -18,13 +18,15 @@ export const useMemoTagInput = ( null; const [isEmpty, setIsEmpty] = useState(true); + const [error, setError] = useState(); const handleChange = useCallback( - ({ patch, value }) => { + ({ patch, value, error }) => { setIsEmpty(!value); + setError(error); updateTransaction(patch); }, [updateTransaction], ); - return Input && { Input, isEmpty, handleChange }; + return Input && { Input, isEmpty, error, handleChange }; }; diff --git a/apps/ledger-live-mobile/src/newArch/features/MemoTag/types.ts b/apps/ledger-live-mobile/src/newArch/features/MemoTag/types.ts index 9687b43d5631..7fbbecc42c43 100644 --- a/apps/ledger-live-mobile/src/newArch/features/MemoTag/types.ts +++ b/apps/ledger-live-mobile/src/newArch/features/MemoTag/types.ts @@ -4,4 +4,4 @@ import type { AnimatedInputProps } from "@ledgerhq/native-ui/components/Form/Inp export type MemoTagInputProps = Omit< AnimatedInputProps, "value" | "onChangeText" | "onChange" -> & { onChange: (update: { patch: Partial; value: string }) => void }; +> & { onChange: (update: { patch: Partial; value: string; error?: Error }) => void }; diff --git a/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx b/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx index 42f9e64b5b3d..3ce151cfb32d 100644 --- a/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx +++ b/apps/ledger-live-mobile/src/screens/SendFunds/02-SelectRecipient.tsx @@ -32,6 +32,7 @@ import NavigationScrollView from "~/components/NavigationScrollView"; import RetryButton from "~/components/RetryButton"; import { SendFundsNavigatorStackParamList } from "~/components/RootNavigator/types/SendFundsNavigator"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; +import TranslatedError from "~/components/TranslatedError"; import { ScreenName } from "~/const"; import { accountScreenSelector } from "~/reducers/accounts"; import { currencySettingsForAccountSelector } from "~/reducers/settings"; @@ -316,6 +317,9 @@ export default function SendSelectRecipient({ navigation, route }: Props) { placeholder={t("send.summary.memo.title")} onChange={memoTag.handleChange} /> + + + )} @@ -337,7 +341,7 @@ export default function SendSelectRecipient({ navigation, route }: Props) { testID="recipient-continue-button" type="primary" title={} - disabled={debouncedBridgePending || !!status.errors.recipient} + disabled={debouncedBridgePending || !!status.errors.recipient || memoTag?.error} pending={debouncedBridgePending} onPress={onPressContinue} /> diff --git a/libs/ledger-live-common/src/families/stellar/bridge/logic.ts b/libs/ledger-live-common/src/families/stellar/bridge/logic.ts new file mode 100644 index 000000000000..81f4484f6401 --- /dev/null +++ b/libs/ledger-live-common/src/families/stellar/bridge/logic.ts @@ -0,0 +1 @@ +export * from "@ledgerhq/coin-stellar/bridge/logic";