Skip to content

Toasts - #15

Open
3scava1i3r wants to merge 5 commits into
ubiquity:developmentfrom
3scava1i3r:toasts
Open

Toasts#15
3scava1i3r wants to merge 5 commits into
ubiquity:developmentfrom
3scava1i3r:toasts

Conversation

@3scava1i3r

@3scava1i3r 3scava1i3r commented Feb 7, 2026

Copy link
Copy Markdown

PR Description

Summary

Implemented a minimal toast system for consistent error/success feedback across key flows (RPC read failures, approve/stake actions, reward claim) without introducing large UI libraries.
Fixes #8

Features

  • Variants: info, success, error with distinct styling
  • Deduplication: Similar messages within 2s window are collapsed
  • Auto-dismiss: Non-persistent toasts dismiss after 5s
  • Portal rendering: Renders at document.body level
  • Accessibility: Keyboard dismissible, ARIA live regions

Validation

  • ✅ All 17 tests pass (16 toast + 1 basic)
  • ✅ Linting passes with no errors
  • ✅ TypeScript compilation successful

Notes

  • Maintains backward compatibility with existing StatusMessageProvider
  • ~180 lines total for core toast system (meets <200 line constraint)
  • Reuses TanStack Query error patterns for RPC errors

@coderabbitai

coderabbitai Bot commented Feb 7, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request implements a lightweight toast notification system and integrates wallet connectivity features. The toast system includes a context-based provider with deduplication and auto-dismiss behavior, accompanied by CSS styling. Dashboard and pool components are updated to use wagmi hooks for wallet interactions and to dispatch toast notifications on transaction success or error. The main entry point is configured with the ToastProvider and Wagmi infrastructure. Unit tests validate the toast reducer logic and configuration.

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Toasts' is vague and generic, providing no meaningful detail about the changeset or its scope. Use a more descriptive title such as 'Implement minimal toast notification system' or 'Add toast system for error/success feedback'.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly outlines the toast system implementation, features, validation steps, and links to issue #8, directly relating to the changeset.
Linked Issues check ✅ Passed All core requirements from issue #8 are met: toast system with variants, provider/hook setup, auto-dismiss (5s), deduplication (2s window), accessibility features, and ~180 lines of code.
Out of Scope Changes check ✅ Passed All changes directly support the toast system implementation. No unrelated modifications detected in dashboard, pool display, or main entry point integrations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (3)
src/ui/toast/ToastProvider.tsx (1)

26-31: Dedup checks only the single last toast, not the full visible set.

If toasts A → B → A fire within the dedup window, the second A won't be deduplicated because lastToast is B. This may be intentional for simplicity, but worth noting.

src/components/pool-display.tsx (1)

82-99: Consider: onSuccess can run after component unmount.

waitForTransactionReceipt is async and may resolve after the component unmounts (e.g., user navigates away). Calling toast()/setSuccessMessage() on an unmounted component will either no-op (toast via context) or trigger a state update warning (setErrorMessage). The status message setState calls are the concern here.

This is pre-existing behavior, not introduced by this PR, so just flagging for awareness.

src/components/dashboard-page.tsx (1)

52-54: Dual error display: toast + inline banner.

The user sees the same error in both a persistent toast (duration: 0) and the inline #error-message-wrapper banner. This is intentional for backward compatibility, but consider whether showing both simultaneously is the desired UX long-term — it could feel redundant.

Comment thread .github/workflows/deploy.yml Outdated
Comment thread .github/workflows/deploy.yml Outdated
Comment thread .github/workflows/deploy.yml Outdated
Comment thread src/css/toast.css
Comment thread src/ui/toast/ToastContainer.tsx
Comment thread src/ui/toast/ToastProvider.tsx Outdated
Comment thread src/ui/toast/ToastProvider.tsx
Comment thread tests/toast.test.ts
Comment thread tests/toast.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
src/components/dashboard-page.tsx (2)

54-57: Errors are shown in both a toast and the inline status banner — intentional?

toast(message, "error", 0) fires a persistent toast, while setErrorMessage(message) also populates the inline error section (lines 72–79). If dual display is intentional, no action needed; otherwise pick one.


21-21: window.ethereum may not be injected yet at render time.

Some wallets inject ethereum asynchronously. This check can return false on first render, then true after. Consider using wagmi's useConnectors() to detect available connectors instead of a raw window.ethereum check.

Comment thread src/components/dashboard-page.tsx
Comment thread src/components/dashboard-page.tsx
Comment thread src/components/dashboard-page.tsx Outdated
Comment thread src/components/pool-display.tsx Outdated
Comment thread src/components/pool-display.tsx Outdated
Comment thread src/components/pool-display.tsx
Comment thread src/components/pool-display.tsx Outdated
Comment thread src/components/pool-display.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/ui/toast/ToastContainer.tsx (1)

16-23: ToastItemProps.toast duplicates the Toast interface.

You already import from ./types. Reusing Toast directly is cleaner.

Proposed fix
+import type { Toast, ToastVariant } from "./types";
-import type { ToastVariant } from "./types";

