Skip to content

Conversation

@kumawatkaran523
Copy link
Contributor

@kumawatkaran523 kumawatkaran523 commented Aug 26, 2025

Updated the UI/UX for token selection.

Then

Screenshot From 2025-08-25 12-33-31
Screenshot From 2025-08-25 12-33-44

Now

image Screenshot From 2025-08-26 16-53-47 image

Summary by CodeRabbit

  • New Features

    • Added TokenPicker with searchable modal, avatars, and copy-to-clipboard.
    • Introduced custom token flow with on-chain verification and status badges.
    • Dynamic, chain-aware token lists replace static presets across Create Invoice and Generate Link.
    • Improved invoice views (Received/Sent) with automatic token metadata/logo resolution and fallbacks.
    • Added UI components: Dialog, Separator, Avatar, CopyButton; simplified Badge.
  • Bug Fixes

    • Enhanced error handling, loading states, and image fallbacks; added notifications for invoice actions.
  • Chores

    • Updated dependencies (Radix UI, framer-motion) and added react-window.

@coderabbitai
Copy link

coderabbitai bot commented Aug 26, 2025

Walkthrough

Dependency updates add Radix UI components and react-window. New UI primitives (Avatar, Dialog, Separator, Badge, CopyButton) are introduced. A new TokenPicker and hooks (useTokenList, useTokenSearch) replace static TOKEN_PRESETS across pages. Token verification via on-chain metadata is added. TOKEN_PRESETS file is removed. A TokenCrousel import was commented causing a runtime error.

Changes

Cohort / File(s) Summary of edits
Dependencies
frontend/package.json
Added Radix Avatar/Dialog and react-window; upgraded framer-motion; adjusted @radix-ui/react-slot and added Separator; dependency versions updated.
UI primitives
frontend/src/components/ui/avatar.jsx, frontend/src/components/ui/badge.jsx, frontend/src/components/ui/copyButton.jsx, frontend/src/components/ui/dialog.jsx, frontend/src/components/ui/separator.jsx
New Avatar, Dialog, Separator, CopyButton components; Badge simplified and variants removed; exports added/updated accordingly.
Token selection components and hooks
frontend/src/components/TokenPicker.jsx, frontend/src/hooks/useTokenList.js, frontend/src/hooks/useTokenSearch.js
Added TokenPicker with modal, search highlighting, and selection; added hooks for fetching/caching token lists and client-side search with indexing/pagination.
Pages updated for new token flow
frontend/src/page/CreateInvoice.jsx, frontend/src/page/GenerateLink.jsx, frontend/src/page/ReceivedInvoice.jsx, frontend/src/page/SentInvoice.jsx
Replaced static presets with TokenPicker/useTokenList; added custom token verification via on-chain queries; integrated token metadata resolution, fallbacks, and UI updates; adjusted URL and payment handling.
Carousel component
frontend/src/components/TokenCrousel.jsx
Commented out TOKEN_PRESETS import while still referencing it, introducing a runtime ReferenceError at initialization.
Removal of static presets
frontend/src/utils/erc20_token.js
Deleted TOKEN_PRESETS dataset and its export.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Page as Page (Create/Generate)
  participant TokenPicker
  participant useTokenList
  participant useTokenSearch

  User->>TokenPicker: Open picker
  TokenPicker->>useTokenList: useTokenList(chainId)
  useTokenList-->>TokenPicker: { tokens, loading, error }
  alt loading
    TokenPicker-->>User: Show loading
  else error
    TokenPicker-->>User: Show error
  else tokens loaded
    User->>TokenPicker: Type query
    TokenPicker->>useTokenSearch: setQuery(query)
    useTokenSearch-->>TokenPicker: filtered tokens (debounced)
    User->>TokenPicker: Select token
    TokenPicker-->>Page: onSelect(token)
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant Page as Page (Create/Generate)
  participant Verify as verifyToken()
  participant Chain as Blockchain (ERC20)

  User->>Page: Enter contract address
  Page->>Verify: verifyToken(address)
  Verify->>Chain: read symbol/name/decimals
  alt success
    Chain-->>Verify: {symbol,name,decimals}
    Verify-->>Page: verified token data
    Page-->>User: Show Verified + details
  else failure
    Chain-->>Verify: error
    Verify-->>Page: error
    Page-->>User: Show error guidance
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • adityabhattad2021

Poem

I hop through lists where tokens flow,
From presets past to live-data glow.
A dialog opens, search lights the way,
Verify a contract—hip hooray!
With badges bright and copy clicks,
I thump approval—shipping tricks! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
frontend/src/components/TokenCrousel.jsx (3)

4-9: ReferenceError at runtime: TOKEN_PRESETS is undefined.

The import was commented out, but the code still spreads TOKEN_PRESETS to build duplicatedTokens. This will crash on first render.

Apply this minimal, future-proof change to accept tokens from props (works with your new hooks), and avoid any dependency on the removed presets:

-import { useEffect, useRef } from "react";
+import { useEffect, useMemo, useRef } from "react";
 import { motion } from "framer-motion";
 import { SiEthereum } from "react-icons/si";
