Skip to content

[Dashboard] add wallet user search #7182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

joaquim-verges
Copy link
Member

@joaquim-verges joaquim-verges commented May 28, 2025

Fixes TOOL-4581

Summary

  • add a SearchInput component for the embedded wallet users page
  • filter wallet users table by id, address or linked account details

Testing

  • npx biome check apps/dashboard/src/components/embedded-wallets/Users/SearchInput.tsx apps/dashboard/src/components/embedded-wallets/Users/index.tsx
  • pnpm lint (fails: EHOSTUNREACH)
  • pnpm test (fails: spawn anvil ENOENT)

PR-Codex overview

This PR introduces a SearchInput component to enhance user experience by allowing users to filter wallet users based on search criteria. It integrates the search functionality into the existing InAppWalletUsersPageContent component.

Detailed summary

  • Added a new SearchInput component in SearchInput.tsx.
  • Integrated SearchInput into InAppWalletUsersPageContent in index.tsx.
  • Introduced searchValue state to manage input value.
  • Implemented filtering logic for wallets based on searchValue.
  • Updated the displayed wallet data to show filtered results instead of all wallets.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features
    • Added a search bar to the wallet users page, allowing users to filter wallet users by ID, wallet address, or linked account details.
  • Style
    • Introduced a visually styled input field with a search icon for improved user experience.

@joaquim-verges joaquim-verges requested review from a team as code owners May 28, 2025 07:48
Copy link

changeset-bot bot commented May 28, 2025

⚠️ No Changeset found

Latest commit: f55c007

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel vercel bot temporarily deployed to Preview – wallet-ui May 28, 2025 07:48 Inactive
@vercel vercel bot temporarily deployed to Preview – login May 28, 2025 07:48 Inactive
Copy link

vercel bot commented May 28, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
thirdweb-www ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 28, 2025 7:56am
4 Skipped Deployments
Name Status Preview Comments Updated (UTC)
docs-v2 ⬜️ Skipped (Inspect) May 28, 2025 7:56am
login ⬜️ Skipped (Inspect) May 28, 2025 7:56am
thirdweb_playground ⬜️ Skipped (Inspect) May 28, 2025 7:56am
wallet-ui ⬜️ Skipped (Inspect) May 28, 2025 7:56am

@vercel vercel bot temporarily deployed to Preview – docs-v2 May 28, 2025 07:48 Inactive
Copy link
Contributor

graphite-app bot commented May 28, 2025

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

@vercel vercel bot temporarily deployed to Preview – thirdweb_playground May 28, 2025 07:48 Inactive
Copy link
Contributor

coderabbitai bot commented May 28, 2025

Walkthrough

A search functionality was added to the in-app wallet users page. This includes a new SearchInput component for user input, state management for the search term, and logic to filter the displayed wallet users based on the search query. The user interface and data flow were updated to support real-time filtering.

Changes

File(s) Change Summary
apps/dashboard/src/components/embedded-wallets/Users/SearchInput.tsx Added new exported SearchInput React component for styled search input with icon and change handler.
apps/dashboard/src/components/embedded-wallets/Users/index.tsx Integrated SearchInput, added search state, implemented filtering logic, and updated table data source.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SearchInput
    participant InAppWalletUsersPageContent
    participant TWTable

    User->>SearchInput: Types in search query
    SearchInput->>InAppWalletUsersPageContent: onValueChange(searchValue)
    InAppWalletUsersPageContent->>InAppWalletUsersPageContent: Update searchValue state
    InAppWalletUsersPageContent->>InAppWalletUsersPageContent: Filter wallets (filteredWallets)
    InAppWalletUsersPageContent->>TWTable: Pass filteredWallets as data prop
    TWTable-->>User: Display filtered wallet users
Loading
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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.

@github-actions github-actions bot added the Dashboard Involves changes to the Dashboard. label May 28, 2025
Copy link

linear bot commented May 28, 2025

Copy link

codecov bot commented May 28, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 55.66%. Comparing base (e14a134) to head (f55c007).
Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7182   +/-   ##
=======================================
  Coverage   55.66%   55.66%           
=======================================
  Files         904      904           
  Lines       58391    58391           
  Branches     4114     4114           
=======================================
  Hits        32504    32504           
  Misses      25781    25781           
  Partials      106      106           
Flag Coverage Δ
packages 55.66% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@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: 2

🧹 Nitpick comments (3)
apps/dashboard/src/components/embedded-wallets/Users/SearchInput.tsx (2)

6-10: Extract the props interface for better reusability and type safety.

Consider extracting the props interface to improve reusability and make the component more maintainable.

