Skip to content

Commit

Permalink
feat: error reporting dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
ogous committed Aug 30, 2024
1 parent 1bf7f7a commit fcf8408
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 21 deletions.
3 changes: 2 additions & 1 deletion web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { graphqlEndpoint } from "@/config/graphqlEndpoint";
import { graphqlQueryGlobal } from "@/hooks/useGraphql";
import PopulateQueryCache from "@/app/PopulateQueryCache";
import BottomBanner from "@/components/Banners/BottomBanner";

import ErrorReportingDialog from "@/components/ErrorReportingDialog";
const title = "Longtail";

const description = "Longtail is Arbitrum's cheapest and most rewarding AMM.";
Expand Down Expand Up @@ -141,6 +141,7 @@ export default async function RootLayout({
</div>
</footer>
<BottomBanner />
<ErrorReportingDialog />
</Provider>
</body>
</html>
Expand Down
6 changes: 3 additions & 3 deletions web/src/app/stake/MyPositions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import { usdFormat } from "@/lib/usdFormat";
import Position from "@/assets/icons/position.svg";
import { output as seawaterContract } from "@/lib/abi/ISeawaterAMM";
import { Button } from "@/components/ui/button";
import { nanoid } from "nanoid";
import { motion } from "framer-motion";
import Link from "next/link";
import TokenIridescent from "@/assets/icons/token-iridescent.svg";
import SegmentedControl from "@/components/ui/segmented-control";
import { useAccount, useSimulateContract, useWriteContract } from "wagmi";
import { useAccount, useSimulateContract } from "wagmi";
import useWriteContract from "@/fixtures/wagmi/useWriteContract";
import { mockMyPositions } from "@/demoData/myPositions";
import { useFeatureFlag } from "@/hooks/useFeatureFlag";
import { Token, fUSDC, getTokenFromAddress } from "@/config/tokens";
Expand Down Expand Up @@ -85,7 +85,7 @@ export const MyPositions = () => {
: 0n;

const {
writeContract: writeContractCollect,
writeContractAsync: writeContractCollect,
data: collectData,
error: collectError,
isPending: isCollectPending,
Expand Down
6 changes: 3 additions & 3 deletions web/src/app/stake/pool/confirm-withdraw/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
useChainId,
useSimulateContract,
useWaitForTransactionReceipt,
useWriteContract,
} from "wagmi";
import useWriteContract from "@/fixtures/wagmi/useWriteContract";
import { fUSDC } from "@/config/tokens";
import { sqrtPriceX96ToPrice } from "@/lib/math";
import {
Expand Down Expand Up @@ -62,7 +62,7 @@ export default function ConfirmWithdrawLiquidity() {
const isWithdrawingEntirePosition = positionLiquidity?.result === delta;

const {
writeContract: writeContractUpdatePosition,
writeContractAsync: writeContractUpdatePosition,
data: updatePositionData,
error: updatePositionError,
isPending: isUpdatePositionPending,
Expand All @@ -74,7 +74,7 @@ export default function ConfirmWithdrawLiquidity() {
});

const {
writeContract: writeContractCollect,
writeContractAsync: writeContractCollect,
data: collectData,
error: collectError,
isPending: isCollectPending,
Expand Down
5 changes: 2 additions & 3 deletions web/src/app/stake/pool/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import Token from "@/assets/icons/token.svg";
import { Badge } from "@/components/ui/badge";
import { Line } from "rc-progress";
import { motion } from "framer-motion";
import { format, subDays } from "date-fns";
import ReactECharts from "echarts-for-react";
import Link from "next/link";
import { output as seawaterContract } from "@/lib/abi/ISeawaterAMM";
import { useCallback, useEffect, useMemo, useState } from "react";
Expand All @@ -29,7 +27,8 @@ import { getFormattedPriceFromTick } from "@/lib/amounts";
import { useStakeStore } from "@/stores/useStakeStore";
import { useSwapStore } from "@/stores/useSwapStore";
import { ammAddress } from "@/lib/addresses";
import { useSimulateContract, useWriteContract } from "wagmi";
import { useSimulateContract } from "wagmi";
import useWriteContract from "@/fixtures/wagmi/useWriteContract";
import {
getSqrtRatioAtTick,
getTokenAmountsNumeric,
Expand Down
10 changes: 5 additions & 5 deletions web/src/components/ConfirmStake.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
useChainId,
useSimulateContract,
useWaitForTransactionReceipt,
useWriteContract,
} from "wagmi";
import useWriteContract from "@/fixtures/wagmi/useWriteContract";
import { output as seawaterContract } from "@/lib/abi/ISeawaterAMM";
import {
sqrtPriceX96ToPrice,
Expand Down Expand Up @@ -129,27 +129,27 @@ export const ConfirmStake = ({ mode, positionId }: ConfirmStakeProps) => {

// set up write contract hooks
const {
writeContract: writeContractMint,
writeContractAsync: writeContractMint,
data: mintData,
error: mintError,
isPending: isMintPending,
} = useWriteContract();
const {
writeContract: writeContractApprovalToken0,
writeContractAsync: writeContractApprovalToken0,
data: approvalDataToken0,
error: approvalErrorToken0,
isPending: isApprovalPendingToken0,
reset: resetApproveToken0,
} = useWriteContract();
const {
writeContract: writeContractApprovalToken1,
writeContractAsync: writeContractApprovalToken1,
data: approvalDataToken1,
error: approvalErrorToken1,
isPending: isApprovalPendingToken1,
reset: resetApproveToken1,
} = useWriteContract();
const {
writeContract: writeContractUpdatePosition,
writeContractAsync: writeContractUpdatePosition,
data: updatePositionData,
error: updatePositionError,
isPending: isUpdatePositionPending,
Expand Down
9 changes: 3 additions & 6 deletions web/src/components/ConfirmSwap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
useChainId,
useSimulateContract,
useWaitForTransactionReceipt,
useWriteContract,
} from "wagmi";
import useWriteContract from "@/fixtures/wagmi/useWriteContract";
import { output as seawaterContract } from "@/lib/abi/ISeawaterAMM";
import { sqrtPriceX96ToPrice } from "@/lib/math";
import { useEffect, useCallback, useMemo } from "react";
Expand Down Expand Up @@ -109,14 +109,14 @@ export const ConfirmSwap = () => {

// set up write hooks
const {
writeContract: writeContractApproval,
writeContractAsync: writeContractApproval,
data: approvalData,
error: approvalError,
isPending: isApprovalPending,
reset: resetApproval,
} = useWriteContract();
const {
writeContract: writeContractSwap,
writeContractAsync: writeContractSwap,
data: swapData,
error: swapError,
isPending: isSwapPending,
Expand Down Expand Up @@ -207,9 +207,6 @@ export const ConfirmSwap = () => {
const performSwap = useCallback(() => {
writeContractSwap({
...swapOptions,
// Typescript doesn't support strongly typing this with destructuring
// https://github.com/microsoft/TypeScript/issues/46680
// @ts-expect-error
args: swapOptions.args,
});
}, [swapOptions, writeContractSwap]);
Expand Down
40 changes: 40 additions & 0 deletions web/src/components/ErrorReportingDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use client";
import React from "react";
import { useErrorReportingStore } from "../stores/useErrorReport";
import { Dialog, DialogContent } from "./ui/dialog";
import { Button } from "./ui/button";
import * as Sentry from "@sentry/nextjs";

export default function ErrorReportingDialog() {
const { isOpen, setIsOpen, error, setError } = useErrorReportingStore();
async function handleError() {
Sentry.captureException(error);
// close dialog
setError(null);
setIsOpen(false);
}
return (
<Dialog
open={isOpen}
onOpenChange={(isOpen) => {
setIsOpen(isOpen);
!isOpen && setError(null);
}}
>
<DialogContent className="fixed bottom-4 right-4 z-50 rounded-md bg-black ">
<div className="flex flex-col items-center justify-center gap-4 p-6 text-sm text-white">
<span className="font-bold">Report this error?</span>
<span>
{error instanceof Error ? error.name : "Unknown error name"}
</span>
<p className="max-h-40 max-w-xl overflow-x-hidden overflow-y-scroll whitespace-break-spaces rounded-md bg-white/10 p-4 text-xs">
{error instanceof Error ? error.message : "Unknown error message"}
</p>
<Button onClick={handleError} variant={"secondary"}>
Report
</Button>
</div>
</DialogContent>
</Dialog>
);
}
26 changes: 26 additions & 0 deletions web/src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";

export const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ children, ...props }, forwardedRef) => (
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay className="fixed inset-0 z-30 bg-black/50" />
<DialogPrimitive.Content {...props} ref={forwardedRef}>
<div className="mr-2 mt-2 flex justify-end">
<DialogPrimitive.Close
aria-label="Close"
className="flex size-6 items-center justify-center rounded-md bg-white"
>
<span className="text-xs">X</span>
</DialogPrimitive.Close>
</div>
{children}
</DialogPrimitive.Content>
</DialogPrimitive.Portal>
));

export const Dialog = DialogPrimitive.Root;
export const DialogTrigger = DialogPrimitive.Trigger;
DialogContent.displayName = DialogPrimitive.Content.displayName;
41 changes: 41 additions & 0 deletions web/src/fixtures/wagmi/useWriteContract.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useWriteContract as baseUseWriteContract, Config } from "wagmi";
import { WriteContractMutateAsync } from "wagmi/query";
import { useErrorReportingStore } from "@/stores/useErrorReport";

type VariablesType<T> = T extends (variables: infer V, ...args: any[]) => void
? V
: never;

export default function useWriteContract() {
const {
writeContractAsync: baseWriteContractAsync,
writeContract,
...props
} = baseUseWriteContract();
const setIsOpen = useErrorReportingStore((s) => s.setIsOpen);
const setError = useErrorReportingStore((s) => s.setError);

function handleError(error: unknown) {
if (error instanceof Error && error.message.includes("User rejected"))
return;
setError(error);
setIsOpen(true);
}

async function writeContractAsync(
props: VariablesType<WriteContractMutateAsync<Config>>,
) {
try {
return await baseWriteContractAsync(props);
} catch (error) {
handleError(error);
}
}

return {
...props,
// do not export a sync write to be able to handle error
// writeContract,
writeContractAsync,
};
}
15 changes: 15 additions & 0 deletions web/src/stores/useErrorReport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { create } from "zustand";

interface ErrorReportingStore {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
error: unknown | null;
setError: (error: unknown) => void;
}

export const useErrorReportingStore = create<ErrorReportingStore>((set) => ({
isOpen: false,
setIsOpen: (isOpen) => set({ isOpen }),
error: null,
setError: (error: unknown) => set({ error }),
}));

0 comments on commit fcf8408

Please sign in to comment.