-// import { TOKEN_PRESETS } from "@/utils/erc20_token";
 
-const TokenCarousel = () => {
-  const carouselRef = useRef();
-  const duplicatedTokens = [...TOKEN_PRESETS, ...TOKEN_PRESETS]; // Double the tokens for seamless loop
+const TokenCarousel = ({ tokens = [] }) => {
+  const carouselRef = useRef(null);
+  const duplicatedTokens = useMemo(() => tokens.length ? [...tokens, ...tokens] : [], [tokens]);

Follow-up: Update the calling site(s) to pass the token array from your new useTokenList hook once it resolves.


10-22: Guard against null refs and empty lists to avoid exceptions and wasted RAF cycles.

If the ref is still null or the list is empty, the effect should bail. Also ensure we don’t read scrollWidth from an undefined node.

 useEffect(() => {
-  const carousel = carouselRef.current;
+  const carousel = carouselRef.current;
+  if (!carousel || duplicatedTokens.length === 0) return;
   let animationFrame;
   let speed = 1; // Pixels per frame
   let position = 0;
 
   const animate = () => {
     position -= speed;
-    if (position <= -carousel.scrollWidth / 2) {
+    const half = carousel.scrollWidth / 2;
+    if (!Number.isFinite(half) || half <= 0) {
+      return; // nothing to animate yet
+    }
+    if (position <= -half) {
       position = 0;
     }
     carousel.style.transform = `translateX(${position}px)`;
     animationFrame = requestAnimationFrame(animate);
   };
 
-  animate();
+  animationFrame = requestAnimationFrame(animate);

84-85: Rename TokenCrousel.jsx to TokenCarousel.jsx and update its import

Verification shows the component file is misspelled and there’s a single import site that must be updated:

  • File to rename:
    frontend/src/components/TokenCrousel.jsxfrontend/src/components/TokenCarousel.jsx
  • Import to update (Landing.jsx, line 14):
    • Change
    import TokenCarousel from "@/components/TokenCrousel";
    to
    import TokenCarousel from "@/components/TokenCarousel";

I can provide a simple jscodeshift or sed codemod to automate renaming the file and fixing all import paths—just let me know.

frontend/src/page/SentInvoice.jsx (2)

155-161: Fix BigInt/Number mismatch when checking network chainId

Ethers v6 returns chainId as a BigInt. Comparing to a Number throws or miscompares. Cast before comparing.

Apply this diff:

-        if (network.chainId != 11155111) {
+        if (Number(network.chainId) !== 11155111) {
           setError(
             `You're connected to ${network.name}. Please switch to Sepolia network to view your invoices.`
           );
           setLoading(false);
           return;
         }

422-426: Guard address formatter against undefined/null

substring on undefined will throw when client address is missing. Add a safe fallback.

Apply this diff:

-  const formatAddress = (address) => {
-    return `${address.substring(0, 10)}...${address.substring(
-      address.length - 10
-    )}`;
-  };
+  const formatAddress = (addr) => {
+    if (!addr || typeof addr !== "string") return "—";
+    const start = addr.slice(0, 10);
+    const end = addr.slice(-10);
+    return `${start}...${end}`;
+  };
frontend/src/page/CreateInvoice.jsx (4)

20-27: Fix incorrect Badge import (will crash at runtime).

Badge is imported from lucide-react, but it's a UI primitive defined in components/ui/badge. This will throw a module export error at build/runtime.

Apply this diff:

-import {
-  Badge,
-  CalendarIcon,
-  CheckCircle2,
-  Coins,
-  Loader2,
-  PlusIcon,
-  XCircle,
-} from "lucide-react";
+import {
+  CalendarIcon,
+  CheckCircle2,
+  Coins,
+  Loader2,
+  PlusIcon,
+  XCircle,
+} from "lucide-react";
@@
-import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker";
+import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker";
+import { Badge } from "@/components/ui/badge";

Also applies to: 46-48


97-116: Remove legacy TOKEN_PRESETS usage; preselect using dynamic token list.

TOKEN_PRESETS no longer exists. This reference will throw ReferenceError when a tokenAddress is present in the URL and customToken !== "true".

Apply this diff to resolve via the new token list:

-      } else {
-        const preselectedToken = TOKEN_PRESETS.find(
-          (token) =>
-            token.address.toLowerCase() === urlTokenAddress.toLowerCase()
-        );
-        if (preselectedToken) {
-          setSelectedToken(preselectedToken);
-          setUseCustomToken(false);
-        } else {
-          setUseCustomToken(true);
-          setCustomTokenAddress(urlTokenAddress);
-          verifyToken(urlTokenAddress);
-        }
-      }
+      } else {
+        // Try to resolve from dynamic token list
+        const found = (tokens || []).find((t) => {
+          const ca = (t.contract_address || t.address || "").toLowerCase();
+          return ca === urlTokenAddress.toLowerCase();
+        });
+        if (found) {
+          setSelectedToken({
+            address: found.contract_address || found.address,
+            symbol: found.symbol,
+            name: found.name,
+            logo: found.image,
+            decimals: Number(found.decimals ?? 18),
+          });
+          setUseCustomToken(false);
+        } else {
+          setUseCustomToken(true);
+          setCustomTokenAddress(urlTokenAddress);
+          verifyToken(urlTokenAddress);
+        }
+      }

Add these supporting lines (outside the shown hunk):

// imports
import { useTokenList } from "../hooks/useTokenList";

// inside component
const { tokens } = useTokenList(account?.chainId || 1);

233-244: Validate payment token before building invoice payload.

If no token is selected/verified, accessing paymentToken fields will throw. Also guard custom flow until verification succeeds.

   const paymentToken = useCustomToken ? verifiedToken : selectedToken;
 
-  const invoicePayload = {
+  if (!paymentToken) {
+    alert("Please select a token or verify your custom token before creating the invoice.");
+    setLoading(false);
+    return;
+  }
+  if (useCustomToken && tokenVerificationState !== "success") {
+    alert("Please wait for token verification to complete.");
+    setLoading(false);
+    return;
+  }
+
+  const invoicePayload = {

339-351: Enforce dynamic token decimals across all payment flows

We’ve identified multiple instances where 18 is still hard-coded for token decimals. To correctly support tokens with varying decimals, replace every 18 with the selected token’s paymentToken.decimals. Specifically:

  • frontend/src/page/ReceivedInvoice.jsx (≈ line 402)
  • const amountDueInWei = ethers.parseUnits(String(amountDue), 18);
  • const amountDueInWei = ethers.parseUnits(String(amountDue), paymentToken.decimals);
    
    
  • frontend/src/page/CreateInvoice.jsx
    Initial total calculation (≈ lines 121–125)
  • const qty = parseUnits(item.qty || "0", 18);
  • const unitPrice = parseUnits(item.unitPrice|| "0", 18);
  • const discount = parseUnits(item.discount || "0", 18);
  • const tax = parseUnits(item.tax || "0", 18);
  • const lineTotal = (qty * unitPrice) / parseUnits("1", 18);
  • const qty = parseUnits(item.qty || "0", paymentToken.decimals);
  • const unitPrice = parseUnits(item.unitPrice|| "0", paymentToken.decimals);
  • const discount = parseUnits(item.discount || "0", paymentToken.decimals);
  • const tax = parseUnits(item.tax || "0", paymentToken.decimals);
  • const lineTotal = (qty * unitPrice) / parseUnits("1", paymentToken.decimals);
    
    • **On-change recalculation** (≈ lines 165–170)  
    ```diff
    
  • const qty = parseUnits(updatedItem.qty || "0", 18);
  • const unitPrice = parseUnits(updatedItem.unitPrice|| "0", 18);
  • const discount = parseUnits(updatedItem.discount || "0", 18);
  • const tax = parseUnits(updatedItem.tax || "0", 18);
  • const lineTotal = (qty * unitPrice) / parseUnits("1", 18);
  • const qty = parseUnits(updatedItem.qty || "0", paymentToken.decimals);
  • const unitPrice = parseUnits(updatedItem.unitPrice|| "0", paymentToken.decimals);
  • const discount = parseUnits(updatedItem.discount || "0", paymentToken.decimals);
  • const tax = parseUnits(updatedItem.tax || "0", paymentToken.decimals);
  • const lineTotal = (qty * unitPrice) / parseUnits("1", paymentToken.decimals);
    
    
  • Token metadata definitions
    frontend/src/utils/CitreaTestnet.jsx (line 9) &
    frontend/src/page/GenerateLink.jsx (line 226)
    // Ensure these “decimals: 18” entries match each token’s actual decimals,
    // or load decimals from a shared config/token list instead of hard-coding.

By refactoring these spots to use paymentToken.decimals, the app will correctly handle tokens beyond the 18-decimal default.

🧹 Nitpick comments (26)
frontend/src/components/TokenCrousel.jsx (3)

34-41: Cleanup safety: remove listeners only if node exists.

Tiny guard to avoid errors during unmount in edge cases.

   return () => {
-    cancelAnimationFrame(animationFrame);
-    carousel.removeEventListener("mouseenter", pause);
-    carousel.removeEventListener("mouseleave", resume);
+    if (animationFrame) cancelAnimationFrame(animationFrame);
+    if (carousel) {
+      carousel.removeEventListener("mouseenter", pause);
+      carousel.removeEventListener("mouseleave", resume);
+    }
   };

48-51: Key stability: some tokens may lack an address; provide a robust key.

Use a fallback based on chainId/symbol to avoid duplicate keys and React churn.

-          {duplicatedTokens.map((token, index) => (
+          {duplicatedTokens.map((token, index) => {
+            const key = token.address ?? `${token.chainId ?? 'na'}-${token.symbol ?? 'token'}-${index}`;
+            return (
             <motion.div
-              key={`${token.address}-${index}`}
+              key={key}

…and close the returned JSX accordingly.


64-69: Zero-address heuristic for native token is brittle.

Relying on "0x000...000" to detect native currency will mislabel tokens across chains that encode natives differently. Prefer a semantic flag (e.g., token.isNative) from your token hook, or a central helper.

-  {token.address === "0x0000000000000000000000000000000000000000" && (
+  {(token.isNative === true) && (
     <div className="absolute -bottom-1 -right-1 bg-green-500 rounded-full p-0.5">
       <SiEthereum className="text-white text-xs" />
     </div>
   )}

If your data model doesn’t include isNative, add a small mapper in the hook layer to set it consistently per chain.

frontend/src/components/ui/copyButton.jsx (1)

1-39: Solid UX; add fallback + a11y polish + guard for empty text.

Current behavior is fine. A few low-effort improvements:

  • Guard when textToCopy is empty.
  • Graceful fallback if navigator.clipboard is unavailable (HTTP, older browsers).
  • Slightly longer success state improves perceived feedback.
  • Expose aria-label for screen readers; avoid hard-coded “Copy address”.
-import { Check, Copy } from "lucide-react";
-import { useState } from "react";
+import { Check, Copy } from "lucide-react";
+import { useState } from "react";

-const CopyButton = ({ textToCopy, className = "" }) => {
+const CopyButton = ({ textToCopy, className = "", ariaLabel = "Copy to clipboard" }) => {
   const [copied, setCopied] = useState(false);

   const handleCopy = async (e) => {
     e.stopPropagation();
     try {
-      await navigator.clipboard.writeText(textToCopy);
+      if (!textToCopy) return;
+      if (navigator?.clipboard?.writeText) {
+        await navigator.clipboard.writeText(textToCopy);
+      } else {
+        // Fallback: use a temporary textarea
+        const ta = document.createElement("textarea");
+        ta.value = textToCopy;
+        ta.setAttribute("readonly", "");
+        ta.style.position = "absolute";
+        ta.style.left = "-9999px";
+        document.body.appendChild(ta);
+        ta.select();
+        document.execCommand("copy");
+        document.body.removeChild(ta);
+      }
       setCopied(true);
-      setTimeout(() => setCopied(false), 500);
+      setTimeout(() => setCopied(false), 1500);
     } catch (err) {
       console.error("Failed to copy:", err);
     }
   };

   return (
     <button
       type="button"
       onClick={handleCopy}
-      className={`inline-flex items-center gap-1 px-2 py-1 text-xs rounded hover:bg-gray-100 transition-colors ${className}`}
-      title="Copy address"
+      className={`inline-flex items-center gap-1 px-2 py-1 text-xs rounded hover:bg-gray-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${className}`}
+      title={ariaLabel}
+      aria-label={ariaLabel}
+      disabled={!textToCopy}
     >
       {copied ? (
         <>
           <Check className="w-3 h-3 text-green-600" />
           <span className="text-green-600">Copied!</span>
         </>
       ) : (
         <>
           <Copy className="w-3 h-3 text-gray-500" />
           <span className="text-gray-500">Copy</span>
         </>
       )}
     </button>
   );
};

If you prefer toast-based feedback across the app, I can wire it to react-hot-toast instead of inline state.

frontend/src/components/ui/avatar.jsx (1)

1-18: Broken fallback on image error; consider Radix Avatar for a11y and consistency.

As written, if the img fails to load, children won’t render because the presence of src keeps the path active. Also, we added @radix-ui/react-avatar—using it gives us semantics and fallbacks for free.

Two options:

  • Option A (minimal fix): Track error state and show children when load fails.
  • Option B (preferred): Wrap Radix Avatar primitives for consistency with other UI components.

Minimal fix:

-const Avatar = ({ src, alt, className = "", children, onError }) => (
-  <div
-    className={`inline-flex items-center justify-center rounded-full bg-gray-100 overflow-hidden ${className}`}
-  >
-    {src ? (
-      <img
-        src={src}
-        alt={alt}
-        className="w-full h-full object-cover rounded-full"
-        onError={onError}
-      />
-    ) : (
-      <div className="flex items-center justify-center w-full h-full text-xs font-medium text-gray-600">
-        {children}
-      </div>
-    )}
-  </div>
-);
+import * as React from "react";
+
+const Avatar = ({ src, alt = "", className = "", children }) => {
+  const [errored, setErrored] = React.useState(false);
+  const showImage = !!src && !errored;
+  return (
+    <div className={`inline-flex items-center justify-center rounded-full bg-gray-100 overflow-hidden ${className}`}>
+      {showImage ? (
+        <img
+          src={src}
+          alt={alt}
+          loading="lazy"
+          decoding="async"
+          className="w-full h-full object-cover rounded-full"
+          onError={() => setErrored(true)}
+        />
+      ) : (
+        <div className="flex items-center justify-center w-full h-full text-xs font-medium text-gray-600">
+          {children}
+        </div>
+      )}
+    </div>
+  );
+};

If you want Option B, I can drop in a Radix-based wrapper to match the Separator pattern.

frontend/src/components/ui/separator.jsx (1)

21-23: Minor a11y/naming polish (optional).

  • Consider exposing a role when decorative={false} so screen readers treat it as a separator.
  • Exporting a default alongside named export can reduce import verbosity in consumers, but up to style.
-<no code change required>
+# If you flip decorative to false at call sites, Radix will set role="separator".
+# Document that in a JSDoc comment above the component for discoverability.
frontend/src/hooks/useTokenSearch.js (3)

21-37: Prefix indexing builds O(N·L) entries; cap prefix length to control memory

Indexing every prefix of name/symbol for long strings grows maps quickly. Cap to a reasonable length (e.g., 10) to keep memory predictable.

Apply this diff:

-    for (let i = 1; i <= symbol.length; i++) {
+    for (let i = 1; i <= Math.min(symbol.length, 10); i++) {
       const prefix = symbol.slice(0, i);
       ...
     }
...
-    for (let i = 1; i <= name.length; i++) {
+    for (let i = 1; i <= Math.min(name.length, 10); i++) {
       const prefix = name.slice(0, i);
       ...
     }

90-99: Address search is O(N) per keystroke; optionally use prefix-only index for 0x… inputs

Current substring search over all addresses may stutter on large lists. If you restrict to prefix search (most natural for addresses), you can index by prefixes like you do for symbol/name.

If you choose this route, add an address-prefix index and replace the includes() loop with a set lookup.


45-50: Either wire up loading state for the debounce, or remove it from the API

loading/error are never updated. This can mislead consumers relying on them.

Option A: remove them from the hook’s return. Option B: toggle loading around the debounce:

  const [debouncedQuery, setDebouncedQuery] = useState("");
  useEffect(() => {
-    const timer = setTimeout(() => {
+    setLoading(true);
+    const timer = setTimeout(() => {
       setDebouncedQuery(query);
       setPage(1); // Reset pagination when query changes
+      setLoading(false);
     }, 250);
     return () => clearTimeout(timer);
   }, [query]);

Also applies to: 51-59

frontend/src/components/ui/dialog.jsx (1)

31-35: Optional: consider mobile overflow handling

On small screens with tall content, adding max-h-[90vh] overflow-y-auto to DialogContent improves UX.

Apply this diff:

-      className={cn(
-        "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
-        className
-      )}
+      className={cn(
+        "fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 sm:rounded-lg max-h-[90vh] overflow-y-auto data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
+        className
+      )}
frontend/src/page/SentInvoice.jsx (2)

163-167: Replace alert with user-friendly error state and toast

Avoid alert() in-app. Use the existing error/toast patterns and stop the loading spinner.

Apply this diff:

-        if (!litNodeClient) {
-          alert("Lit client not initialized");
-          return;
-        }
+        if (!litNodeClient) {
+          setError("Encryption client not initialized. Please refresh and try again.");
+          toast.error("Unable to initialize secure session");
+          setLoading(false);
+          return;
+        }

151-153: Verify provider construction with wagmi’s WalletClient

Passing walletClient directly to BrowserProvider may not be EIP-1193 compatible in all wagmi versions. Prefer window.ethereum or walletClient.transport where available.

If needed, switch to:

-        const provider = new BrowserProvider(walletClient);
+        const provider = new BrowserProvider(
+          walletClient?.transport?.value || window.ethereum
+        );

Do the same in handleCancelInvoice. Please confirm your wagmi/ethers versions and adjust accordingly.

Also applies to: 381-387

frontend/src/hooks/useTokenList.js (2)

60-62: Remove debug logging

Leftover console.log(data) adds noise.

Apply this diff:

-        const data = await response.json();
-        console.log(data);
+        const data = await response.json();

48-55: Consider making the token-list URL configurable

Hardcoding a GitHub URL is brittle. Prefer an env/config value so you can swap hosts without redeploying.

Example:

-        const dataUrl = `https://raw.githubusercontent.com/StabilityNexus/TokenList/main/${ChainIdToName[chainId]}-tokens.json`;
+        const base = import.meta.env.VITE_TOKENLIST_BASE_URL
+          ?? "https://raw.githubusercontent.com/StabilityNexus/TokenList/main";
+        const dataUrl = `${base}/${ChainIdToName[chainId]}-tokens.json`;
frontend/src/page/CreateInvoice.jsx (4)

119-132: Tax labeled as percentage is added as absolute amount.

Headers/UI say “TAX(%)”, but calculations add tax as a flat amount. This will miscompute line totals and the invoice.

If tax is a percentage, compute it relative to (qty*unitPrice - discount). Example fix within handleItemData:

-            const tax = parseUnits(updatedItem.tax || "0", 18);
-            const lineTotal = (qty * unitPrice) / parseUnits("1", 18);
-            const finalAmount = lineTotal - discount + tax;
+            const taxPct = parseUnits(updatedItem.tax || "0", 18); // e.g., "10" => 10%
+            const one = parseUnits("1", 18);
+            const hundred = parseUnits("100", 18);
+            const lineTotal = (qty * unitPrice) / one;
+            const base = lineTotal - discount;
+            const tax = (base * taxPct) / hundred;
+            const finalAmount = base + tax;

Adjust the read-only UI calculation similarly to avoid display drift.

Also applies to: 154-174, 892-899


196-216: Use walletClient as primary provider for verification; window.ethereum as fallback.

Keeps behavior consistent with the rest of the page and allows verification when injected provider is unavailable.

-  try {
-      if (typeof window !== "undefined" && window.ethereum) {
-        const provider = new BrowserProvider(window.ethereum);
+  try {
+      const provider = walletClient
+        ? new BrowserProvider(walletClient)
+        : (typeof window !== "undefined" && window.ethereum)
+        ? new BrowserProvider(window.ethereum)
+        : null;
+      if (provider) {
         const contract = new ethers.Contract(address, ERC20_ABI, provider);
         const [symbol, name, decimals] = await Promise.all([
           contract.symbol().catch(() => "UNKNOWN"),
           contract.name().catch(() => "Unknown Token"),
           contract.decimals().catch(() => 18),
         ]);
         setVerifiedToken({ address, symbol, name, decimals });
         setTokenVerificationState("success");
       } else {
         console.error("No Ethereum provider found");
         setTokenVerificationState("error");
       }

50-53: Avoid double useAccount calls; destructure once.

Minor cleanup: get address/isConnected/chainId from a single useAccount to prevent mismatched states across hooks.

-  const { data: walletClient } = useWalletClient();
-  const { isConnected } = useAccount();
-  const account = useAccount();
+  const { data: walletClient } = useWalletClient();
+  const account = useAccount(); // if you need the full object only
+  // or better:
+  // const { address: accountAddress, isConnected, chainId } = useAccount();

70-73: Remove dead constant.

TESTNET_TOKEN is unused.

-  const TESTNET_TOKEN = ["0xB5E9C6e57C9d312937A059089B547d0036c155C7"]; //sepolia based chainvoice test token (CIN)
frontend/src/components/TokenPicker.jsx (2)

1-11: Remove unused icons.

ToggleLeft and ToggleRight are imported but never used.

-import {
-  Search,
-  X,
-  AlertCircle,
-  Loader2,
-  ChevronDown,
-  Coins,
-  ToggleLeft,
-  ToggleRight,
-} from "lucide-react";
+import { Search, X, AlertCircle, Loader2, ChevronDown, Coins } from "lucide-react";

90-111: Fix regex highlighting logic to avoid stateful .test() bug.

Using a global regex with .test() within map is stateful and can miss matches. Since split() uses a capturing group, matched parts are at odd indices.

 function HighlightMatch({ text, query }) {
   if (!query.trim()) return <>{text}</>;
 
-  const regex = new RegExp(
-    `(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&")})`,
-    "gi"
-  );
-  const parts = text.split(regex);
+  const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&")})`, "gi");
+  const parts = text.split(regex);
 
   return (
     <>
-      {parts.map((part, index) =>
-        regex.test(part) ? (
+      {parts.map((part, index) =>
+        index % 2 === 1 ? (
           <span
             key={index}
             className="bg-blue-100 text-blue-600 rounded px-0.5 font-medium"
           >
             {part}
           </span>
         ) : (
           part
         )
       )}
     </>
   );
 }
frontend/src/page/GenerateLink.jsx (4)

17-27: Clean up unused imports.

cn, CopyButton, Badge, and PlusIcon are imported but not used.

-import { cn } from "@/lib/utils";
@@
-import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker";
-import { CopyButton } from "@/components/ui/copyButton";
-import { Badge } from "@/components/ui/badge";
+import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker";
@@
-import { Copy, Link, Check, Wallet, PlusIcon, Loader2, CheckCircle2, XCircle, Coins, } from "lucide-react";
+import { Copy, Link, Check, Wallet, Loader2, CheckCircle2, XCircle, Coins } from "lucide-react";

62-63: Remove unused constant.

TESTNET_TOKEN is not referenced.

-  const TESTNET_TOKEN = ["0xB5E9C6e57C9d312937A059089B547d0036c155C7"];

218-233: Avoid hardcoding decimals=18 for selected tokens (future proofing).

While decimals aren’t used in link generation, keeping the accurate value prevents propagation of incorrect metadata if this object is reused.

-                            setSelectedToken({
-                              address: token.contract_address,
-                              symbol: token.symbol,
-                              name: token.name,
-                              logo: token.image,
-                              decimals: 18,
-                            });
+                            setSelectedToken({
+                              address: token.contract_address,
+                              symbol: token.symbol,
+                              name: token.name,
+                              logo: token.image,
+                              decimals: Number(token.decimals ?? 18),
+                            });

Also consider extending useTokenList to include decimals if the upstream JSON has it (see note below).


104-119: Permit verification without walletClient by falling back to window.ethereum.

This keeps the “auto-verify” promise even if wagmi isn’t connected yet.

-  if (!walletClient) return;
-
   setTokenVerificationState("verifying");
 
   try {
-    const provider = new BrowserProvider(walletClient);
+    const provider = walletClient
+      ? new BrowserProvider(walletClient)
+      : (typeof window !== "undefined" && window.ethereum)
+      ? new BrowserProvider(window.ethereum)
+      : null;
+    if (!provider) {
+      setTokenVerificationState("error");
+      return;
+    }
frontend/src/page/ReceivedInvoice.jsx (2)

652-663: Use e.currentTarget for image fallback.

Minor: prefer currentTarget over target to avoid surprises with event bubbling.

- onError={(e) => {
-   e.target.src = "/tokenImages/generic.png";
- }}
+ onError={(e) => {
+   e.currentTarget.src = "/tokenImages/generic.png";
+ }}

Also applies to: 951-955


43-44: Consider enriching token list with decimals to reduce chain calls.

getTokenDecimals relies on tokenInfo.decimals, but useTokenList currently drops decimals during transform. If your TokenList JSON includes decimals, carry it through to avoid extra RPC calls later.

Outside this file, adjust useTokenList transformation:

// in useTokenList.js transform
const transformedData = data.map((token) => ({
  contract_address: token.contract_address || token.address,
  symbol: token.symbol,
  name: token.name,
  image: token.image || token.logo || "/tokenImages/generic.png",
  decimals: token.decimals, // preserve if present
}));

Also applies to: 70-72, 340-342

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 23b6a31 and 10ef878.

📒 Files selected for processing (15)
  • frontend/package.json (3 hunks)
  • frontend/src/components/TokenCrousel.jsx (1 hunks)
  • frontend/src/components/TokenPicker.jsx (1 hunks)
  • frontend/src/components/ui/avatar.jsx (1 hunks)
  • frontend/src/components/ui/badge.jsx (1 hunks)
  • frontend/src/components/ui/copyButton.jsx (1 hunks)
  • frontend/src/components/ui/dialog.jsx (1 hunks)
  • frontend/src/components/ui/separator.jsx (1 hunks)
  • frontend/src/hooks/useTokenList.js (1 hunks)
  • frontend/src/hooks/useTokenSearch.js (1 hunks)
  • frontend/src/page/CreateInvoice.jsx (7 hunks)
  • frontend/src/page/GenerateLink.jsx (8 hunks)
  • frontend/src/page/ReceivedInvoice.jsx (14 hunks)
  • frontend/src/page/SentInvoice.jsx (12 hunks)
  • frontend/src/utils/erc20_token.js (0 hunks)
💤 Files with no reviewable changes (1)
  • frontend/src/utils/erc20_token.js
🧰 Additional context used
🧬 Code graph analysis (9)
frontend/src/components/TokenPicker.jsx (8)
frontend/src/lib/utils.js (1)
  • cn (4-6)
frontend/src/hooks/useTokenSearch.js (3)
  • query (48-48)
  • filteredTokens (64-101)
  • useTokenSearch (45-118)
frontend/src/components/ui/avatar.jsx (1)
  • Avatar (1-18)
frontend/src/components/ui/badge.jsx (1)
  • Badge (1-7)
frontend/src/components/ui/copyButton.jsx (1)
  • CopyButton (5-39)
frontend/src/hooks/useTokenList.js (2)
  • useTokenList (18-86)
  • tokens (19-19)
frontend/src/components/ui/button.jsx (1)
  • Button (37-45)
frontend/src/components/ui/input.jsx (1)
  • Input (5-16)
frontend/src/page/SentInvoice.jsx (4)
frontend/src/page/GenerateLink.jsx (3)
  • useAccount (30-30)
  • useTokenList (38-38)
  • walletClient (31-31)
frontend/src/page/ReceivedInvoice.jsx (6)
  • useAccount (59-59)
  • useTokenList (71-71)
  • getTokenInfo (83-91)
  • getTokenLogo (94-102)
  • getTokenDecimals (105-108)
  • walletClient (58-58)
frontend/src/hooks/useTokenList.js (1)
  • tokens (19-19)
frontend/src/contractsABI/ERC20_ABI.js (2)
  • ERC20_ABI (1-10)
  • ERC20_ABI (1-10)
frontend/src/components/ui/dialog.jsx (1)
frontend/src/lib/utils.js (1)
  • cn (4-6)
frontend/src/hooks/useTokenSearch.js (2)
frontend/src/hooks/useTokenList.js (1)
  • tokens (19-19)
frontend/src/components/TokenPicker.jsx (1)
  • filteredTokens (193-193)
frontend/src/hooks/useTokenList.js (2)
frontend/src/page/GenerateLink.jsx (1)
  • useTokenList (38-38)
frontend/src/page/ReceivedInvoice.jsx (1)
  • useTokenList (71-71)
frontend/src/components/ui/separator.jsx (1)
frontend/src/lib/utils.js (1)
  • cn (4-6)
frontend/src/page/GenerateLink.jsx (4)
frontend/src/components/ui/copyButton.jsx (2)
  • copied (6-6)
  • CopyButton (5-39)
frontend/src/hooks/useTokenList.js (3)
  • useTokenList (18-86)
  • loading (20-20)
  • tokens (19-19)
frontend/src/components/TokenPicker.jsx (3)
  • ToggleSwitch (50-84)
  • ToggleSwitch (50-84)
  • TokenPicker (175-382)
frontend/src/components/ui/badge.jsx (1)
  • Badge (1-7)
frontend/src/page/CreateInvoice.jsx (4)
frontend/src/page/GenerateLink.jsx (2)
  • loading (46-46)
  • verifyToken (104-124)
frontend/src/components/TokenPicker.jsx (3)
  • ToggleSwitch (50-84)
  • ToggleSwitch (50-84)
  • TokenPicker (175-382)
frontend/src/components/ui/badge.jsx (1)
  • Badge (1-7)
frontend/src/components/ui/copyButton.jsx (1)
  • CopyButton (5-39)
frontend/src/page/ReceivedInvoice.jsx (3)
frontend/src/page/SentInvoice.jsx (5)
  • useTokenList (79-79)
  • getTokenInfo (82-90)
  • getTokenLogo (93-101)
  • getTokenDecimals (104-107)
  • error (69-69)
frontend/src/hooks/useTokenList.js (3)
  • useTokenList (18-86)
  • tokens (19-19)
  • error (21-21)
frontend/src/contractsABI/ERC20_ABI.js (2)
  • ERC20_ABI (1-10)
  • ERC20_ABI (1-10)
🔇 Additional comments (14)
frontend/package.json (1)

21-22: All specified versions validated & peer dependencies are compatible

  • Confirmed that the following packages exist on npm and match the versions in frontend/package.json:
    • framer-motion@^12.23.12
    • @radix-ui/react-avatar@^1.1.10
    • @radix-ui/react-dialog@^1.1.15
    • @radix-ui/react-separator@^1.1.7
    • @radix-ui/react-slot@^1.2.3
    • react-window@^1.8.11
  • Verified peerDependencies:
    • wagmi requires react >=18 (covers 18.3.x), viem@2.x, @tanstack/react-query@>=5.0.0
    • viem only requires TypeScript@>=5.0.4
    • @radix-ui/react-dialog supports react ^16.8 || ^17 || ^18 || ^19
    • framer-motion supports react ^18 || ^19
    • ethers declares no peerDependencies
  • Project’s React (18.3.x) and Vite 6 usage remain fully compatible—none of these libraries impose Vite peer constraints.

Optional recommendations (non–blocking):

  • Add an “engines” field, e.g.
    "engines": { "node": ">=18.17" }
  • Add a “packageManager” lock, e.g.
    "packageManager": "pnpm@LATEST" | "npm@LATEST"

Applies to lines 21–22, 26–27, 37, 50 in frontend/package.json.

frontend/src/components/ui/copyButton.jsx (1)

41-41: Named export is consistent.

Keeping a named export here is fine and aligns with other UI primitives.

frontend/src/components/ui/avatar.jsx (1)

19-19: Export style is fine.

Named export matches how you’re composing other primitives.

frontend/src/components/ui/separator.jsx (1)

6-20: LGTM; idiomatic Radix wrapper with good defaults.

  • Correctly forwards ref and props.
  • Orientation-based sizing is clean; cn usage aligns with project utils.
  • Decorative defaults are sensible.
frontend/src/components/ui/badge.jsx (2)

1-7: LGTM — simple, consistent primitive

Compact, accessible, and Tailwind-friendly. No functional concerns.


1-9: Badge export verification complete – no breaking references found

  • No occurrences of badgeVariants detected across .js, .jsx, .ts, or .tsx files.
  • No default-import usages of Badge found in the codebase.
  • The component is safely consumed via its named export; adding
    +export default Badge
    remains an optional convenience rather than a necessity.
frontend/src/components/ui/dialog.jsx (1)

15-25: LGTM — well-structured Radix wrappers with sensible defaults

Forwarded refs, class merging, overlay/content layering, and accessible close control are all set up correctly.

Also applies to: 26-45, 67-81

frontend/src/page/CreateInvoice.jsx (1)

1011-1016: Display fallback symbol is OK.

Nice touch using verifiedToken?.symbol / selectedToken?.symbol with a neutral "TOKEN" fallback to keep totals readable before selection.

frontend/src/components/TokenPicker.jsx (2)

136-146: Nice avatar fallbacks and truncation.

Good use of a generic token image fallback and symbol initials; the truncation keeps rows tight.

Also applies to: 247-255


340-351: Selection predicate covers both shapes.

Accounting for selected.contract_address vs selected.address is correct given caller variability.

frontend/src/page/GenerateLink.jsx (2)

186-211: Great UX for toggleable token selection vs custom address.

The two-mode flow, verification states, and contextual notes are solid and consistent with CreateInvoice.

Also applies to: 356-401


77-84: Link param composition looks correct.

tokenAddress resolves to either address or contract_address; chain param fallback to "1". Good.

frontend/src/page/ReceivedInvoice.jsx (2)

272-321: Good fallback strategy for unknown tokens.

Attempting on-chain symbol/name/decimals when the token isn’t in the list keeps invoices readable. The generic logo fallback is sensible.


343-412: ERC-20 vs native payment branches make sense.

  • For ERC-20: ensure allowance ≥ amountDue, approve if needed, pay with ETH fee only.
  • For native: include fee + amount in value.

Nice UX feedback via toasts/alerts.

Also applies to: 1056-1074

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.

2 participants