diff --git a/packages/server/src/queries/complex/assets/index.ts b/packages/server/src/queries/complex/assets/index.ts index 3b30028b77..26d9144b4d 100644 --- a/packages/server/src/queries/complex/assets/index.ts +++ b/packages/server/src/queries/complex/assets/index.ts @@ -167,7 +167,8 @@ function filterAssetList( assetListAssets, /** Search is performed on the raw asset list data, instead of `Asset` type. */ ["symbol", "name"] as (keyof AssetListAsset)[], - params.search + params.search, + 0.2 ); assetListAssets = symbolOrNameMatches; } diff --git a/packages/stores/src/account/base.ts b/packages/stores/src/account/base.ts index 7ac0e37b57..44f5c359a8 100644 --- a/packages/stores/src/account/base.ts +++ b/packages/stores/src/account/base.ts @@ -887,6 +887,15 @@ export class AccountStore[] = []> { throw new Error("One click trading info is not available"); } + if (memo === "") { + // If the memo is empty, set it to "1CT" so we know it originated from the frontend for + // QA purposes. + memo = "1CT"; + } else { + // Otherwise, tack on "1CT" to the end of the memo. + memo += " \n1CT"; + } + const pubkey = encodePubkey( encodeSecp256k1Pubkey(accountFromSigner.pubkey) ); diff --git a/packages/tx/src/__tests__/gas.spec.ts b/packages/tx/src/__tests__/gas.spec.ts index 93e9728861..360776301b 100644 --- a/packages/tx/src/__tests__/gas.spec.ts +++ b/packages/tx/src/__tests__/gas.spec.ts @@ -42,7 +42,8 @@ describe("simulateCosmosTxBody", () => { (queryBaseAccount as jest.Mock).mockResolvedValue({ account: { "@type": BaseAccountTypeStr, - sequence: "1" }, + sequence: "1", + }, } as Awaited>); (sendTxSimulate as jest.Mock).mockResolvedValue({ gas_info: { gas_used: "200000" }, @@ -73,11 +74,11 @@ describe("simulateCosmosTxBody", () => { account: { "@type": "non-base-type-assummed-vesting", base_vesting_account: { - base_account: { - sequence: "1" - } - } - }, + base_account: { + sequence: "1", + }, + }, + }, } as Awaited>); (sendTxSimulate as jest.Mock).mockResolvedValue({ gas_info: { gas_used: "200000" }, @@ -118,7 +119,8 @@ describe("simulateCosmosTxBody", () => { (queryBaseAccount as jest.Mock).mockResolvedValue({ account: { "@type": BaseAccountTypeStr, - sequence: "invalid" }, + sequence: "invalid", + }, } as Awaited>); await expect( @@ -204,7 +206,8 @@ describe("simulateCosmosTxBody", () => { (queryBaseAccount as jest.Mock).mockResolvedValue({ account: { "@type": BaseAccountTypeStr, - sequence: "1" }, + sequence: "1", + }, }); (sendTxSimulate as jest.Mock).mockRejectedValue(new Error("Other error")); diff --git a/packages/tx/src/gas.ts b/packages/tx/src/gas.ts index fcc533c0c3..9c882a3632 100644 --- a/packages/tx/src/gas.ts +++ b/packages/tx/src/gas.ts @@ -157,7 +157,7 @@ export async function generateCosmosUnsignedTx({ bech32Address, }); - const sequence: number = parseSeqeunceFromAccount(account); + const sequence: number = parseSequenceFromAccount(account); // create placeholder transaction document const rawUnsignedTx = TxRaw.encode({ @@ -195,7 +195,7 @@ export async function generateCosmosUnsignedTx({ // Parses the sequence number from the account object. // The structure of the account object is different for base and vesting accounts. // Therefore, we need to check the type of the account object to parse the sequence number. -function parseSeqeunceFromAccount(account: any) { +function parseSequenceFromAccount(account: any) { let sequence: number = 0; if (account.account["@type"] === BaseAccountTypeStr) { const base_acc = account as BaseAccount; diff --git a/packages/web/components/bridge/amount-and-review-screen.tsx b/packages/web/components/bridge/amount-and-review-screen.tsx index 13761a97d4..dcf9048987 100644 --- a/packages/web/components/bridge/amount-and-review-screen.tsx +++ b/packages/web/components/bridge/amount-and-review-screen.tsx @@ -91,6 +91,7 @@ export const AmountAndReviewScreen = observer( enabled: !isNil(selectedAssetDenom), cacheTime: 10 * 60 * 1000, // 10 minutes staleTime: 10 * 60 * 1000, // 10 minutes + useErrorBoundary: true, } ); @@ -109,18 +110,15 @@ export const AmountAndReviewScreen = observer( const assetSupportedBridges = new Set(); if (direction === "deposit" && fromAsset) { - Object.values(fromAsset.supportedVariants) - .flat() - .forEach((provider) => assetSupportedBridges.add(provider)); + const providers = Object.values(fromAsset.supportedVariants).flat(); + providers.forEach((provider) => assetSupportedBridges.add(provider)); } else if (direction === "withdraw" && fromAsset && toAsset) { - // withdraw - counterpartySupportedAssetsByChainId[toAsset.chainId].forEach( - (asset) => { - asset.supportedVariants[fromAsset.address]?.forEach((provider) => { - assetSupportedBridges.add(provider); - }); - } - ); + const counterpartyAssets = + counterpartySupportedAssetsByChainId[toAsset.chainId]; + counterpartyAssets.forEach((asset) => { + const providers = asset.supportedVariants[fromAsset.address] || []; + providers.forEach((provider) => assetSupportedBridges.add(provider)); + }); } return Array.from(assetSupportedBridges).filter( @@ -159,14 +157,19 @@ export const AmountAndReviewScreen = observer( inputAmount: cryptoAmount, bridges: quoteBridges, onTransfer: () => { + // Ensures user queries are reset for other chains txs, since + // only cosmos txs reset queries from root store + if ( + fromChain?.chainType !== "cosmos" || + toChain?.chainType !== "cosmos" + ) { + refetchUserQueries(apiUtils); + } + setToAsset(undefined); setFromAsset(undefined); setCryptoAmount("0"); setFiatAmount("0"); - - // redundantly ensures user queries are reset for EVM txs, since - // only cosmos txs reset queries from root store - refetchUserQueries(apiUtils); }, }); diff --git a/packages/web/components/bridge/amount-screen.tsx b/packages/web/components/bridge/amount-screen.tsx index 15d8a1aa5f..93274de09b 100644 --- a/packages/web/components/bridge/amount-screen.tsx +++ b/packages/web/components/bridge/amount-screen.tsx @@ -108,6 +108,7 @@ export const AmountScreen = observer( bridgesSupportedAssets: { supportedAssetsByChainId: counterpartySupportedAssetsByChainId, supportedChains, + isLoading: isLoadingSupportedAssets, }, fromChain, @@ -209,9 +210,14 @@ export const AmountScreen = observer( ? evmAddress : toCosmosCounterpartyAccount?.address; - const { data: osmosisChain } = api.edge.chains.getChain.useQuery({ - findChainNameOrId: accountStore.osmosisChainId, - }); + const { data: osmosisChain } = api.edge.chains.getChain.useQuery( + { + findChainNameOrId: accountStore.osmosisChainId, + }, + { + useErrorBoundary: true, + } + ); const canonicalAsset = assetsInOsmosis?.[0]; @@ -572,7 +578,7 @@ export const AmountScreen = observer( if ( isLoadingCanonicalAssetPrice || - isNil(supportedSourceAssets) || + isLoadingSupportedAssets || !assetsInOsmosis || !canonicalAsset || !assetInOsmosisPrice || @@ -584,6 +590,13 @@ export const AmountScreen = observer( return ; } + /** + * This will trigger an error boundary + */ + if (!supportedSourceAssets) { + throw new Error("Supported source assets are not defined"); + } + const resetAssets = () => { setFromAsset(undefined); setToAsset(undefined); @@ -1096,24 +1109,19 @@ export const AmountScreen = observer(
{!osmosisWalletConnected ? ( - connectWalletButton + <>{connectWalletButton} ) : !isWalletNeededConnected || quote.isWrongEvmChainSelected ? ( ) : ( diff --git a/packages/web/components/bridge/asset-select-screen.tsx b/packages/web/components/bridge/asset-select-screen.tsx index 80f9e6d605..9a2e002d5f 100644 --- a/packages/web/components/bridge/asset-select-screen.tsx +++ b/packages/web/components/bridge/asset-select-screen.tsx @@ -216,31 +216,37 @@ export const AssetSelectScreen: FunctionComponent = alt={`${asset.coinDenom} asset image`} /> - - {truncateString(asset.coinName, 22)} - +
+ + {truncateString(asset.coinName, 22)} + + {!asset.isVerified && shouldShowUnverifiedAssets && ( + + + + )} +
+ {asset.coinDenom}
- {!asset.isVerified && shouldShowUnverifiedAssets && ( - - - - )} + {!shouldShowUnverifiedAssets && !asset.isVerified && (

{t("components.selectToken.clickToActivate")}

)} {asset.amount && - asset.isVerified && + (shouldShowUnverifiedAssets || asset.isVerified) && asset.usdValue && asset.amount.toDec().isPositive() && (
diff --git a/packages/web/components/bridge/bridge-network-select-modal.tsx b/packages/web/components/bridge/bridge-network-select-modal.tsx index 2727894b68..81655d3d70 100644 --- a/packages/web/components/bridge/bridge-network-select-modal.tsx +++ b/packages/web/components/bridge/bridge-network-select-modal.tsx @@ -55,6 +55,8 @@ export const BridgeNetworkSelectModal: FunctionComponent< "prettyName", ]); + const showSwitchingNetworkState = isEvmWalletConnected && isSwitchingEvmChain; + return ( } - className="min-h-[80vh] !max-w-lg" + className={classNames("!max-w-lg", { + "min-h-[80vh]": !showSwitchingNetworkState, + })} {...modalProps} onAfterClose={() => setQuery("")} >
- {isEvmWalletConnected && isSwitchingEvmChain && ( + {showSwitchingNetworkState && (
{children} + setStep(screen as ImmersiveBridgeScreen)} > - {({ currentScreen }) => ( - { - setSelectedAssetDenom(null); - setStep(ImmersiveBridgeScreen.Asset); - }} - > -
-
- {step === ImmersiveBridgeScreen.Asset ? ( -
- ) : ( - - } - onClick={() => { - setStep( - (Number(step) - 1).toString() as ImmersiveBridgeScreen - ); - }} - /> - )} - setStep(ImmersiveBridgeScreen.Asset) - : undefined, - }, - { - displayLabel: t("transfer.stepLabels.amount"), - onClick: - step === ImmersiveBridgeScreen.Review - ? () => setStep(ImmersiveBridgeScreen.Amount) - : undefined, - }, - { - displayLabel: t("transfer.stepLabels.review"), - }, - ]} - currentStep={Number(step)} - /> - } - onClick={onClose} - /> -
- -
-
- - {({ setCurrentScreen }) => - direction ? ( - { - setCurrentScreen(ImmersiveBridgeScreen.Amount); - setSelectedAssetDenom(asset.coinDenom); - }} - /> - ) : null - } - - {currentScreen !== ImmersiveBridgeScreen.Asset && - direction && ( - + {({ currentScreen }) => { + return ( + { + setSelectedAssetDenom(null); + setStep(ImmersiveBridgeScreen.Asset); + }} + > + + {({ reset: resetQueries }) => ( + ( + )} -
-
-
- - )} + onReset={resetQueries} + > +
+
+ {step === ImmersiveBridgeScreen.Asset ? ( +
+ ) : ( + + } + onClick={() => { + setStep( + ( + Number(step) - 1 + ).toString() as ImmersiveBridgeScreen + ); + }} + /> + )} + setStep(ImmersiveBridgeScreen.Asset) + : undefined, + }, + { + displayLabel: t("transfer.stepLabels.amount"), + onClick: + step === ImmersiveBridgeScreen.Review + ? () => setStep(ImmersiveBridgeScreen.Amount) + : undefined, + }, + { + displayLabel: t("transfer.stepLabels.review"), + }, + ]} + currentStep={Number(step)} + /> + } + onClick={onClose} + /> +
+ +
+
+ + {({ setCurrentScreen }) => + direction ? ( + { + setCurrentScreen( + ImmersiveBridgeScreen.Amount + ); + setSelectedAssetDenom(asset.coinDenom); + }} + /> + ) : null + } + + {currentScreen !== ImmersiveBridgeScreen.Asset && + direction && ( + + )} +
+
+
+ + )} + + + ); + }} {!isNil(fiatRampParams) && ( diff --git a/packages/web/components/bridge/use-bridges-supported-assets.ts b/packages/web/components/bridge/use-bridges-supported-assets.ts index 291c221919..2e83892bd7 100644 --- a/packages/web/components/bridge/use-bridges-supported-assets.ts +++ b/packages/web/components/bridge/use-bridges-supported-assets.ts @@ -71,6 +71,15 @@ export const useBridgesSupportedAssets = ({ [supportedAssetsResults] ); + const isLoading = useMemo( + () => + supportedAssetsResults.some( + (data): data is NonNullable> => + !isNil(data) && data.isLoading + ), + [supportedAssetsResults] + ); + /** * Aggregate supported assets from all successful queries. * This would be an object with chain id as key and an array of supported assets as value. @@ -204,5 +213,5 @@ export const useBridgesSupportedAssets = ({ ); }, [successfulQueries]); - return { supportedAssetsByChainId, supportedChains }; + return { supportedAssetsByChainId, supportedChains, isLoading }; }; diff --git a/packages/web/components/control/crypto-fiat-input.tsx b/packages/web/components/control/crypto-fiat-input.tsx index 393cddb8c9..89d79be7f1 100644 --- a/packages/web/components/control/crypto-fiat-input.tsx +++ b/packages/web/components/control/crypto-fiat-input.tsx @@ -218,6 +218,9 @@ export const CryptoFiatInput: FunctionComponent<{ input: classNames({ "text-rust-300": isInsufficientBal || isInsufficientFee, }), + ticker: classNames({ + "text-rust-300": isInsufficientBal || isInsufficientFee, + }), }} value={fiatInputRaw} onChange={(value) => { diff --git a/packages/web/components/error/__tests__/error-boundary.spec.tsx b/packages/web/components/error/__tests__/error-boundary.spec.tsx new file mode 100644 index 0000000000..b1cd0ecfb9 --- /dev/null +++ b/packages/web/components/error/__tests__/error-boundary.spec.tsx @@ -0,0 +1,67 @@ +import "@testing-library/jest-dom"; + +import { render, screen } from "@testing-library/react"; +import React from "react"; + +import { captureError } from "~/utils/error"; + +import { ErrorBoundary } from "../error-boundary"; + +jest.mock("~/utils/error"); + +describe("ErrorBoundary", () => { + const ChildComponent = () => { + throw new Error("Test error"); + }; + + beforeEach(() => { + jest.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("should render fallback UI when an error is thrown", () => { + const FallbackComponent = () =>
Something went wrong
; + + render( + + + + ); + + expect(screen.getByText("Something went wrong")).toBeInTheDocument(); + }); + + it("should call captureError when an error is thrown", () => { + const FallbackComponent = () =>
Something went wrong
; + + render( + + + + ); + + expect(captureError).toHaveBeenCalledWith(expect.any(Error)); + }); + + it("should call onError prop when an error is thrown", () => { + const FallbackComponent = () =>
Something went wrong
; + const onErrorMock = jest.fn(); + + render( + + + + ); + + expect(onErrorMock).toHaveBeenCalledWith( + expect.any(Error), + expect.any(Object) + ); + }); +}); diff --git a/packages/web/components/error/error-boundary.tsx b/packages/web/components/error/error-boundary.tsx index 9189483371..3b8a26ceb6 100644 --- a/packages/web/components/error/error-boundary.tsx +++ b/packages/web/components/error/error-boundary.tsx @@ -1,37 +1,19 @@ -import * as Sentry from "@sentry/nextjs"; -import React, { ReactNode } from "react"; - -interface ErrorBoundaryProps { - children: ReactNode; - fallback: ReactNode; -} - -interface ErrorBoundaryState { - hasError: boolean; -} - -export class ErrorBoundary extends React.Component< +import React, { ErrorInfo, FunctionComponent } from "react"; +import { + ErrorBoundary as ReactErrorBoundary, ErrorBoundaryProps, - ErrorBoundaryState -> { - constructor(props: ErrorBoundaryProps) { - super(props); - this.state = { hasError: false }; - } - - static getDerivedStateFromError(_: Error): ErrorBoundaryState { - return { hasError: true }; - } +} from "react-error-boundary"; - componentDidCatch(error: Error): void { - Sentry.captureException(error); - } +import { captureError } from "~/utils/error"; - render() { - if (this.state.hasError) { - return this.props.fallback; - } +export const ErrorBoundary: FunctionComponent = ({ + onError: onErrorProp, + ...props +}) => { + const handleError = (error: Error, info: ErrorInfo) => { + captureError(error); + onErrorProp?.(error, info); + }; - return this.props.children; - } -} + return ; +}; diff --git a/packages/web/components/error/general-error-screen.tsx b/packages/web/components/error/general-error-screen.tsx new file mode 100644 index 0000000000..d35ebd7f12 --- /dev/null +++ b/packages/web/components/error/general-error-screen.tsx @@ -0,0 +1,39 @@ +import Image from "next/image"; +import { FunctionComponent } from "react"; +import { FallbackProps } from "react-error-boundary"; + +import { Icon } from "~/components/assets"; +import { Button, IconButton } from "~/components/ui/button"; +import { useTranslation } from "~/hooks"; + +export const GeneralErrorScreen: FunctionComponent< + FallbackProps & { onClose: () => void } +> = ({ resetErrorBoundary, onClose }) => { + const { t } = useTranslation(); + return ( +
+ } + onClick={onClose} + /> +
+ Leaking beaker +

+ {t("errors.uhOhSomethingWentWrong")} +

+

{t("errors.sorryForTheInconvenience")}

+ +
+
+ ); +}; diff --git a/packages/web/components/input/scaled-currency-input.tsx b/packages/web/components/input/scaled-currency-input.tsx index 66dd90b99c..7198fbb1d8 100644 --- a/packages/web/components/input/scaled-currency-input.tsx +++ b/packages/web/components/input/scaled-currency-input.tsx @@ -72,7 +72,11 @@ export function ScaledCurrencyInput({ {fiatSymbol ? ( {fiatSymbol} @@ -88,7 +92,7 @@ export function ScaledCurrencyInput({ diff --git a/packages/web/components/table/cells/asset.tsx b/packages/web/components/table/cells/asset.tsx index cf3b9206f5..876aa4e1a9 100644 --- a/packages/web/components/table/cells/asset.tsx +++ b/packages/web/components/table/cells/asset.tsx @@ -29,21 +29,21 @@ export const AssetCell: FunctionComponent< return (
{isInUserWatchlist !== undefined && onClickWatchlist && ( - { - e.stopPropagation(); - e.preventDefault(); +
+ { + e.stopPropagation(); + e.preventDefault(); - onClickWatchlist(); - }} - className={classNames( - "cursor-pointer transition-colors duration-150 ease-out hover:text-wosmongton-300", - isInUserWatchlist ? "text-wosmongton-400" : "text-osmoverse-600" - )} - id="star" - width={24} - height={24} - /> + onClickWatchlist(); + }} + className={classNames( + "cursor-pointer transition-colors duration-150 ease-out hover:text-wosmongton-300", + isInUserWatchlist ? "text-wosmongton-400" : "text-osmoverse-600" + )} + id="star" + /> +
)}
{coinImageUrl && ( diff --git a/packages/web/localizations/de.json b/packages/web/localizations/de.json index 5ca6890012..4266773114 100644 --- a/packages/web/localizations/de.json +++ b/packages/web/localizations/de.json @@ -367,7 +367,6 @@ } }, "connectWallet": "Wallet verbinden", - "switchNetwork": "Netzwerk wechseln", "errors": { "calculatingShareOutAmount": "Problem beim Berechnen des Betrags", "depositNoBalance": "Sie haben kein Vermögen zum Einzahlen", @@ -407,7 +406,10 @@ "invalidAmounts": "Ungültige(r) Betrag(e)", "txTimedOutError": "Zeitüberschreitung bei der Transaktion. Bitte erneut versuchen.", "insufficientFee": "Unzureichendes Guthaben für Transaktionsgebühren. Bitte fügen Sie Geld hinzu, um fortzufahren.", - "noData": "Keine Daten" + "noData": "Keine Daten", + "uhOhSomethingWentWrong": "Oh oh, etwas ist schiefgelaufen", + "sorryForTheInconvenience": "Entschuldigen Sie die Unannehmlichkeiten. Bitte versuchen Sie es später erneut.", + "startAgain": "Fang nochmal an" }, "frontierMigration": { "introducingUnverifiedAssets": "Einführung nicht verifizierter Vermögenswerte", @@ -912,6 +914,7 @@ "to": "Zu {network}", "max": "Max", "available": "verfügbar", + "connectTo": "Verbinden mit {network}", "transferWith": "Übertragen mit", "transferTo": "Übertragen nach", "receiveAsset": "Anlagegut erhalten", diff --git a/packages/web/localizations/en.json b/packages/web/localizations/en.json index af8e9d8733..b7d2f11da5 100644 --- a/packages/web/localizations/en.json +++ b/packages/web/localizations/en.json @@ -367,7 +367,6 @@ } }, "connectWallet": "Connect wallet", - "switchNetwork": "Switch network", "errors": { "calculatingShareOutAmount": "Problem calculating out amount", "depositNoBalance": "You have no assets to deposit", @@ -407,7 +406,10 @@ "invalidAmounts": "Invalid amount(s)", "txTimedOutError": "Transaction timed out. Please retry.", "insufficientFee": "Insufficient balance for transaction fees. Please add funds to continue.", - "noData": "No data" + "noData": "No data", + "uhOhSomethingWentWrong": "Uh oh, something went wrong", + "sorryForTheInconvenience": "Sorry for the inconvenience. Please try again later.", + "startAgain": "Start again" }, "frontierMigration": { "introducingUnverifiedAssets": "Introducing Unverified Assets", @@ -912,6 +914,7 @@ "to": "To", "max": "Max", "available": "available", + "connectTo": "Connect to {network}", "transferWith": "Transfer with", "transferTo": "Transfer to", "receiveAsset": "Receive asset", diff --git a/packages/web/localizations/es.json b/packages/web/localizations/es.json index c4ca40accc..da984dc7a1 100644 --- a/packages/web/localizations/es.json +++ b/packages/web/localizations/es.json @@ -367,7 +367,6 @@ } }, "connectWallet": "Conectar billetera", - "switchNetwork": "Cambiar de red", "errors": { "calculatingShareOutAmount": "Problema calculando la cantidad de salida", "depositNoBalance": "No tienes activos para depositar", @@ -407,7 +406,10 @@ "invalidAmounts": "Montos no válidos", "txTimedOutError": "Se agotó el tiempo de espera de la transacción. Por favor, intenta de nuevo.", "insufficientFee": "Saldo insuficiente para tarifas de transacción. Por favor agregue fondos para continuar.", - "noData": "Sin datos" + "noData": "Sin datos", + "uhOhSomethingWentWrong": "Oh oh algo salió mal", + "sorryForTheInconvenience": "Lo siento por los inconvenientes ocasionados. Por favor, inténtelo de nuevo más tarde.", + "startAgain": "Empezar de nuevo" }, "frontierMigration": { "introducingUnverifiedAssets": "Introduciendo Activos No Verificados", @@ -912,6 +914,7 @@ "to": "A", "max": "máx.", "available": "disponible", + "connectTo": "Conéctese a {network}", "transferWith": "Transferir con", "transferTo": "Transferir a", "receiveAsset": "Recibir activo", diff --git a/packages/web/localizations/fa.json b/packages/web/localizations/fa.json index 85bf77678c..873726edae 100644 --- a/packages/web/localizations/fa.json +++ b/packages/web/localizations/fa.json @@ -367,7 +367,6 @@ } }, "connectWallet": "اتصال کیف پول", - "switchNetwork": "سوئیچ شبکه", "errors": { "calculatingShareOutAmount": "مشکل محاسباتی در بدست آوردن مقدار خروجی", "depositNoBalance": "شما هیچ دارایی برای واریز وجه انتخاب نکردید", @@ -407,7 +406,10 @@ "invalidAmounts": "مقدار(های) نامعتبر", "txTimedOutError": "زمان معامله تمام شد. لطفا دوباره امتحان کنید.", "insufficientFee": "موجودی ناکافی برای کارمزد تراکنش لطفاً برای ادامه بودجه اضافه کنید.", - "noData": "اطلاعاتی وجود ندارد" + "noData": "اطلاعاتی وجود ندارد", + "uhOhSomethingWentWrong": "اوه اوه، مشکلی پیش آمد", + "sorryForTheInconvenience": "با عرض پوزش برای ناراحتی. لطفاً بعداً دوباره امتحان کنید.", + "startAgain": "دوباره شروع کن" }, "frontierMigration": { "introducingUnverifiedAssets": "معرفی دارایی‌های تایید نشده", @@ -912,6 +914,7 @@ "to": "برای", "max": "حداکثر", "available": "در دسترس", + "connectTo": "اتصال به {network}", "transferWith": "انتقال با", "transferTo": "انتقال به", "receiveAsset": "دریافت دارایی", diff --git a/packages/web/localizations/fr.json b/packages/web/localizations/fr.json index d6803bcaf8..a5475558c9 100644 --- a/packages/web/localizations/fr.json +++ b/packages/web/localizations/fr.json @@ -367,7 +367,6 @@ } }, "connectWallet": "Connecter le portefeuille", - "switchNetwork": "Changer de réseau", "errors": { "calculatingShareOutAmount": "Problème de calcul du montant de sortie", "depositNoBalance": "Vous n'avez pas d'actifs à déposer", @@ -407,7 +406,10 @@ "invalidAmounts": "Montant(s) invalide(s)", "txTimedOutError": "La transaction a expiré. Veuillez réessayer.", "insufficientFee": "Solde insuffisant pour les frais de transaction. Veuillez ajouter des fonds pour continuer.", - "noData": "Pas de données" + "noData": "Pas de données", + "uhOhSomethingWentWrong": "Oh oh, quelque chose s'est mal passé", + "sorryForTheInconvenience": "Désolé pour le dérangement. Veuillez réessayer plus tard.", + "startAgain": "Recommencer" }, "frontierMigration": { "introducingUnverifiedAssets": "Introduction des actifs non vérifiés", @@ -912,6 +914,7 @@ "to": "Vers", "max": "Max.", "available": "disponible", + "connectTo": "Connectez-vous à {network}", "transferWith": "Transfert avec", "transferTo": "Transférer à", "receiveAsset": "Recevoir l'actif", diff --git a/packages/web/localizations/gu.json b/packages/web/localizations/gu.json index 675fcbf66c..a7beaf3a5e 100644 --- a/packages/web/localizations/gu.json +++ b/packages/web/localizations/gu.json @@ -367,7 +367,6 @@ } }, "connectWallet": "વૉલેટ કનેક્ટ કરો", - "switchNetwork": "નેટવર્ક સ્વિચ કરો", "errors": { "calculatingShareOutAmount": "રકમની ગણતરી કરવામાં સમસ્યા", "depositNoBalance": "તમારી પાસે જમા કરવા માટે કોઈ સંપત્તિ નથી", @@ -407,7 +406,10 @@ "invalidAmounts": "અમાન્ય રકમ(ઓ)", "txTimedOutError": "વ્યવહારનો સમય સમાપ્ત થયો. કૃપા કરીને ફરી પ્રયાસ કરો.", "insufficientFee": "વ્યવહાર શુલ્ક માટે અપર્યાપ્ત બેલેન્સ. ચાલુ રાખવા માટે કૃપા કરીને ભંડોળ ઉમેરો.", - "noData": "કોઈ ડેટા નથી" + "noData": "કોઈ ડેટા નથી", + "uhOhSomethingWentWrong": "ઓહ, કંઈક ખોટું થયું", + "sorryForTheInconvenience": "અસુવીધી બદલ માફી. પછીથી ફરી પ્રયત્ન કરો.", + "startAgain": "ફરી શરૂ કરો" }, "frontierMigration": { "introducingUnverifiedAssets": "વણચકાસાયેલ અસ્કયામતોનો પરિચય", @@ -912,6 +914,7 @@ "to": "માટે", "max": "મહત્તમ", "available": "ઉપલબ્ધ", + "connectTo": "{network} થી કનેક્ટ કરો", "transferWith": "સાથે ટ્રાન્સફર કરો", "transferTo": "પર ટ્રાન્સફર કરો", "receiveAsset": "સંપત્તિ પ્રાપ્ત કરો", diff --git a/packages/web/localizations/hi.json b/packages/web/localizations/hi.json index 9a2f4a2ccb..c8e6d11a57 100644 --- a/packages/web/localizations/hi.json +++ b/packages/web/localizations/hi.json @@ -367,7 +367,6 @@ } }, "connectWallet": "वॉलेट कनेक्ट करें", - "switchNetwork": "नेटवर्क स्विच करें", "errors": { "calculatingShareOutAmount": "राशि की गणना करने में समस्या", "depositNoBalance": "आपके पास जमा करने के लिए कोई संपत्ति नहीं है", @@ -407,7 +406,10 @@ "invalidAmounts": "अमान्य राशि", "txTimedOutError": "लेन-देन का समय समाप्त हो गया. कृपया फिर से कोशिश करें।", "insufficientFee": "लेन-देन शुल्क के लिए अपर्याप्त शेष. कृपया जारी रखने के लिए धनराशि जोड़ें।", - "noData": "कोई डेटा नहीं" + "noData": "कोई डेटा नहीं", + "uhOhSomethingWentWrong": "ओह ओह, कुछ ग़लत हो गया", + "sorryForTheInconvenience": "असुविधा के लिए खेद है। कृपया बाद में पुनः प्रयास करें।", + "startAgain": "फिर से शुरू करें" }, "frontierMigration": { "introducingUnverifiedAssets": "असत्यापित संपत्तियों का परिचय", @@ -912,6 +914,7 @@ "to": "को", "max": "अधिकतम", "available": "उपलब्ध", + "connectTo": "{network} से कनेक्ट करें", "transferWith": "साथ स्थानांतरित करें", "transferTo": "के लिए स्थानांतरण", "receiveAsset": "संपत्ति प्राप्त करें", diff --git a/packages/web/localizations/ja.json b/packages/web/localizations/ja.json index 02fb17bed5..1cde50edc4 100644 --- a/packages/web/localizations/ja.json +++ b/packages/web/localizations/ja.json @@ -367,7 +367,6 @@ } }, "connectWallet": "ウォレットを接続する", - "switchNetwork": "ネットワークを切り替える", "errors": { "calculatingShareOutAmount": "金額の計算に問題があります", "depositNoBalance": "預ける資産がありません", @@ -407,7 +406,10 @@ "invalidAmounts": "無効な金額です", "txTimedOutError": "トランザクションがタイムアウトしました。再試行してください。", "insufficientFee": "取引手数料の残高が不足しています。続行するには資金を追加してください。", - "noData": "データなし" + "noData": "データなし", + "uhOhSomethingWentWrong": "ああ、何か問題が発生しました", + "sorryForTheInconvenience": "ご不便をおかけして申し訳ございません。しばらくしてからもう一度お試しください。", + "startAgain": "再開する" }, "frontierMigration": { "introducingUnverifiedAssets": "未検証のアセットの紹介", @@ -912,6 +914,7 @@ "to": "{network}へ", "max": "マックス", "available": "利用可能", + "connectTo": "{network}に接続します", "transferWith": "転送", "transferTo": "転送先", "receiveAsset": "資産を受け取る", diff --git a/packages/web/localizations/ko.json b/packages/web/localizations/ko.json index b8e0e7cbf7..7a0180af70 100644 --- a/packages/web/localizations/ko.json +++ b/packages/web/localizations/ko.json @@ -367,7 +367,6 @@ } }, "connectWallet": "지갑 연결하기", - "switchNetwork": "네트워크 전환", "errors": { "calculatingShareOutAmount": "수량을 계산하는데 오류가 발생했습니다", "depositNoBalance": "입금할 자산이 없습니다", @@ -407,7 +406,10 @@ "invalidAmounts": "잘못된 금액", "txTimedOutError": "거래 시간이 초과되었습니다. 다시 시도해 주세요.", "insufficientFee": "거래 수수료 잔액이 부족합니다. 계속하려면 자금을 추가하세요.", - "noData": "데이터 없음" + "noData": "데이터 없음", + "uhOhSomethingWentWrong": "아, 뭔가 잘못됐어", + "sorryForTheInconvenience": "불편을 드려 죄송합니다. 나중에 다시 시도 해주십시오.", + "startAgain": "다시 시작" }, "frontierMigration": { "introducingUnverifiedAssets": "검증되지 않은 자산 소개", @@ -912,6 +914,7 @@ "to": "에게", "max": "맥스", "available": "사용 가능", + "connectTo": "{network} 에 연결", "transferWith": "다음으로 환승", "transferTo": "로 전송", "receiveAsset": "자산 수령", diff --git a/packages/web/localizations/pl.json b/packages/web/localizations/pl.json index 382465b11b..d7672cc752 100644 --- a/packages/web/localizations/pl.json +++ b/packages/web/localizations/pl.json @@ -367,7 +367,6 @@ } }, "connectWallet": "Połącz portfel", - "switchNetwork": "Przełącz sieć", "errors": { "calculatingShareOutAmount": "Problem z obliczeniem wartości", "depositNoBalance": "Nie masz środków do wpłaty", @@ -407,7 +406,10 @@ "invalidAmounts": "Nieprawidłowe kwoty", "txTimedOutError": "Upłynął limit czasu transakcji. Proszę spróbuj ponownie.", "insufficientFee": "Niewystarczające saldo opłat transakcyjnych. Dodaj środki, aby kontynuować.", - "noData": "Brak danych" + "noData": "Brak danych", + "uhOhSomethingWentWrong": "Oj, coś poszło nie tak", + "sorryForTheInconvenience": "Przepraszam za niedogodności. Spróbuj ponownie później.", + "startAgain": "Zacznij jeszcze raz" }, "frontierMigration": { "introducingUnverifiedAssets": "Wprowadzanie niezweryfikowanych aktywów", @@ -912,6 +914,7 @@ "to": "Do", "max": "Maks", "available": "dostępny", + "connectTo": "Połącz się z {network}", "transferWith": "Przenieś z", "transferTo": "Przenieść do", "receiveAsset": "Odbierz zasób", diff --git a/packages/web/localizations/pt-br.json b/packages/web/localizations/pt-br.json index 51bab3c0e4..87b937d5a9 100644 --- a/packages/web/localizations/pt-br.json +++ b/packages/web/localizations/pt-br.json @@ -367,7 +367,6 @@ } }, "connectWallet": "Conectar carteira", - "switchNetwork": "Trocar de rede", "errors": { "calculatingShareOutAmount": "Problema ao calcular o valor", "depositNoBalance": "Você não tem ativos para depositar", @@ -407,7 +406,10 @@ "invalidAmounts": "Quantidade(s) inválida(s)", "txTimedOutError": "A transação expirou. Por favor tente novamente.", "insufficientFee": "Saldo insuficiente para taxas de transação. Adicione fundos para continuar.", - "noData": "Sem dados" + "noData": "Sem dados", + "uhOhSomethingWentWrong": "Ah, ah, algo deu errado", + "sorryForTheInconvenience": "Desculpe pela inconveniência. Por favor, tente novamente mais tarde.", + "startAgain": "Comece de novo" }, "frontierMigration": { "introducingUnverifiedAssets": "Apresentando Ativos Não Verificados", @@ -912,6 +914,7 @@ "to": "Para", "max": "Máx.", "available": "disponível", + "connectTo": "Conecte-se a {network}", "transferWith": "Transferir com", "transferTo": "Transferir para", "receiveAsset": "Receber ativo", diff --git a/packages/web/localizations/ro.json b/packages/web/localizations/ro.json index 7f2ec3508a..11ff12378e 100644 --- a/packages/web/localizations/ro.json +++ b/packages/web/localizations/ro.json @@ -367,7 +367,6 @@ } }, "connectWallet": "Conecteaza Portofel", - "switchNetwork": "Schimbați rețeaua", "errors": { "calculatingShareOutAmount": "Nu s-a putut calcula suma rezultata", "depositNoBalance": "Nu ai active de depozitat", @@ -407,7 +406,10 @@ "invalidAmounts": "Sume nevalide", "txTimedOutError": "Tranzacția a expirat. Vă rugăm să reîncercați.", "insufficientFee": "Sold insuficient pentru taxele de tranzacție. Vă rugăm să adăugați fonduri pentru a continua.", - "noData": "Nu există date" + "noData": "Nu există date", + "uhOhSomethingWentWrong": "Uh oh, ceva a mers prost", + "sorryForTheInconvenience": "Îmi pare rău pentru neplăcerile create. Vă rugăm să încercați din nou mai târziu.", + "startAgain": "Incepe din nou" }, "frontierMigration": { "introducingUnverifiedAssets": "Introducerea activelor neverificate", @@ -912,6 +914,7 @@ "to": "Către", "max": "Max", "available": "disponibil", + "connectTo": "Conectați-vă la {network}", "transferWith": "Transfer cu", "transferTo": "Transfer către", "receiveAsset": "Primiți activ", diff --git a/packages/web/localizations/ru.json b/packages/web/localizations/ru.json index b893778eb9..0013bfc0d8 100644 --- a/packages/web/localizations/ru.json +++ b/packages/web/localizations/ru.json @@ -367,7 +367,6 @@ } }, "connectWallet": "Подключить кошелек", - "switchNetwork": "Переключить сеть", "errors": { "calculatingShareOutAmount": "Проблема с расчетом суммы", "depositNoBalance": "У вас нет активов для депозита", @@ -407,7 +406,10 @@ "invalidAmounts": "Неверная сумма(ы)", "txTimedOutError": "Время транзакции истекло. Пожалуйста, повторите попытку.", "insufficientFee": "Недостаточно средств для оплаты комиссий за транзакции. Пожалуйста, добавьте средства, чтобы продолжить.", - "noData": "Нет данных" + "noData": "Нет данных", + "uhOhSomethingWentWrong": "Ой-ой, что-то пошло не так", + "sorryForTheInconvenience": "Приносим извинения за неудобства. Пожалуйста, повторите попытку позже.", + "startAgain": "Начать заново" }, "frontierMigration": { "introducingUnverifiedAssets": "Представляем непроверенные активы", @@ -912,6 +914,7 @@ "to": "Чтобы", "max": "Макс", "available": "доступный", + "connectTo": "Подключитесь к {network}", "transferWith": "Трансфер с", "transferTo": "Перевести", "receiveAsset": "Получить актив", diff --git a/packages/web/localizations/tr.json b/packages/web/localizations/tr.json index ee01ad8ee4..5fe3233d9c 100644 --- a/packages/web/localizations/tr.json +++ b/packages/web/localizations/tr.json @@ -367,7 +367,6 @@ } }, "connectWallet": "Cüzdanı bağla", - "switchNetwork": "Ağı değiştir", "errors": { "calculatingShareOutAmount": "Miktar hesaplamasında sorun", "depositNoBalance": "Yatıracak varlığınız yok", @@ -407,7 +406,10 @@ "invalidAmounts": "Geçersiz tutarlar", "txTimedOutError": "İşlem zaman aşımına uğradı. Lütfen tekrar deneyiniz.", "insufficientFee": "İşlem ücretleri için yeterli bakiye yok. Devam etmek için lütfen para ekleyin.", - "noData": "Veri yok" + "noData": "Veri yok", + "uhOhSomethingWentWrong": "Ah, bir şeyler ters gitti", + "sorryForTheInconvenience": "Rahatsızlıktan dolayı özür dileriz. Lütfen daha sonra tekrar deneyiniz.", + "startAgain": "Tekrar başla" }, "frontierMigration": { "introducingUnverifiedAssets": "Doğrulanmamış Varlıkların Tanıtılması", @@ -912,6 +914,7 @@ "to": "için", "max": "Maksimum", "available": "mevcut", + "connectTo": "{network} ağına bağlanın", "transferWith": "Şununla aktar:", "transferTo": "Transfer", "receiveAsset": "Varlık al", diff --git a/packages/web/localizations/zh-cn.json b/packages/web/localizations/zh-cn.json index 3a94306ace..789d8cce60 100644 --- a/packages/web/localizations/zh-cn.json +++ b/packages/web/localizations/zh-cn.json @@ -367,7 +367,6 @@ } }, "connectWallet": "连接钱包", - "switchNetwork": "切换网络", "errors": { "calculatingShareOutAmount": "计算金额溢出", "depositNoBalance": "您没有可以存入的资产", @@ -407,7 +406,10 @@ "invalidAmounts": "金额无效", "txTimedOutError": "交易超时。请重试。", "insufficientFee": "余额不足,无法支付交易费用。请添加资金以继续。", - "noData": "没有数据" + "noData": "没有数据", + "uhOhSomethingWentWrong": "哦,出问题了", + "sorryForTheInconvenience": "抱歉造成不便。请稍后重试。", + "startAgain": "重新开始" }, "frontierMigration": { "introducingUnverifiedAssets": "介绍未经验证的资产", @@ -912,6 +914,7 @@ "to": "到", "max": "最大限度", "available": "可用的", + "connectTo": "连接到{network}", "transferWith": "转让", "transferTo": "传送到", "receiveAsset": "接收资产", diff --git a/packages/web/localizations/zh-hk.json b/packages/web/localizations/zh-hk.json index eb08037911..c295fd5049 100644 --- a/packages/web/localizations/zh-hk.json +++ b/packages/web/localizations/zh-hk.json @@ -367,7 +367,6 @@ } }, "connectWallet": "連接錢包", - "switchNetwork": "交換網絡", "errors": { "calculatingShareOutAmount": "計算幣額時出錯", "depositNoBalance": "你冇資產存入喎", @@ -407,7 +406,10 @@ "invalidAmounts": "金額無效", "txTimedOutError": "交易超時。請重試。", "insufficientFee": "餘額不足,無法支付交易費用。請添加資金以繼續。", - "noData": "沒有數據" + "noData": "沒有數據", + "uhOhSomethingWentWrong": "呃哦,出了點問題", + "sorryForTheInconvenience": "帶來不便敬請諒解。請稍後再試。", + "startAgain": "重新開始" }, "frontierMigration": { "introducingUnverifiedAssets": "介紹未經驗證的資產", @@ -912,6 +914,7 @@ "to": "到", "max": "最大限度", "available": "可用的", + "connectTo": "連接到{network}", "transferWith": "轉移與", "transferTo": "傳送到", "receiveAsset": "接收資產", diff --git a/packages/web/localizations/zh-tw.json b/packages/web/localizations/zh-tw.json index d91c13eebc..6f63dc6ba6 100644 --- a/packages/web/localizations/zh-tw.json +++ b/packages/web/localizations/zh-tw.json @@ -367,7 +367,6 @@ } }, "connectWallet": "連接錢包", - "switchNetwork": "交換網絡", "errors": { "calculatingShareOutAmount": "計算幣額時出錯", "depositNoBalance": "你沒有資產存入", @@ -407,7 +406,10 @@ "invalidAmounts": "金額無效", "txTimedOutError": "交易超時。請重試。", "insufficientFee": "餘額不足,無法支付交易費用。請添加資金以繼續。", - "noData": "沒有數據" + "noData": "沒有數據", + "uhOhSomethingWentWrong": "呃哦,出了點問題", + "sorryForTheInconvenience": "帶來不便敬請諒解。請稍後再試。", + "startAgain": "重新開始" }, "frontierMigration": { "introducingUnverifiedAssets": "介紹未經驗證的資產", @@ -912,6 +914,7 @@ "to": "到", "max": "最大限度", "available": "可用的", + "connectTo": "連接到{network}", "transferWith": "轉移與", "transferTo": "傳送到", "receiveAsset": "接收資產", diff --git a/packages/web/package.json b/packages/web/package.json index b5a4f238f1..d98aa4169a 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -133,6 +133,7 @@ "qrcode": "^1.5.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.13", "react-input-autosize": "^3.0.0", "react-markdown": "8.0.7", "react-modal": "^3.16.1", diff --git a/packages/web/pages/_app.tsx b/packages/web/pages/_app.tsx index b26d72cd83..b89ec4efd9 100644 --- a/packages/web/pages/_app.tsx +++ b/packages/web/pages/_app.tsx @@ -19,11 +19,11 @@ import { ComponentType, useMemo } from "react"; import { FunctionComponent } from "react"; import { ReactNode } from "react"; import { useEffect } from "react"; +import { ErrorBoundary } from "react-error-boundary"; import { Bounce, ToastContainer } from "react-toastify"; import { WagmiProvider } from "wagmi"; import { Icon } from "~/components/assets"; -import { ErrorBoundary } from "~/components/error/error-boundary"; import { ErrorFallback } from "~/components/error/error-fallback"; import { Pill } from "~/components/indicators/pill"; import { MainLayout } from "~/components/layouts"; @@ -77,22 +77,22 @@ function MyApp({ Component, pageProps }: AppProps) { > - - - - - - }> + }> + + + + + {Component && } - - - + + + diff --git a/packages/web/public/images/leaking-beaker.svg b/packages/web/public/images/leaking-beaker.svg new file mode 100644 index 0000000000..1ef2ec0912 --- /dev/null +++ b/packages/web/public/images/leaking-beaker.svg @@ -0,0 +1,1169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/utils/error.ts b/packages/web/utils/error.ts new file mode 100644 index 0000000000..d192c8e713 --- /dev/null +++ b/packages/web/utils/error.ts @@ -0,0 +1,10 @@ +import * as Sentry from "@sentry/nextjs"; + +export function captureError(e: any) { + if (e instanceof Error) { + Sentry.captureException(e); + if (process.env.NODE_ENV === "development") console.warn("Captured:", e); + } else if (process.env.NODE_ENV === "development") { + console.warn("Did not capture non-Error:", e); + } +} diff --git a/yarn.lock b/yarn.lock index 75bb38900c..01c81f9814 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23998,6 +23998,13 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-error-boundary@^4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.13.tgz#80386b7b27b1131c5fbb7368b8c0d983354c7947" + integrity sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ== + dependencies: + "@babel/runtime" "^7.12.5" + react-input-autosize@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz"