diff --git a/apps/app/components/Swap/Atomic/Form.tsx b/apps/app/components/Swap/Atomic/Form.tsx index 49e1cf85..51efb8b3 100644 --- a/apps/app/components/Swap/Atomic/Form.tsx +++ b/apps/app/components/Swap/Atomic/Form.tsx @@ -62,9 +62,9 @@ const SwapForm: FC = ({ polling = true, onQuoteChange }) => { shouldConnectWallet={shouldConnectWallet} shouldConnectDestinationWallet={shouldConnectDestinationWallet} values={values} - isValid={isValid && quote !== undefined} + isValid={isValid && quote !== undefined && !isQuoteLoading} errors={errors} - isSubmitting={isSubmitting || isQuoteLoading} + isSubmitting={isSubmitting} actionDisplayName={actionDisplayName} /> diff --git a/apps/app/hooks/useFee.ts b/apps/app/hooks/useFee.ts index e76c2554..5d1fa8ff 100644 --- a/apps/app/hooks/useFee.ts +++ b/apps/app/hooks/useFee.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import useSWR from 'swr' +import useSWR, { useSWRConfig } from 'swr' import { parseUnits } from 'viem' import { SwapFormValues } from '../components/DTOs/SwapFormValues' import TrainApiClient, { SwapQuote, AggregatedQuoteResponse } from '../lib/trainApiClient' @@ -128,6 +128,7 @@ export function useQuoteData(formValues: Props | undefined, refreshInterval?: nu : null const isQuoteLoading = useLoadingStore((state) => state.isLoading) + const { mutate: globalMutate } = useSWRConfig() const quoteFetchWrapper = useCallback(async (url: string): Promise => { const { setLoading, key, setKey } = useLoadingStore.getState() @@ -167,12 +168,17 @@ export function useQuoteData(formValues: Props | undefined, refreshInterval?: nu refreshInterval: (refreshInterval !== undefined && refreshInterval !== null) ? refreshInterval : 42000, dedupingInterval: 5000, keepPreviousData: true, + onError: (_err, key) => { + globalMutate(key, null, { revalidate: false }) + }, } ) + const resolvedQuote = (quoteError || !canGetQuote) ? undefined : data?.quote + const resolvedSolverId = (quoteError || !canGetQuote) ? undefined : data?.solverId return { - quote: (quoteError || !canGetQuote) ? undefined : data?.quote, - solverId: (quoteError || !canGetQuote) ? undefined : data?.solverId, + quote: resolvedQuote, + solverId: resolvedSolverId, isQuoteLoading, isDebouncing, quoteError: quoteError as QuoteError | undefined, diff --git a/apps/app/lib/mainStepValidator.ts b/apps/app/lib/mainStepValidator.ts index ea2e76a2..f1cfb4c1 100644 --- a/apps/app/lib/mainStepValidator.ts +++ b/apps/app/lib/mainStepValidator.ts @@ -22,7 +22,7 @@ export default function MainStepValidation(): ((values: SwapFormValues) => Formi if (!amount) { errors.amount = 'Enter an amount'; } - if (amount && !/^[0-9]*[.,]?[0-9]*$/i.test(amount.toString())) { + if (values.amount && !/^[0-9]*[.,]?[0-9]*$/i.test(values.amount)) { errors.amount = 'Invalid amount'; } if (amount && amount < 0) { diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css index ab58f1aa..328ebf6b 100644 --- a/apps/app/styles/globals.css +++ b/apps/app/styles/globals.css @@ -191,6 +191,32 @@ .rdxCommandList { max-height: var(--radix-popper-available-height); } + + @keyframes pulse-strong { + 50% { + opacity: 0.4; + } + } + .animate-pulse-strong { + animation: pulse-strong 1.4s cubic-bezier(0.77, 0, 0.17, 1) infinite; + } + + @keyframes pulse-stronger { + 50% { + opacity: 0.15; + } + } + .animate-pulse-stronger { + animation: pulse-stronger 1.4s cubic-bezier(0.77, 0, 0.17, 1) infinite; + } +} + +number-flow-react::part(left), +number-flow-react::part(right), +number-flow-react::part(left)::after, +number-flow-react::part(right)::after, +number-flow-react::part(symbol) { + padding: calc(var(--number-flow-mask-height, 0.25em) / 2) 0; } @layer base {