+interface SearchInputProps {
+  placeholder: string;
+  value: string;
+  onValueChange: (value: string) => void;
+}
+
-export function SearchInput(props: {
-  placeholder: string;
-  value: string;
-  onValueChange: (value: string) => void;
-}) {
+export function SearchInput(props: SearchInputProps) {

6-22: Component implementation looks good with minor accessibility enhancement needed.

The SearchInput component is well-structured and follows React best practices. Consider adding accessibility attributes for better screen reader support.

 export function SearchInput(props: {
   placeholder: string;
   value: string;
   onValueChange: (value: string) => void;
 }) {
   return (
     <div className="relative">
       <Input
         placeholder={props.placeholder}
         value={props.value}
         onChange={(e) => props.onValueChange(e.target.value)}
         className="bg-card pl-9"
+        aria-label="Search wallet users"
       />
       <SearchIcon className="-translate-y-1/2 absolute top-1/2 left-3 size-4 text-muted-foreground" />
     </div>
   );
 }
apps/dashboard/src/components/embedded-wallets/Users/index.tsx (1)

114-138: Consider performance optimizations for the filtering logic.

The current filtering approach runs on every render and could be expensive for large datasets. Consider adding debouncing and memoization.

+import { useMemo } from "react";
+// Also consider adding a debouncing hook like useDebounce

-  const filteredWallets = searchValue
-    ? wallets.filter((wallet) => {
-        const term = searchValue.toLowerCase();
-        if (wallet.id.toLowerCase().includes(term)) {
-          return true;
-        }
-        if (
-          wallet.wallets?.some((w) => w.address?.toLowerCase().includes(term))
-        ) {
-          return true;
-        }
-        if (
-          wallet.linkedAccounts?.some((acc) => {
-            return Object.values(acc.details).some(
-              (detail) =>
-                typeof detail === "string" &&
-                detail.toLowerCase().includes(term),
-            );
-          })
-        ) {
-          return true;
-        }
-        return false;
-      })
-    : wallets;
+  const filteredWallets = useMemo(() => {
+    if (!searchValue) return wallets;
+    
+    const term = searchValue.toLowerCase();
+    return wallets.filter((wallet) => {
+      if (wallet.id.toLowerCase().includes(term)) {
+        return true;
+      }
+      if (
+        wallet.wallets?.some((w) => w.address?.toLowerCase().includes(term))
+      ) {
+        return true;
+      }
+      if (
+        wallet.linkedAccounts?.some((acc) => {
+          return Object.values(acc.details).some(
+            (detail) =>
+              typeof detail === "string" &&
+              detail.toLowerCase().includes(term),
+          );
+        })
+      ) {
+        return true;
+      }
+      return false;
+    });
+  }, [wallets, searchValue]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e14a134 and f55c007.

📒 Files selected for processing (2)
  • apps/dashboard/src/components/embedded-wallets/Users/SearchInput.tsx (1 hunks)
  • apps/dashboard/src/components/embedded-wallets/Users/index.tsx (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/dashboard/src/components/embedded-wallets/Users/index.tsx (1)
apps/dashboard/src/components/embedded-wallets/Users/SearchInput.tsx (1)
  • SearchInput (6-22)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Unit Tests
  • GitHub Check: Size
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: Build Packages
  • GitHub Check: E2E Tests (pnpm, vite)
  • GitHub Check: Lint Packages
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (11)
apps/dashboard/src/components/embedded-wallets/Users/SearchInput.tsx (1)

11-22: LGTM! Clean and well-structured search input component.

The component implementation is clean with proper controlled component pattern, appropriate styling, and good icon positioning.

apps/dashboard/src/components/embedded-wallets/Users/index.tsx (10)

107-107: LGTM! Proper state management for search functionality.

The search state is appropriately initialized and managed.


176-183: LGTM! Well-positioned search input in the UI.

The search input is appropriately placed alongside the download button with proper responsive styling.


199-199: LGTM! Correct data flow with filtered results.

The table correctly receives the filtered wallet data instead of the raw data.


23-23: LGTM! Clean import of the SearchInput component.

The import statement is properly placed and follows the project's import conventions.


127-131:

❓ Verification inconclusive

Verify type safety of the details object access.

The filtering logic uses Object.values(acc.details) which might not be type-safe if the details structure is unexpected.


🏁 Script executed:

#!/bin/bash
# Check the WalletUser type definition to understand the structure of linkedAccounts.details
ast-grep --pattern 'type WalletUser = $$$'
ast-grep --pattern 'interface WalletUser {$$$}'
ast-grep --pattern 'linkedAccounts$$$details'

Length of output: 240


🏁 Script executed:

#!/bin/bash
# Search for the usage of acc.details in the Users component
rg -n "acc\\.details" -A5 -B5 apps/dashboard/src/components/embedded-wallets/Users/index.tsx
# Also locate the definition/type of acc to see where details comes from
rg -n "users\\s*[:=]" -A5 -B5 apps/dashboard/src/components/embedded-wallets/Users/index.tsx
rg -n "map\\(" -A5 -B5 apps/dashboard/src/components/embedded-wallets/Users/index.tsx

Length of output: 1752


Verify type safety of acc.details before filtering

The code at apps/dashboard/src/components/embedded-wallets/Users/index.tsx (lines 127–131) does:

return Object.values(acc.details).some(
  (detail) =>
    typeof detail === "string" &&
    detail.toLowerCase().includes(term),
);

This assumes that:

  • acc.details is always a non-null object
  • All values are (or safely coerced to) strings

Please confirm the actual type of linkedAccounts and its details field as returned by getAllEmbeddedWallets(). If details can be undefined, null, or contain non-string values, you should either:

  • Tighten the TypeScript definition (e.g. Record<string, string>)
  • Add a runtime guard, for example:
    if (typeof acc.details !== "object" || acc.details == null) {
      return false;
    }
    return Object.values(acc.details).some(/* … */);
  • Or transform/validate details at the API boundary to ensure it’s always in the expected shape.

23-23: Import looks good.

Clean import of the SearchInput component.


107-107: State management is appropriate.

Adding searchValue state for controlled input is the right approach.


176-183: UI integration looks clean.

The SearchInput is well-positioned in the header section with appropriate responsive styling. The layout with the download button works well.


199-199: Correct data source for filtered results.

Good integration - passing the filtered wallets to the table component instead of the original data.


114-138: Consider user experience with pagination and search interaction.

The current implementation filters only the wallets on the current page. Users might expect the search to work across all their wallet users, not just the current page. Consider whether this behavior aligns with user expectations.

Verify if the current page-level filtering meets the product requirements or if a different approach (like server-side search or client-side search across all pages) would be more appropriate.

Also applies to: 199-199

Comment on lines +13 to +19
<Input
placeholder={props.placeholder}
value={props.value}
onChange={(e) => props.onValueChange(e.target.value)}
className="bg-card pl-9"
/>
<SearchIcon className="-translate-y-1/2 absolute top-1/2 left-3 size-4 text-muted-foreground" />
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add accessibility attributes for better screen reader support.

The input field lacks proper accessibility attributes which could impact users with assistive technologies.

      <Input
        placeholder={props.placeholder}
        value={props.value}
        onChange={(e) => props.onValueChange(e.target.value)}
        className="bg-card pl-9"
+       aria-label={props.placeholder || "Search"}
+       type="search"
      />
📝 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
<Input
placeholder={props.placeholder}
value={props.value}
onChange={(e) => props.onValueChange(e.target.value)}
className="bg-card pl-9"
/>
<SearchIcon className="-translate-y-1/2 absolute top-1/2 left-3 size-4 text-muted-foreground" />
<Input
placeholder={props.placeholder}
value={props.value}
onChange={(e) => props.onValueChange(e.target.value)}
className="bg-card pl-9"
aria-label={props.placeholder || "Search"}
type="search"
/>
<SearchIcon className="-translate-y-1/2 absolute top-1/2 left-3 size-4 text-muted-foreground" />
🤖 Prompt for AI Agents
In apps/dashboard/src/components/embedded-wallets/Users/SearchInput.tsx around
lines 13 to 19, the Input component is missing accessibility attributes. Add
appropriate attributes such as aria-label or aria-labelledby to the Input
element to provide descriptive text for screen readers, ensuring the input field
is accessible to users with assistive technologies.

Comment on lines +114 to +138
const filteredWallets = searchValue
? wallets.filter((wallet) => {
const term = searchValue.toLowerCase();
if (wallet.id.toLowerCase().includes(term)) {
return true;
}
if (
wallet.wallets?.some((w) => w.address?.toLowerCase().includes(term))
) {
return true;
}
if (
wallet.linkedAccounts?.some((acc) => {
return Object.values(acc.details).some(
(detail) =>
typeof detail === "string" &&
detail.toLowerCase().includes(term),
);
})
) {
return true;
}
return false;
})
: wallets;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add performance optimization and fix potential null access.

The filtering logic is comprehensive and handles multiple search criteria well. However, consider adding debouncing for better performance and fix a potential null access issue.

+import { useMemo } from "react";
+import { useDebounce } from "@/hooks/useDebounce"; // or implement debouncing

 export function InAppWalletUsersPageContent(props: {
   clientId: string;
   trackingCategory: string;
   authToken: string;
 }) {
   const [activePage, setActivePage] = useState(1);
   const [searchValue, setSearchValue] = useState("");
+  const debouncedSearchValue = useDebounce(searchValue, 300);
   
-  const filteredWallets = searchValue
+  const filteredWallets = useMemo(() => debouncedSearchValue
     ? wallets.filter((wallet) => {
-        const term = searchValue.toLowerCase();
+        const term = debouncedSearchValue.toLowerCase();
         if (wallet.id.toLowerCase().includes(term)) {
           return true;
         }
         if (
           wallet.wallets?.some((w) => w.address?.toLowerCase().includes(term))
         ) {
           return true;
         }
         if (
           wallet.linkedAccounts?.some((acc) => {
+            if (!acc.details) return false;
             return Object.values(acc.details).some(
               (detail) =>
                 typeof detail === "string" &&
                 detail.toLowerCase().includes(term),
             );
           })
         ) {
           return true;
         }
         return false;
       })
-    : wallets;
+    : wallets, [debouncedSearchValue, wallets]);

Note: The current implementation only searches within the current page of results. Consider if this meets the expected user behavior or if server-side search would be more appropriate for larger datasets.

📝 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
const filteredWallets = searchValue
? wallets.filter((wallet) => {
const term = searchValue.toLowerCase();
if (wallet.id.toLowerCase().includes(term)) {
return true;
}
if (
wallet.wallets?.some((w) => w.address?.toLowerCase().includes(term))
) {
return true;
}
if (
wallet.linkedAccounts?.some((acc) => {
return Object.values(acc.details).some(
(detail) =>
typeof detail === "string" &&
detail.toLowerCase().includes(term),
);
})
) {
return true;
}
return false;
})
: wallets;
import { useMemo } from "react";
import { useDebounce } from "@/hooks/useDebounce";
export function InAppWalletUsersPageContent(props: {
clientId: string;
trackingCategory: string;
authToken: string;
}) {
const [activePage, setActivePage] = useState(1);
const [searchValue, setSearchValue] = useState("");
const debouncedSearchValue = useDebounce(searchValue, 300);
const filteredWallets = useMemo(
() =>
debouncedSearchValue
? wallets.filter((wallet) => {
const term = debouncedSearchValue.toLowerCase();
if (wallet.id.toLowerCase().includes(term)) {
return true;
}
if (
wallet.wallets?.some((w) =>
w.address?.toLowerCase().includes(term)
)
) {
return true;
}
if (
wallet.linkedAccounts?.some((acc) => {
if (!acc.details) return false;
return Object.values(acc.details).some(
(detail) =>
typeof detail === "string" &&
detail.toLowerCase().includes(term)
);
})
) {
return true;
}
return false;
})
: wallets,
[debouncedSearchValue, wallets]
);
// …rest of component
}
🤖 Prompt for AI Agents
In apps/dashboard/src/components/embedded-wallets/Users/index.tsx around lines
114 to 138, the filtering logic should be optimized by adding debouncing to the
search input to reduce the frequency of filter executions and improve
performance. Additionally, ensure all optional chaining is correctly applied to
avoid potential null or undefined access, especially when accessing nested
properties like wallet.wallets and wallet.linkedAccounts. Review whether the
current client-side filtering on the current page meets user expectations or if
a server-side search implementation is needed for larger datasets.

Copy link
Contributor

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
thirdweb (esm) 61.75 KB (0%) 1.3 s (0%) 241 ms (+78.46% 🔺) 1.5 s
thirdweb (cjs) 344.4 KB (0%) 6.9 s (0%) 978 ms (+6.16% 🔺) 7.9 s
thirdweb (minimal + tree-shaking) 5.69 KB (0%) 114 ms (0%) 89 ms (+1427.21% 🔺) 203 ms
thirdweb/chains (tree-shaking) 531 B (0%) 11 ms (0%) 32 ms (+1375.47% 🔺) 43 ms
thirdweb/react (minimal + tree-shaking) 19.54 KB (0%) 391 ms (0%) 68 ms (+258.08% 🔺) 459 ms

@joaquim-verges joaquim-verges merged commit cbde32e into main May 28, 2025
25 checks passed
@joaquim-verges joaquim-verges deleted the codex/add-text-search-to-inappwalletuserspagecontent branch May 28, 2025 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
codex Dashboard Involves changes to the Dashboard.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant