From be2348746d87ee9247be12d1029963ef3c3b6e36 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Wed, 29 Apr 2026 12:17:09 -0300 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=92=84=20app:=20drop=20unfreeze=20car?= =?UTF-8?q?d=20label?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/cool-rockets-raise.md | 5 +++++ .maestro/subflows/activateCard.yaml | 2 +- src/components/card/Card.tsx | 2 +- src/i18n/es.json | 1 - src/i18n/pt.json | 1 - 5 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 .changeset/cool-rockets-raise.md diff --git a/.changeset/cool-rockets-raise.md b/.changeset/cool-rockets-raise.md new file mode 100644 index 0000000000..dcc0898ec7 --- /dev/null +++ b/.changeset/cool-rockets-raise.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +💄 drop unfreeze card label diff --git a/.maestro/subflows/activateCard.yaml b/.maestro/subflows/activateCard.yaml index 0462ded097..ee6fbd7361 100644 --- a/.maestro/subflows/activateCard.yaml +++ b/.maestro/subflows/activateCard.yaml @@ -11,7 +11,7 @@ appId: ${APP_ID ?? "app.exactly"} - assertVisible: Manually add your card to Apple Pay & Google Pay to make contactless payments. - tapOn: Close - tapOn: Freeze card -- tapOn: Unfreeze card +- tapOn: Freeze card - tapOn: View PIN number - tapOn: Close - tapOn: Weekly spending limit diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index 188cf42e3f..7d348bc99b 100644 --- a/src/components/card/Card.tsx +++ b/src/components/card/Card.tsx @@ -355,7 +355,7 @@ export default function Card() { )} - {displayStatus === "FROZEN" ? t("Unfreeze card") : t("Freeze card")} + {t("Freeze card")} diff --git a/src/i18n/es.json b/src/i18n/es.json index de1b55ce5a..07d5ce3579 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -593,7 +593,6 @@ "Turn {{currency}} transfers to onchain USDC": "Convierte transferencias de {{currency}} a USDC on-chain", "Unable to fetch a bridge quote right now. Please adjust the amount or try again later.": "No es posible obtener una cotización de bridge en este momento. Ajusta el monto o inténtalo más tarde.", "Unable to simulate a transfer right now. Please adjust the amount or try again later.": "No es posible simular una transferencia en este momento. Ajusta el monto o inténtalo más tarde.", - "Unfreeze card": "Descongelar tarjeta", "Unknown": "Desconocido", "Unlike monthly payments, our installments are due every 4 weeks, which means payments are aligned with a 28-day cycle rather than the calendar month.": "A diferencia de los pagos mensuales, nuestras cuotas vencen cada 4 semanas, lo que significa que los pagos se alinean con un ciclo de 28 días en lugar del mes calendario.", "Upcoming payments": "Pagos próximos", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index e5e7fa71cd..3e96f46240 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -593,7 +593,6 @@ "Turn {{currency}} transfers to onchain USDC": "Converta transferências de {{currency}} em USDC on-chain", "Unable to fetch a bridge quote right now. Please adjust the amount or try again later.": "Não é possível obter uma cotação de bridge no momento. Ajuste o valor ou tente novamente mais tarde.", "Unable to simulate a transfer right now. Please adjust the amount or try again later.": "Não é possível simular uma transferência no momento. Ajuste o valor ou tente novamente mais tarde.", - "Unfreeze card": "Descongelar cartão", "Unknown": "Desconhecido", "Unlike monthly payments, our installments are due every 4 weeks, which means payments are aligned with a 28-day cycle rather than the calendar month.": "Diferentemente dos pagamentos mensais, nossas parcelas vencem a cada 4 semanas, o que significa que os pagamentos são alinhados a um ciclo de 28 dias em vez do mês do calendário.", "Upcoming payments": "Próximos pagamentos", From c396d069a25952c0bbd038a54e4de03fe87b081d Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Wed, 29 Apr 2026 12:34:52 -0300 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9C=A8=20app:=20add=20card=20freeze=20co?= =?UTF-8?q?nfirmation=20sheet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/frank-snails-move.md | 5 +++ .maestro/subflows/activateCard.yaml | 4 ++ src/components/card/Card.tsx | 33 +++++++++++---- src/components/card/CardFreezeSheet.tsx | 55 +++++++++++++++++++++++++ src/i18n/es.json | 3 ++ src/i18n/pt.json | 3 ++ 6 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 .changeset/frank-snails-move.md create mode 100644 src/components/card/CardFreezeSheet.tsx diff --git a/.changeset/frank-snails-move.md b/.changeset/frank-snails-move.md new file mode 100644 index 0000000000..9990d3a8c3 --- /dev/null +++ b/.changeset/frank-snails-move.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ add card freeze confirmation sheet diff --git a/.maestro/subflows/activateCard.yaml b/.maestro/subflows/activateCard.yaml index ee6fbd7361..e2ee781597 100644 --- a/.maestro/subflows/activateCard.yaml +++ b/.maestro/subflows/activateCard.yaml @@ -11,6 +11,10 @@ appId: ${APP_ID ?? "app.exactly"} - assertVisible: Manually add your card to Apple Pay & Google Pay to make contactless payments. - tapOn: Close - tapOn: Freeze card +- assertVisible: Freeze your card? +- tapOn: Freeze card +- assertNotVisible: Freeze your card? +- waitForAnimationToEnd - tapOn: Freeze card - tapOn: View PIN number - tapOn: Close diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index 7d348bc99b..095b34e9ba 100644 --- a/src/components/card/Card.tsx +++ b/src/components/card/Card.tsx @@ -16,6 +16,7 @@ import { useReadUpgradeableModularAccountGetInstalledPlugins } from "@exactly/co import CardDetails from "./CardDetails"; import CardDisclaimer from "./CardDisclaimer"; +import CardFreezeSheet from "./CardFreezeSheet"; import CardPIN from "./CardPIN"; import ExaCard from "./exa-card/ExaCard"; import SpendingLimits from "./SpendingLimits"; @@ -58,6 +59,7 @@ export default function Card() { } = useTranslation(); const [disclaimerShown, setDisclaimerShown] = useState(false); const [verificationFailureShown, setVerificationFailureShown] = useState(false); + const [freezeConfirmOpen, setFreezeConfirmOpen] = useState(false); const { data: cardDetailsOpen } = useQuery({ queryKey: ["card-details-open"] }); const [spendingLimitsOpen, setSpendingLimitsOpen] = useState(false); @@ -301,7 +303,7 @@ export default function Card() { { if (isRevealing || isGeneratingCard || beginKYC.isPending) return; revealCard().catch(reportError); @@ -343,7 +345,11 @@ export default function Card() { cursor="pointer" onPress={() => { if (isFetchingCard || isSettingCardStatus) return; - changeCardStatus(cardDetails.status === "FROZEN" ? "ACTIVE" : "FROZEN").catch(reportError); + if (cardDetails.status === "FROZEN") { + changeCardStatus("ACTIVE").catch(reportError); + return; + } + setFreezeConfirmOpen(true); }} > @@ -362,21 +368,20 @@ export default function Card() { @@ -526,6 +531,16 @@ export default function Card() { setVerificationFailureShown(false); }} /> + { + setFreezeConfirmOpen(false); + }} + onConfirm={() => { + setFreezeConfirmOpen(false); + changeCardStatus("FROZEN").catch(reportError); + }} + /> ); diff --git a/src/components/card/CardFreezeSheet.tsx b/src/components/card/CardFreezeSheet.tsx new file mode 100644 index 0000000000..61eeefe80d --- /dev/null +++ b/src/components/card/CardFreezeSheet.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Pressable } from "react-native"; + +import { Snowflake } from "@tamagui/lucide-icons"; +import { YStack } from "tamagui"; + +import ModalSheet from "../shared/ModalSheet"; +import Button from "../shared/StyledButton"; +import Text from "../shared/Text"; + +export default function CardFreezeSheet({ + onClose, + onConfirm, + open, +}: { + onClose: () => void; + onConfirm: () => void; + open: boolean; +}) { + const { t } = useTranslation(); + return ( + + + + + {t("Freeze your card?")} + + + {t("Your card will be temporarily paused. You can unfreeze it anytime.")} + + + + + + + {t("Cancel")} + + + + + + ); +} diff --git a/src/i18n/es.json b/src/i18n/es.json index 07d5ce3579..01f232d2e5 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -109,6 +109,7 @@ "By continuing, I accept the Terms & Conditions": "Al continuar, acepto los Términos y Condiciones", "By continuing, you accept both the notice below and the Terms and Conditions of the Exa Card.": "Al continuar, aceptas tanto el aviso a continuación como los Términos y Condiciones de la Exa Card.", "Camera access is currently disabled for Exa App. In order to continue, enable camera access for Exa App from your device settings.": "El acceso a la cámara está actualmente deshabilitado para Exa App. Para continuar, habilita el acceso a la cámara para Exa App desde la configuración de tu dispositivo.", + "Cancel": "Cancelar", "Cannot proceed": "No se puede continuar", "Card activated!": "¡Tarjeta activada!", "Card details": "Detalles de la tarjeta", @@ -245,6 +246,7 @@ "Free": "Gratis", "FREE": "GRATIS", "Freeze card": "Congelar tarjeta", + "Freeze your card?": "¿Congelar tu tarjeta?", "From a bank account": "Desde una cuenta bancaria", "From another wallet": "Desde otra billetera", "From any account in your name": "Desde cualquier cuenta a tu nombre", @@ -658,6 +660,7 @@ "Your {{chain}} address": "Tu dirección en {{chain}}", "Your address needs to be verified": "Tu dirección necesita ser verificada", "Your card is awaiting activation. Follow the steps to enable it.": "Tu tarjeta está a la espera de activación. Sigue los pasos para habilitarla.", + "Your card will be temporarily paused. You can unfreeze it anytime.": "Tu tarjeta se pausará temporalmente. Puedes descongelarla en cualquier momento.", "Your card’s PIN may be required to confirm transactions and ensure security.": "El PIN de tu tarjeta puede ser necesario para confirmar transacciones y garantizar la seguridad.", "Your Exa account": "Tu cuenta Exa", "Your Exa Card is now upgraded to Visa Signature.": "Tu Exa Card ha sido actualizada a Visa Signature.", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 3e96f46240..6610cbdd6e 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -109,6 +109,7 @@ "By continuing, I accept the Terms & Conditions": "Ao continuar, aceito os Termos e Condições", "By continuing, you accept both the notice below and the Terms and Conditions of the Exa Card.": "Ao continuar, você aceita tanto o aviso abaixo quanto os Termos e Condições do Exa Card.", "Camera access is currently disabled for Exa App. In order to continue, enable camera access for Exa App from your device settings.": "O acesso à câmera está desativado para o Exa App. Para continuar, ative o acesso à câmera para o Exa App nas configurações do seu dispositivo.", + "Cancel": "Cancelar", "Cannot proceed": "Não é possível continuar", "Card activated!": "Cartão ativado!", "Card details": "Detalhes do cartão", @@ -245,6 +246,7 @@ "Free": "Grátis", "FREE": "GRÁTIS", "Freeze card": "Congelar cartão", + "Freeze your card?": "Congelar seu cartão?", "From a bank account": "De uma conta bancária", "From another wallet": "De outra carteira", "From any account in your name": "De qualquer conta em seu nome", @@ -658,6 +660,7 @@ "Your {{chain}} address": "Seu endereço na {{chain}}", "Your address needs to be verified": "Seu endereço precisa ser verificado", "Your card is awaiting activation. Follow the steps to enable it.": "Seu cartão está aguardando ativação. Siga os passos para ativá-lo.", + "Your card will be temporarily paused. You can unfreeze it anytime.": "Seu cartão será pausado temporariamente. Você pode descongelá-lo a qualquer momento.", "Your card’s PIN may be required to confirm transactions and ensure security.": "O PIN do seu cartão pode ser necessário para confirmar transações e garantir a segurança.", "Your Exa account": "Sua conta Exa", "Your Exa Card is now upgraded to Visa Signature.": "Seu Exa Card foi atualizado para Visa Signature.", From 0cadadab5d179e7c9e775358f490511c0d9573ec Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Wed, 29 Apr 2026 16:17:24 -0300 Subject: [PATCH 3/6] =?UTF-8?q?=E2=9C=A8=20app:=20show=20frozen=20overlay?= =?UTF-8?q?=20on=20card?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/six-breads-shout.md | 5 + src/components/card/exa-card/CardContents.tsx | 134 +++++++++++------- 2 files changed, 87 insertions(+), 52 deletions(-) create mode 100644 .changeset/six-breads-shout.md diff --git a/.changeset/six-breads-shout.md b/.changeset/six-breads-shout.md new file mode 100644 index 0000000000..6919c6f5f5 --- /dev/null +++ b/.changeset/six-breads-shout.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ show frozen overlay on card diff --git a/src/components/card/exa-card/CardContents.tsx b/src/components/card/exa-card/CardContents.tsx index aa7b5797ad..6c84ef779c 100644 --- a/src/components/card/exa-card/CardContents.tsx +++ b/src/components/card/exa-card/CardContents.tsx @@ -54,62 +54,55 @@ export default function CardContents({ > - <> - {disabled ? ( - - ) : revealing ? ( - - - - ) : frozen ? ( - - ) : isCredit ? ( - - - {`$${(markets ? Number(borrowLimit(markets, marketUSDCAddress)) / 1e6 : 0).toLocaleString(language, { - style: "decimal", - minimumFractionDigits: 2, - maximumFractionDigits: 2, - })}`} + {disabled ? ( + + ) : revealing ? ( + + + + ) : frozen ? null : isCredit ? ( + + + {`$${(markets ? Number(borrowLimit(markets, marketUSDCAddress)) / 1e6 : 0).toLocaleString(language, { + style: "decimal", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })}`} + + + + {t("Available balance")} - - - {t("Available balance")} - - - ) : ( - - - {`$${(markets ? Number(withdrawLimit(markets, marketUSDCAddress)) / 1e6 : 0).toLocaleString( - language, - { - style: "decimal", - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }, - )}`} + + ) : ( + + + {`$${(markets ? Number(withdrawLimit(markets, marketUSDCAddress)) / 1e6 : 0).toLocaleString(language, { + style: "decimal", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })}`} + + + + {t("Available balance")} - - - {t("Available balance")} - - - )} - + + )} @@ -129,6 +122,43 @@ export default function CardContents({ /> )} + + {frozen && !disabled && !revealing && ( + + )} + {frozen && !disabled && !revealing && ( + + + + )} + ); } From c6c8d3ae79a586feb6ceae39757ea5754b51901d Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Wed, 29 Apr 2026 16:17:30 -0300 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9C=A8=20app:=20add=20freeze=20controls?= =?UTF-8?q?=20to=20home=20card?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/social-icons-retire.md | 5 + src/components/home/CardStatus.tsx | 178 +++++++++++++++++++++++++---- 2 files changed, 160 insertions(+), 23 deletions(-) create mode 100644 .changeset/social-icons-retire.md diff --git a/.changeset/social-icons-retire.md b/.changeset/social-icons-retire.md new file mode 100644 index 0000000000..525e8132d7 --- /dev/null +++ b/.changeset/social-icons-retire.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ add freeze controls to home card diff --git a/src/components/home/CardStatus.tsx b/src/components/home/CardStatus.tsx index 1de6957439..7021fcc4a1 100644 --- a/src/components/home/CardStatus.tsx +++ b/src/components/home/CardStatus.tsx @@ -14,14 +14,16 @@ import { scheduleOnRN } from "react-native-worklets"; import { selectionAsync } from "expo-haptics"; -import { CalendarDays, ChevronRight, CreditCard, Info, Wallet, Zap } from "@tamagui/lucide-icons"; -import { useTheme, View, XStack, YStack } from "tamagui"; +import { CalendarDays, ChevronRight, CreditCard, Info, Snowflake, Wallet, Zap } from "@tamagui/lucide-icons"; +import { AnimatePresence, Spinner, Square, Switch, useTheme, View, XStack, YStack } from "tamagui"; -import { useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery } from "@tanstack/react-query"; import CardBg from "../../assets/images/card-bg.svg"; import Exa from "../../assets/images/exa.svg"; +import queryClient from "../../utils/queryClient"; import reportError from "../../utils/reportError"; +import { setCardStatus, type CardDetails } from "../../utils/server"; import Text from "../shared/Text"; export default function CardStatus({ @@ -52,6 +54,19 @@ export default function CardStatus({ spotlightRef?: React.RefObject; }) { const { t } = useTranslation(); + const { data: card } = useQuery({ queryKey: ["card", "details"] }); + const { + mutateAsync: changeCardStatus, + isPending: isSettingCardStatus, + variables: optimisticCardStatus, + } = useMutation({ + mutationKey: ["card", "status"], + mutationFn: setCardStatus, + onSettled: async () => { + await queryClient.invalidateQueries({ queryKey: ["card", "details"] }); + }, + }); + const frozen = (isSettingCardStatus ? optimisticCardStatus : card?.status) === "FROZEN"; return ( - + + {frozen ? null : ( + + )} + { event.stopPropagation(); selectionAsync().catch(reportError); @@ -123,23 +143,135 @@ export default function CardStatus({ {t("Details")} + + {frozen && ( + + )} + {frozen && ( + + + + )} + - + + {frozen && ( + + { + if (isSettingCardStatus) return; + selectionAsync().catch(reportError); + changeCardStatus("ACTIVE").catch(reportError); + }} + > + + + {isSettingCardStatus ? ( + + ) : ( + + )} + + + {t("Freeze card")} + + + + + + + + + + )} + {!frozen && ( + + + + )} + - + + {!frozen && ( + + + + )} + ); } From 080a61bc68e12b8770a9caaf66d3da0aebb118ca Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Wed, 29 Apr 2026 14:06:50 -0300 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8=20app:=20create=20shared=20switch?= =?UTF-8?q?=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/spotty-rules-camp.md | 5 ++++ src/components/card/Card.tsx | 27 ++++----------------- src/components/home/CardStatus.tsx | 21 ++++------------- src/components/shared/Switch.tsx | 38 ++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 38 deletions(-) create mode 100644 .changeset/spotty-rules-camp.md create mode 100644 src/components/shared/Switch.tsx diff --git a/.changeset/spotty-rules-camp.md b/.changeset/spotty-rules-camp.md new file mode 100644 index 0000000000..621703b90d --- /dev/null +++ b/.changeset/spotty-rules-camp.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +✨ create shared switch component diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index 095b34e9ba..6493a603d6 100644 --- a/src/components/card/Card.tsx +++ b/src/components/card/Card.tsx @@ -6,7 +6,7 @@ import { useRouter } from "expo-router"; import { ChevronRight, CircleHelp, CreditCard, DollarSign, Eye, EyeOff, Hash, Snowflake } from "@tamagui/lucide-icons"; import { useToastController } from "@tamagui/toast"; -import { ScrollView, Separator, Spinner, Square, Switch, XStack, YStack } from "tamagui"; +import { ScrollView, Separator, Spinner, Square, XStack, YStack } from "tamagui"; import { useMutation, useQuery } from "@tanstack/react-query"; @@ -44,6 +44,7 @@ import LatestActivity from "../shared/LatestActivity"; import PluginUpgrade from "../shared/PluginUpgrade"; import SafeView from "../shared/SafeView"; import Skeleton from "../shared/Skeleton"; +import Switch from "../shared/Switch"; import Text from "../shared/Text"; import View from "../shared/View"; @@ -364,27 +365,9 @@ export default function Card() { {t("Freeze card")} - - - - - + + + diff --git a/src/components/home/CardStatus.tsx b/src/components/home/CardStatus.tsx index 7021fcc4a1..49631b01da 100644 --- a/src/components/home/CardStatus.tsx +++ b/src/components/home/CardStatus.tsx @@ -15,7 +15,7 @@ import { scheduleOnRN } from "react-native-worklets"; import { selectionAsync } from "expo-haptics"; import { CalendarDays, ChevronRight, CreditCard, Info, Snowflake, Wallet, Zap } from "@tamagui/lucide-icons"; -import { AnimatePresence, Spinner, Square, Switch, useTheme, View, XStack, YStack } from "tamagui"; +import { AnimatePresence, Spinner, Square, useTheme, View, XStack, YStack } from "tamagui"; import { useMutation, useQuery } from "@tanstack/react-query"; @@ -24,6 +24,7 @@ import Exa from "../../assets/images/exa.svg"; import queryClient from "../../utils/queryClient"; import reportError from "../../utils/reportError"; import { setCardStatus, type CardDetails } from "../../utils/server"; +import Switch from "../shared/Switch"; import Text from "../shared/Text"; export default function CardStatus({ @@ -216,21 +217,9 @@ export default function CardStatus({ {t("Freeze card")} - - - - - + + + )} diff --git a/src/components/shared/Switch.tsx b/src/components/shared/Switch.tsx new file mode 100644 index 0000000000..0f9d2fc417 --- /dev/null +++ b/src/components/shared/Switch.tsx @@ -0,0 +1,38 @@ +import { createStyledContext, styled, View, withStaticProperties } from "tamagui"; + +const SwitchContext = createStyledContext<{ checked: boolean }>({ checked: false }); + +const SwitchFrame = styled(View, { + name: "Switch", + context: SwitchContext, + width: "$s8", + height: "$s5", + borderRadius: "$r_0", + padding: "$s1", + animation: "default", + animateOnly: ["backgroundColor"], + variants: { + checked: { + true: { backgroundColor: "$backgroundBrandMild" }, + false: { backgroundColor: "$backgroundStrong" }, + }, + } as const, +}); + +const SwitchThumb = styled(View, { + name: "SwitchThumb", + context: SwitchContext, + width: "$s4_5", + height: "$s4_5", + borderRadius: "$r_0", + animation: "default", + animateOnly: ["transform", "backgroundColor"], + variants: { + checked: { + true: { backgroundColor: "$backgroundBrand", x: "$s5" }, + false: { backgroundColor: "$backgroundSoft", x: 0 }, + }, + } as const, +}); + +export default withStaticProperties(SwitchFrame, { Thumb: SwitchThumb }); From 7082d26b202bdc8c7d622c2ede038a536a6b62e4 Mon Sep 17 00:00:00 2001 From: guillermo dieguez Date: Wed, 29 Apr 2026 14:38:09 -0300 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=92=84=20app:=20add=20haptic=20feedba?= =?UTF-8?q?ck=20to=20card=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/tangy-deer-design.md | 5 +++++ src/components/card/Card.tsx | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/tangy-deer-design.md diff --git a/.changeset/tangy-deer-design.md b/.changeset/tangy-deer-design.md new file mode 100644 index 0000000000..2913ab7b21 --- /dev/null +++ b/.changeset/tangy-deer-design.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +💄 add haptic feedback to card actions diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index 6493a603d6..334dca0531 100644 --- a/src/components/card/Card.tsx +++ b/src/components/card/Card.tsx @@ -2,6 +2,7 @@ import React, { useRef, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { RefreshControl } from "react-native"; +import { selectionAsync } from "expo-haptics"; import { useRouter } from "expo-router"; import { ChevronRight, CircleHelp, CreditCard, DollarSign, Eye, EyeOff, Hash, Snowflake } from "@tamagui/lucide-icons"; @@ -323,6 +324,7 @@ export default function Card() { justifyContent="space-between" cursor="pointer" onPress={() => { + selectionAsync().catch(reportError); revealCard().catch(reportError); }} > @@ -346,6 +348,7 @@ export default function Card() { cursor="pointer" onPress={() => { if (isFetchingCard || isSettingCardStatus) return; + selectionAsync().catch(reportError); if (cardDetails.status === "FROZEN") { changeCardStatus("ACTIVE").catch(reportError); return; @@ -381,6 +384,7 @@ export default function Card() { justifyContent="space-between" cursor="pointer" onPress={() => { + selectionAsync().catch(reportError); setDisplayPIN(true); }} > @@ -404,6 +408,7 @@ export default function Card() { gap="$s3" onPress={() => { if (!limit) return; + selectionAsync().catch(reportError); setSpendingLimitsOpen(true); }} >