From 3870d4414884bf4455e7b47e1a0390d30a265f52 Mon Sep 17 00:00:00 2001 From: chyyynh Date: Fri, 14 Nov 2025 22:49:29 +0800 Subject: [PATCH 1/2] chore:migrate react-toastify to sooner --- app/positions/components/PositionsContent.tsx | 4 +- package.json | 2 +- pnpm-lock.yaml | 29 +++---- src/components/common/StyledToast.tsx | 28 ------ .../providers/ConnectRedirectProvider.tsx | 2 +- src/components/providers/ThemeProvider.tsx | 31 +++++-- src/hooks/useStyledToast.tsx | 72 +++++++++++++--- src/hooks/useTransactionWithToast.tsx | 86 +++++++++++-------- src/hooks/useWrapLegacyMorpho.ts | 5 +- 9 files changed, 155 insertions(+), 104 deletions(-) delete mode 100644 src/components/common/StyledToast.tsx diff --git a/app/positions/components/PositionsContent.tsx b/app/positions/components/PositionsContent.tsx index 71d45b41..ac613443 100644 --- a/app/positions/components/PositionsContent.tsx +++ b/app/positions/components/PositionsContent.tsx @@ -6,7 +6,6 @@ import { useParams } from 'next/navigation'; import { FaHistory, FaPlus } from 'react-icons/fa'; import { IoRefreshOutline } from 'react-icons/io5'; import { TbReport } from 'react-icons/tb'; -import { toast } from 'react-toastify'; import { Address } from 'viem'; import { useAccount } from 'wagmi'; import { AddressDisplay } from '@/components/common/AddressDisplay'; @@ -16,6 +15,7 @@ import EmptyScreen from '@/components/Status/EmptyScreen'; import LoadingScreen from '@/components/Status/LoadingScreen'; import { SupplyModalV2 } from '@/components/SupplyModalV2'; import { useMarkets } from '@/hooks/useMarkets'; +import { useStyledToast } from '@/hooks/useStyledToast'; import useUserPositionsSummaryData from '@/hooks/useUserPositionsSummaryData'; import { MarketPosition } from '@/utils/types'; import { OnboardingModal } from './onboarding/OnboardingModal'; @@ -32,6 +32,8 @@ export default function Positions() { const [mounted, setMounted] = useState(false); + const toast = useStyledToast(); + const isOwner = useMemo(() => { if (!account || !address || !mounted) return false; return account === address; diff --git a/package.json b/package.json index dc3a8065..5f63496e 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "react-remove-scroll": "^2.5.7", "react-spinners": "^0.14.1", "react-table": "^7.8.0", - "react-toastify": "11.0.2", "react-type-animation": "^3.2.0", "recharts": "^2.13.0", "rehype-pretty-code": "0.12.3", @@ -73,6 +72,7 @@ "sharp": "^0.33.5", "shikiji": "^0.9.17", "shikiji-core": "^0.9.17", + "sonner": "^2.0.7", "tailwind-merge": "^2.5.5", "tailwind-variants": "^2.1.0", "unified": "^11.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f62521f..8c815fea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,9 +140,6 @@ importers: react-table: specifier: ^7.8.0 version: 7.8.0(react@18.3.1) - react-toastify: - specifier: 11.0.2 - version: 11.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-type-animation: specifier: ^3.2.0 version: 3.2.0(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -170,6 +167,9 @@ importers: shikiji-core: specifier: ^0.9.17 version: 0.9.19 + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwind-merge: specifier: ^2.5.5 version: 2.6.0 @@ -6762,12 +6762,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-toastify@11.0.2: - resolution: {integrity: sha512-GjHuGaiXMvbls3ywqv8XdWONwrcO4DXCJIY1zVLkHU73gEElKvTTXNI5Vom3s/k/M8hnkrfsqgBSX3OwmlonbA==} - peerDependencies: - react: ^18 || ^19 - react-dom: ^18 || ^19 - react-transition-group@4.4.5: resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: @@ -7068,6 +7062,12 @@ packages: sonic-boom@3.8.1: resolution: {integrity: sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==} + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -16947,12 +16947,6 @@ snapshots: transitivePeerDependencies: - '@types/react' - react-toastify@11.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - clsx: 2.1.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.28.3 @@ -17369,6 +17363,11 @@ snapshots: dependencies: atomic-sleep: 1.0.0 + sonner@2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + source-map-js@1.2.1: {} source-map-support@0.5.13: diff --git a/src/components/common/StyledToast.tsx b/src/components/common/StyledToast.tsx deleted file mode 100644 index 0e9c02e9..00000000 --- a/src/components/common/StyledToast.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { TxHashDisplay } from '../TxHashDisplay'; - -export function StyledToast({ title, message }: { title: string; message?: string }) { - return ( -
-
{title}
- {message &&
{message}
} -
- ); -} - -export function TransactionToast({ - title, - description, - hash, -}: { - title: string; - hash?: string; - description?: string; -}) { - return ( -
-
{title}
- {description &&
{description}
} - -
- ); -} diff --git a/src/components/providers/ConnectRedirectProvider.tsx b/src/components/providers/ConnectRedirectProvider.tsx index 63330804..106fd12c 100644 --- a/src/components/providers/ConnectRedirectProvider.tsx +++ b/src/components/providers/ConnectRedirectProvider.tsx @@ -21,7 +21,7 @@ export function ConnectRedirectProvider({ children }: { children: ReactNode }) { if (redirectPath && !isReconnected) { router.push(`/${redirectPath}/${address}`); toast.success('Address connected', 'Redirecting to portfolio...', { - toastId: 'address-connected', + id: 'address-connected', }); // Reset the path after redirect setRedirectPath(undefined); diff --git a/src/components/providers/ThemeProvider.tsx b/src/components/providers/ThemeProvider.tsx index db3496c1..128031d1 100644 --- a/src/components/providers/ThemeProvider.tsx +++ b/src/components/providers/ThemeProvider.tsx @@ -1,8 +1,29 @@ 'use client'; import { HeroUIProvider } from '@heroui/react'; -import { ThemeProvider as NextThemesProvider } from 'next-themes'; -import { ToastContainer } from 'react-toastify'; +import { ThemeProvider as NextThemesProvider, useTheme } from 'next-themes'; +import { Toaster } from 'sonner'; + +function ToasterProvider() { + const { theme } = useTheme(); + + return ( + + ); +} export function ThemeProviders({ children }: { children: React.ReactNode }) { return ( @@ -14,11 +35,7 @@ export function ThemeProviders({ children }: { children: React.ReactNode }) { themes={['light', 'dark']} > {children} - + ); } diff --git a/src/hooks/useStyledToast.tsx b/src/hooks/useStyledToast.tsx index 383a8ce7..cfba2ba9 100644 --- a/src/hooks/useStyledToast.tsx +++ b/src/hooks/useStyledToast.tsx @@ -1,20 +1,66 @@ -import React, { useCallback } from 'react'; -import { toast, ToastOptions } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; -import { StyledToast } from '../components/common/StyledToast'; +import { useCallback } from 'react'; +import { toast, ExternalToast } from 'sonner'; + +type ToastMessage = string | undefined; export function useStyledToast() { - const success = useCallback((title: string, message?: string, options?: ToastOptions) => { - toast.success(, options); - }, []); + const success = useCallback( + (title: string, messageOrOptions?: ToastMessage | ExternalToast, options?: ExternalToast) => { + // If second param is an object, treat it as options + const isSecondParamOptions = + typeof messageOrOptions === 'object' && messageOrOptions !== null; + const message = isSecondParamOptions ? undefined : (messageOrOptions as string | undefined); + const finalOptions = isSecondParamOptions + ? (messageOrOptions as ExternalToast) + : options; + + toast.success(title, { + description: message, + className: 'font-zen', + descriptionClassName: 'font-inter text-xs', + ...finalOptions, + }); + }, + [], + ); + + const error = useCallback( + (title: string, messageOrOptions?: ToastMessage | ExternalToast, options?: ExternalToast) => { + const isSecondParamOptions = + typeof messageOrOptions === 'object' && messageOrOptions !== null; + const message = isSecondParamOptions ? undefined : (messageOrOptions as string | undefined); + const finalOptions = isSecondParamOptions + ? (messageOrOptions as ExternalToast) + : options; + + toast.error(title, { + description: message, + className: 'font-zen', + descriptionClassName: 'font-inter text-xs', + ...finalOptions, + }); + }, + [], + ); - const error = useCallback((title: string, message?: string, options?: ToastOptions) => { - toast.error(, options); - }, []); + const info = useCallback( + (title: string, messageOrOptions?: ToastMessage | ExternalToast, options?: ExternalToast) => { + const isSecondParamOptions = + typeof messageOrOptions === 'object' && messageOrOptions !== null; + const message = isSecondParamOptions ? undefined : (messageOrOptions as string | undefined); + const finalOptions = isSecondParamOptions + ? (messageOrOptions as ExternalToast) + : options; - const info = useCallback((title: string, message?: string, options?: ToastOptions) => { - toast.info(, options); - }, []); + toast.info(title, { + description: message, + className: 'font-zen', + descriptionClassName: 'font-inter text-xs', + ...finalOptions, + }); + }, + [], + ); return { success, error, info }; } diff --git a/src/hooks/useTransactionWithToast.tsx b/src/hooks/useTransactionWithToast.tsx index 7ffd98ed..4f47b527 100644 --- a/src/hooks/useTransactionWithToast.tsx +++ b/src/hooks/useTransactionWithToast.tsx @@ -1,8 +1,7 @@ import React, { useCallback, useEffect } from 'react'; -import { toast } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; +import { toast } from 'sonner'; import { useSendTransaction, useWaitForTransactionReceipt } from 'wagmi'; -import { StyledToast, TransactionToast } from '@/components/common/StyledToast'; +import { TxHashDisplay } from '@/components/TxHashDisplay'; import { getExplorerTxURL } from '../utils/external'; import { SupportedNetworks } from '../utils/networks'; @@ -50,51 +49,66 @@ export function useTransactionWithToast({ }, [hash, chainId]); useEffect(() => { - if (isConfirming) { - toast.loading( - , - { - toastId, - onClick, - closeButton: true, - }, - ); + if (isConfirming && hash) { + toast.loading(pendingText, { + id: toastId, + description: ( +
+ {pendingDescription &&
{pendingDescription}
} + +
+ ), + action: hash + ? { + label: 'View', + onClick, + } + : undefined, + className: 'font-zen', + }); } }, [isConfirming, pendingText, pendingDescription, toastId, onClick, hash]); useEffect(() => { - if (isConfirmed) { - toast.update(toastId, { - render: ( - + if (isConfirmed && hash) { + toast.success(`${successText} 🎉`, { + id: toastId, + description: ( +
+ {successDescription &&
{successDescription}
} + +
), - type: 'success', - isLoading: false, - autoClose: 5000, - onClick, - closeButton: true, + action: { + label: 'View', + onClick, + }, + duration: 5000, + className: 'font-zen', }); if (onSuccess) { onSuccess(); } } if (isError || txError) { - toast.update(toastId, { - render: ( - + toast.error(errorText, { + id: toastId, + description: ( +
+
+ {txError ? txError.message : 'Transaction Failed'} +
+ {hash && } +
), - type: 'error', - isLoading: false, - autoClose: 5000, - onClick, - closeButton: true, + action: hash + ? { + label: 'View', + onClick, + } + : undefined, + duration: 5000, + className: 'font-zen', }); } }, [ diff --git a/src/hooks/useWrapLegacyMorpho.ts b/src/hooks/useWrapLegacyMorpho.ts index 5e43fefa..d3c25f8d 100644 --- a/src/hooks/useWrapLegacyMorpho.ts +++ b/src/hooks/useWrapLegacyMorpho.ts @@ -1,9 +1,9 @@ import { useCallback, useState } from 'react'; -import { toast } from 'react-toastify'; import { Address, encodeFunctionData } from 'viem'; import { useAccount, useSwitchChain } from 'wagmi'; import wrapperABI from '@/abis/morpho-wrapper'; +import { useStyledToast } from '@/hooks/useStyledToast'; import { useTransactionWithToast } from '@/hooks/useTransactionWithToast'; import { SupportedNetworks } from '@/utils/networks'; import { MORPHO_LEGACY, MORPHO_TOKEN_WRAPPER } from '@/utils/tokens'; @@ -17,6 +17,7 @@ export function useWrapLegacyMorpho(amount: bigint, onSuccess?: () => void) { const { address: account, chainId } = useAccount(); const { switchChainAsync } = useSwitchChain(); + const toast = useStyledToast(); const { isApproved, approve } = useERC20Approval({ token: MORPHO_LEGACY as Address, @@ -69,7 +70,7 @@ export function useWrapLegacyMorpho(amount: bigint, onSuccess?: () => void) { toast.error('Failed to wrap MORPHO.'); setShowProcessModal(false); } - }, [account, amount, chainId, isApproved, approve, sendTransactionAsync, switchChainAsync]); + }, [account, amount, chainId, isApproved, approve, sendTransactionAsync, switchChainAsync, toast]); return { wrap, From 00fa1756268ddb121c3dba380ca2789c0722c065 Mon Sep 17 00:00:00 2001 From: chyyynh Date: Sat, 15 Nov 2025 01:17:53 +0800 Subject: [PATCH 2/2] fix:coderabbit suggestion & transparent bg --- src/components/providers/ThemeProvider.tsx | 10 +++--- src/hooks/useTransactionWithToast.tsx | 38 ++++++++++++---------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/components/providers/ThemeProvider.tsx b/src/components/providers/ThemeProvider.tsx index 128031d1..48c285a5 100644 --- a/src/components/providers/ThemeProvider.tsx +++ b/src/components/providers/ThemeProvider.tsx @@ -7,19 +7,21 @@ import { Toaster } from 'sonner'; function ToasterProvider() { const { theme } = useTheme(); + const isDark = theme === 'dark'; + return ( ); diff --git a/src/hooks/useTransactionWithToast.tsx b/src/hooks/useTransactionWithToast.tsx index 4f47b527..286eb2d6 100644 --- a/src/hooks/useTransactionWithToast.tsx +++ b/src/hooks/useTransactionWithToast.tsx @@ -70,24 +70,26 @@ export function useTransactionWithToast({ }, [isConfirming, pendingText, pendingDescription, toastId, onClick, hash]); useEffect(() => { - if (isConfirmed && hash) { - toast.success(`${successText} 🎉`, { - id: toastId, - description: ( -
- {successDescription &&
{successDescription}
} - -
- ), - action: { - label: 'View', - onClick, - }, - duration: 5000, - className: 'font-zen', - }); - if (onSuccess) { - onSuccess(); + if (isConfirmed) { + if (hash) { + toast.success(`${successText} 🎉`, { + id: toastId, + description: ( +
+ {successDescription &&
{successDescription}
} + +
+ ), + action: { + label: 'View', + onClick, + }, + duration: 5000, + className: 'font-zen', + }); + if (onSuccess) { + onSuccess(); + } } } if (isError || txError) {