-interface ToastItemProps {
-  toast: {
-    id: string;
-    message: string;
-    variant: ToastVariant;
-  };
-  onDismiss: (id: string) => void;
-}
+interface ToastItemProps {
+  toast: Toast;
+  onDismiss: (id: string) => void;
+}
src/ui/toast/ToastProvider.tsx (2)

52-73: Duplicated dedup logic between toast() and the reducer.

Lines 54–60 replicate the same dedup check that exists in toastReducer (lines 22–27). If the dedup criteria change, both must be updated in lockstep. Additionally, state.lastToast in the callback closure can be stale relative to what the reducer sees (TOCTOU).

Consider removing the callback-side check and having the reducer return a signal (e.g., via a ref or separate state) indicating whether the toast was actually added. Alternatively, accept the duplication but add a comment linking the two.


18-27: Reducer calls Date.now() — impure.

Line 23 makes the reducer non-deterministic. This is pragmatic for dedup but complicates testing and violates the React expectation that reducers are pure functions. If you want strict purity, pass the current timestamp as part of the action payload (you already have action.payload.timestamp).

Proposed fix
     case "ADD": {
       if (state.lastToast && state.lastToast.message === action.payload.message) {
-        const timeSinceLast = Date.now() - state.lastToast.timestamp;
+        const timeSinceLast = action.payload.timestamp - state.lastToast.timestamp;
         if (timeSinceLast < TOAST_CONFIG.dedupWindow) {
           return state;
         }
       }

import { BaseError } from "viem";
import { useStatusMessageState, useStatusMessageDispatch } from "../context/status-message.tsx";
import { useToast } from "../ui/toast";
import { ConnectWalletButton } from "./connect-wallet.tsx";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

ConnectWalletButton is imported but never used.

Remove the unused import.

-import { ConnectWalletButton } from "./connect-wallet.tsx";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { ConnectWalletButton } from "./connect-wallet.tsx";
🧰 Tools
🪛 ESLint

[error] 9-9: 'ConnectWalletButton' is defined but never used.

(@typescript-eslint/no-unused-vars)

Comment on lines 15 to +17
const { isConnected } = useAppKitAccount();
const { chainId } = useAppKitNetwork();
const { address, chain, status } = useAccount();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Mixed wallet state sources: isConnected from AppKit vs address from wagmi.

Line 15 reads isConnected from useAppKitAccount() and line 17 reads address/chain/status from wagmi's useAccount(). Line 42 then checks isConnected && address — these can momentarily disagree during connection/disconnection, causing flicker or a brief broken state where one is truthy and the other isn't.

Pick one source of truth. Since the inline buttons already use wagmi's connect/disconnect, consider deriving isConnected from useAccount() as well (e.g., status === "connected").

Also applies to: 42-42

Comment on lines +2 to +10
import { useReadContract, useWriteContract, usePublicClient } from "wagmi";
import { parseUnits, BaseError } from "viem";
import { stakingContract } from "../constants/contracts";
import erc20Abi from "../abis/erc20";
import { useErc20Token } from "../hooks/erc20Token";
import { useState } from "react";
import { waitForTransactionReceipt } from "viem/actions";
import { useStatusMessageState, useStatusMessageDispatch } from "../context/status-message.tsx";
import { useToast } from "../ui/toast";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused imports.

ESLint confirms useWriteContract, parseUnits, erc20Abi, and useStatusMessageState are all unused. These appear to be leftovers from removed code.

Proposed fix
-import { useReadContract, useWriteContract, usePublicClient } from "wagmi";
-import { parseUnits, BaseError } from "viem";
+import { useReadContract, usePublicClient } from "wagmi";
+import { BaseError } from "viem";
 import { stakingContract } from "../constants/contracts";
-import erc20Abi from "../abis/erc20";
 import { useErc20Token } from "../hooks/erc20Token";
 import { useState } from "react";
 import { waitForTransactionReceipt } from "viem/actions";
-import { useStatusMessageState, useStatusMessageDispatch } from "../context/status-message.tsx";
+import { useStatusMessageDispatch } from "../context/status-message.tsx";
🧰 Tools
🪛 ESLint

[error] 2-2: 'useWriteContract' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 3-3: 'parseUnits' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 5-5: 'erc20Abi' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 9-9: 'useStatusMessageState' is defined but never used.

(@typescript-eslint/no-unused-vars)

Copy link
Copy Markdown

I think there is still a build-blocking issue in src/main.tsx.

This PR adds a second wagmi config block before the existing wagmiAdapter import, but the symbols used there are not imported in this file: Chain, isLocalNode, mainnet, anvil, http, RPC_URL, createConfig, and injected. TypeScript/Vite will fail on those names before the toast provider wiring can run.

It also exports that new config, but the render path still uses <WagmiProvider config={wagmiAdapter.wagmiConfig}>, so the added config is unused even if the imports are fixed. The smallest fix is probably to remove the extra config block from main.tsx and keep wallet/config.ts as the single wagmi config source, leaving this file to only add ToastProvider / ToastContainer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error Handling & Status Toasts – Handoff

3 participants