Skip to content

Conversation

kushagrasarathe
Copy link
Contributor

@kushagrasarathe kushagrasarathe commented Apr 16, 2025

contributes to TASK-10527

Summary by CodeRabbit

  • New Features
    • Added a centralized icon system with multiple new SVG icon components for consistent visuals.
    • Introduced UI components: WalletBalance (with toggle visibility), ActionButton and ActionButtonGroup, RewardsCard, Card, AchievementsBadge, StatusBadge, TransactionCard, ProfileHeader (with QR code sharing), ProfileMenuItem, and AvatarWithBadge.
    • Enhanced BottomDrawer with optional subtitle display below the handle title.
  • Refactor
    • Simplified Home and Profile pages with static layouts, grouped action buttons, and improved balance and rewards display.
    • Removed wallet carousel and wallet-based authentication; adopted direct data fetching and context-driven user management.
    • Updated navigation and header components for better usability and optional label display.
  • Style
    • Extended color palettes and Tailwind CSS configuration for consistent theming and status colors.
  • Bug Fixes
    • Enhanced loading state handling and UI responsiveness in Home and Profile sections.
  • Chores
    • Removed deprecated profile components and centralized exports to streamline the codebase.

Copy link

vercel bot commented Apr 16, 2025

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

Name Status Preview Comments Updated (UTC)
peanut-ui ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 23, 2025 8:25am

Copy link
Contributor

coderabbitai bot commented Apr 16, 2025

Walkthrough

This update refactors the mobile UI's Home and Profile pages. The Home page removes the wallet carousel, ENS fetching, Redux wallet state, and related hooks, replacing them with a static layout featuring wallet balance display, grouped action buttons, a rewards card, and a transaction list via new reusable components. The Profile page is simplified by removing wallet-based authentication, SIWE flows, and referral modals, focusing on a streamlined UI with a navigation header, user info, and profile menu items. Numerous new components for icons, badges, cards, and transactions are added, while several older Profile components and related files are removed.

Changes

File(s) Change Summary
src/app/(mobile-ui)/home/page.tsx Refactored Home page: removed wallet carousel, ENS fetching, Redux state, wallet connection hooks, profile section, logout, and animations; introduced static layout with new components for wallet balance, action buttons, rewards card, and static transaction list.
src/components/Global/Icons/*.tsx
(multiple new files)
Added multiple new SVG icon components (e.g., arrows, bank, check, eye, home, logout, search, smile, user, wallet, achievements, fees, exchange, peanut support, etc.) for UI use.
src/components/Global/Icons/Icon.tsx Added dynamic Icon component to render SVG icons by name with type safety and flexible sizing.
src/components/Global/Badges/AchievementsBadge.tsx
src/components/Global/Badges/StatusBadge.tsx
Added new badge components for achievements and status with customizable appearance and positioning.
src/components/Global/Card/index.tsx Added Card component supporting grouped card styling with configurable border and border-radius based on position.
src/components/Home/TransactionCard.tsx Introduced TransactionCard component to display transaction details with icons, formatted amounts, and status badges.
src/components/Home/HomeHistory.tsx Minor style change: removed horizontal padding class from empty state container and adjusted vertical spacing in transaction list container.
src/components/Global/WalletNavigation/index.tsx Updated imports and JSX to use new Icon component for navigation icons; adjusted icon sizes and removed extra height/width classes.
src/components/Global/NavHeader/index.tsx Updated NavHeader to make title optional, added hideLabel prop, changed icon from arrow to chevron with rotation, and conditionally render label.
src/components/Profile/ProfileHeader.tsx Added new ProfileHeader component showing user avatar, name, username with copy-to-clipboard and QR code drawer for profile URL sharing.
src/components/Profile/ProfileMenuItem.tsx Added ProfileMenuItem component for profile navigation items with icon, label, optional link, click handler, card position, and "coming soon" badge support.
src/components/Profile/index.tsx Refactored Profile page: removed SIWE authentication flow, wallet auth, referral modals, and related state; simplified to show user info, profile header, menu items, and logout button with loading and error handling.
src/app/(mobile-ui)/profile/page.tsx Updated page metadata: changed title to "Profile
src/components/0_Bruddle/Button.tsx Changed ButtonSize type alias to be exported for external use.
tailwind.config.js Extended color palettes: added new shades to primary, grey, yellow, success, and error colors; removed deprecated gold palette.
src/components/Profile/Components/OptionsComponent.tsx
src/components/Profile/Components/SkeletonPage.tsx
src/components/Profile/Components/ProfileHeader.tsx
src/components/Profile/Components/ProfileSection.tsx
src/components/Profile/Components/ProfileWalletBalance.tsx
src/components/Profile/Components/TableComponent.tsx
src/components/Profile/Components/Tabs.tsx
src/components/Profile/Components/index.ts
Removed multiple legacy Profile components: options dropdown, skeleton loader, old profile header, profile section, wallet balance display, transaction table, tabs, and their index export file.
src/components/Global/BottomDrawer/index.tsx Extended BottomDrawer props to include optional handleSubtitle with updated rendering in drag handle area to show title and subtitle.
src/components/0_Bruddle/PageContainer.tsx Added md:pl-24 class for left padding on medium screens and above in PageContainer component.
src/components/AddFunds/index.tsx Modified AddFunds component to accept optional cta prop to replace default add button if provided.
src/components/Global/CopyToClipboard/index.tsx Changed import of Icon to named import; added optional iconSize prop to control icon size; updated icon names and dynamic sizing classes.
src/components/Global/DirectSendQR/utils.ts Exported BASE_URL constant for external use; reordered imports for clarity.
src/components/Profile/AvatarWithBadge.tsx Added new AvatarWithBadge component rendering user initials avatar with optional verified badge and size variants.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant HomePage
    participant WalletHook
    participant RewardsAPI
    participant TransactionCard
    participant RewardsModal

    User->>HomePage: Visit Home Page
    HomePage->>WalletHook: Get peanutWalletDetails
    HomePage->>RewardsAPI: Fetch rewards balance (on mount)
    RewardsAPI-->>HomePage: Return rewards balance
    HomePage->>TransactionCard: Render transaction list
    User->>HomePage: Toggle balance visibility / Open RewardsModal
    HomePage->>RewardsModal: Show rewards details (if triggered)
Loading
sequenceDiagram
    participant User
    participant ProfilePage
    participant AuthContext
    participant ProfileHeader
    participant ProfileMenuItem

    User->>ProfilePage: Visit Profile Page
    ProfilePage->>AuthContext: Get user info
    ProfilePage->>ProfileHeader: Render user avatar, name, username
    ProfilePage->>ProfileMenuItem: Render profile navigation items
    User->>ProfileHeader: Click username
    ProfileHeader->>ProfileHeader: Copy URL, open QR drawer
Loading

Possibly related PRs

  • peanutprotocol/peanut-ui#788: Removes a promotional banner in the Home component, related to UI changes in the Home page.
  • peanutprotocol/peanut-ui#626: Modifies conditional routing logic inside the Home component related to wallet provider type; this PR supersedes those changes by removing wallet carousel and connection logic entirely.
  • peanutprotocol/peanut-ui#634: Improves wallet UX with wallet selection and drag/swipe logic, which is removed in this PR's full refactor of the Home page.

Suggested reviewers

  • Hugo0

Poem

In the warren of code, a new path we tread,
Old carousels gone, fresh icons instead.
With cards and with badges, the UI now gleams,
Profile and Home—refined, or so it seems.
The rabbit hops onward, with balance in view,
Simpler, more joyful—new features debut!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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.

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: 1

🧹 Nitpick comments (17)
src/components/Global/Icons/fees.tsx (1)

3-14: Well-implemented reusable icon component.

The FeesIcon is implemented in a clean, idiomatic, and flexible way. For improved accessibility, consider adding aria-hidden="true" if the icon is purely decorative, or allow passing a title prop for screen readers.

src/components/Global/Icons/home.tsx (1)

3-10: Clean and flexible icon component.

The HomeIcon is well-structured and follows best practices for reusable SVG icons. For accessibility, consider adding aria-hidden="true" if the icon is decorative, or support a title prop for screen readers.

src/components/Global/Icons/arrow-up.tsx (1)

3-10: Standard, reusable icon component.

The ArrowUpIcon is implemented in a clean and reusable way. For accessibility, consider adding aria-hidden="true" for decorative use, or support a title prop for screen readers.

src/components/Global/Icons/arrow-down.tsx (1)

3-10: Well-structured icon component.

The ArrowDownIcon is implemented in a standard, reusable way. For accessibility, consider adding aria-hidden="true" if decorative, or allow a title prop for screen readers.

src/components/Global/Icons/arrow-down-left.tsx (1)

3-10: Consistent and reusable icon component.

The ArrowDownLeftIcon is implemented cleanly and is flexible for reuse. For accessibility, consider adding aria-hidden="true" for decorative use, or support a title prop for screen readers.

src/components/Global/Icons/wallet.tsx (1)

3-12: Consider removing unnecessary fragment wrapper

The SVG component is wrapped in an empty fragment (<></>) which is not necessary since there's only a single SVG element being returned.

-export const WalletIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
-    <>
-        <svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
-            <path
-                d="M4.42105 8.46638V3.41375C4.42105 2.71902 4.98316 2.1506 5.68421 2.1506H11.3684V1.51902C11.3684 0.82428 10.8 0.255859 10.1053 0.255859H1.26316C0.562105 0.255859 0 0.82428 0 1.51902V10.3611C0 11.0559 0.562105 11.6243 1.26316 11.6243H10.1053C10.8 11.6243 11.3684 11.0559 11.3684 10.3611V9.72954H5.68421C4.98316 9.72954 4.42105 9.16112 4.42105 8.46638ZM6.31579 3.41375C5.96842 3.41375 5.68421 3.69796 5.68421 4.04533V7.83481C5.68421 8.18217 5.96842 8.46638 6.31579 8.46638H12V3.41375H6.31579ZM8.21053 6.88744C7.68632 6.88744 7.26316 6.46428 7.26316 5.94007C7.26316 5.41586 7.68632 4.9927 8.21053 4.9927C8.73474 4.9927 9.15789 5.41586 9.15789 5.94007C9.15789 6.46428 8.73474 6.88744 8.21053 6.88744Z"
-                fill="currentColor"
-            />
-        </svg>
-    </>
+export const WalletIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
+    <svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
+        <path
+            d="M4.42105 8.46638V3.41375C4.42105 2.71902 4.98316 2.1506 5.68421 2.1506H11.3684V1.51902C11.3684 0.82428 10.8 0.255859 10.1053 0.255859H1.26316C0.562105 0.255859 0 0.82428 0 1.51902V10.3611C0 11.0559 0.562105 11.6243 1.26316 11.6243H10.1053C10.8 11.6243 11.3684 11.0559 11.3684 10.3611V9.72954H5.68421C4.98316 9.72954 4.42105 9.16112 4.42105 8.46638ZM6.31579 3.41375C5.96842 3.41375 5.68421 3.69796 5.68421 4.04533V7.83481C5.68421 8.18217 5.96842 8.46638 6.31579 8.46638H12V3.41375H6.31579ZM8.21053 6.88744C7.68632 6.88744 7.26316 6.46428 7.26316 5.94007C7.26316 5.41586 7.68632 4.9927 8.21053 4.9927C8.73474 4.9927 9.15789 5.41586 9.15789 5.94007C9.15789 6.46428 8.73474 6.88744 8.21053 6.88744Z"
+            fill="currentColor"
+        />
+    </svg>
)
src/components/Global/Icons/Icon.tsx (3)

34-37: Consider adding JSDoc comments for better documentation

Adding JSDoc comments to the interface would improve developer experience when using the component.

+/**
+ * Props for the Icon component
+ * @property {IconName} name - The name of the icon to render
+ * @property {number|string} [size=24] - The size of the icon (applied to both width and height unless explicitly overridden)
+ */
export interface IconProps extends SVGProps<SVGSVGElement> {
    name: IconName
    size?: number | string
}

39-55: Consider using an object literal rather than a Record type

While the current implementation works well, using an object literal with the IconName type as keys would provide better type inference.

-const iconComponents: Record<IconName, ComponentType<SVGProps<SVGSVGElement>>> = {
+const iconComponents: { [K in IconName]: ComponentType<SVGProps<SVGSVGElement>> } = {
    'arrow-down': ArrowDownIcon,
    'arrow-down-left': ArrowDownLeftIcon,
    'arrow-up': ArrowUpIcon,
    'arrow-up-right': ArrowUpRightIcon,
    check: CheckIcon,
    exchange: ExchangeIcon,
    fees: FeesIcon,
    home: HomeIcon,
    open: OpenIcon,
    'peanut-support': PeanutSupportIcon,
    search: SearchIcon,
    'txn-off': TxnOffIcon,
    bank: BankIcon,
    wallet: WalletIcon,
}

57-66: Consider using memo for performance optimization

Since this is a presentational component that renders based on props, wrapping it with React.memo could improve performance by preventing unnecessary re-renders.

-export const Icon: FC<IconProps> = ({ name, size = 24, width, height, ...props }) => {
+export const Icon: FC<IconProps> = React.memo(({ name, size = 24, width, height, ...props }) => {
    const IconComponent = iconComponents[name]

    if (!IconComponent) {
        console.warn(`Icon "${name}" not found`)
        return null
    }

    return <IconComponent width={width || size} height={height || size} {...props} />
-}
+})
src/components/Home/TransactionCard.tsx (4)

16-26: Consider simplifying amount formatting logic

The current implementation has two separate variables for calculating the display amount. This could be simplified into a single calculation.

const TransactionCard: React.FC<TransactionCardProps> = ({ type, name, amount, status, initials = '' }) => {
-    // determine if amount should be displayed as positive or negative
-    const isNegative = type === 'send' || type === 'withdraw'
-    const displayAmount = isNegative
-        ? `-$${formatExtendedNumber(printableUsdc(amount))}`
-        : `+$${formatExtendedNumber(printableUsdc(amount))}`
-
-    // for request and send type, show the raw amount without sign
-    const finalAmount =
-        type === 'request' || type === 'send' ? `$${formatExtendedNumber(printableUsdc(amount))}` : displayAmount

+    // Format amount based on transaction type
+    const formattedAmount = `$${formatExtendedNumber(printableUsdc(amount))}`
+    const finalAmount = (() => {
+        if (type === 'request' || type === 'send') return formattedAmount
+        return type === 'withdraw' ? `-${formattedAmount}` : `+${formattedAmount}`
+    })()

62-75: Consolidate case statements for identical outcomes

In the getIconBackgroundColor function, 'send' and 'request' have the same outcome, as do 'withdraw' and 'add'. These could be consolidated to improve readability.

function getIconBackgroundColor(type: TransactionType): string {
    switch (type) {
-        case 'send':
-        case 'request':
+        case 'send': case 'request':
            return 'bg-success-1 text-black'
-        case 'withdraw':
-            return 'bg-black text-white'
-        case 'add':
+        case 'withdraw': case 'add':
            return 'bg-black text-white'
        default:
            return 'bg-gray-200'
    }
}

77-89: Add error handling for empty initials

In the renderIcon function, when handling 'send' or 'request' types, it attempts to substring the initials. If initials is an empty string (as it could be since it defaults to ''), this will still work but might not be what you intend.

function renderIcon(type: TransactionType, initials: string): React.ReactNode {
    switch (type) {
        case 'send':
        case 'request':
-            return initials.substring(0, 2).toUpperCase()
+            return initials ? initials.substring(0, 2).toUpperCase() : 'TX'
        case 'withdraw':
            return <Icon name="bank" size={16} fill="white" />
        case 'add':
            return <Icon name="arrow-down" size={14} fill="white" />
        default:
            return null
    }
}

91-102: Add a default case to the switch statement

The getActionIcon function doesn't have a default case. While the current implementation covers all values in the TransactionType type, having a default case is a good practice for future-proofing.

function getActionIcon(type: TransactionType): React.ReactNode {
    switch (type) {
        case 'send':
            return <Icon name="arrow-up-right" size={6} fill="currentColor" />
        case 'request':
            return <Icon name="arrow-down-left" size={6} fill="currentColor" />
        case 'withdraw':
            return <Icon name="arrow-up" size={8} fill="currentColor" />
        case 'add':
            return <Icon name="arrow-down" size={8} fill="currentColor" />
+        default:
+            return null
    }
}
src/app/(mobile-ui)/home/page.tsx (4)

41-65: Consider a fallback UI for missing wallet details.
While this layout is clear, if peanutWalletDetails or user info is unavailable, an additional guard or fallback messaging might improve user experience.


67-93: Transition away from hardcoded transaction entries.
These static TransactionCards serve as placeholders. Consider loading dynamic data or removing them if they're purely for demonstration.


101-135: Minor accessibility suggestion for hidden balance display.
Currently, hidden balances show “* * * *”. Consider a screen-reader-friendly approach or aria-label to clarify hidden amounts for screen reader users.


137-183: Switch-case for icons works but could be further simplified.
If more actions are added over time, a map/dictionary can help avoid frequent switch-case expansions. Otherwise, this is clean and readable.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 097fb12 and 589411b.

📒 Files selected for processing (20)
  • src/app/(mobile-ui)/home/page.tsx (2 hunks)
  • src/components/0_Bruddle/Button.tsx (1 hunks)
  • src/components/Global/Icons/Icon.tsx (1 hunks)
  • src/components/Global/Icons/arrow-down-left.tsx (1 hunks)
  • src/components/Global/Icons/arrow-down.tsx (1 hunks)
  • src/components/Global/Icons/arrow-up-right.tsx (1 hunks)
  • src/components/Global/Icons/arrow-up.tsx (1 hunks)
  • src/components/Global/Icons/bank.tsx (1 hunks)
  • src/components/Global/Icons/check.tsx (1 hunks)
  • src/components/Global/Icons/exchange.tsx (1 hunks)
  • src/components/Global/Icons/fees.tsx (1 hunks)
  • src/components/Global/Icons/home.tsx (1 hunks)
  • src/components/Global/Icons/open.tsx (1 hunks)
  • src/components/Global/Icons/peanut-support.tsx (1 hunks)
  • src/components/Global/Icons/search.tsx (1 hunks)
  • src/components/Global/Icons/txn-off.tsx (1 hunks)
  • src/components/Global/Icons/wallet.tsx (1 hunks)
  • src/components/Home/HomeHistory.tsx (3 hunks)
  • src/components/Home/TransactionCard.tsx (1 hunks)
  • tailwind.config.js (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/Global/Icons/Icon.tsx (14)
src/components/Global/Icons/arrow-down.tsx (1)
  • ArrowDownIcon (3-10)
src/components/Global/Icons/arrow-down-left.tsx (1)
  • ArrowDownLeftIcon (3-10)
src/components/Global/Icons/arrow-up.tsx (1)
  • ArrowUpIcon (3-10)
src/components/Global/Icons/arrow-up-right.tsx (1)
  • ArrowUpRightIcon (3-10)
src/components/Global/Icons/check.tsx (1)
  • CheckIcon (3-10)
src/components/Global/Icons/exchange.tsx (1)
  • ExchangeIcon (3-10)
src/components/Global/Icons/fees.tsx (1)
  • FeesIcon (3-14)
src/components/Global/Icons/home.tsx (1)
  • HomeIcon (3-10)
src/components/Global/Icons/open.tsx (1)
  • OpenIcon (3-10)
src/components/Global/Icons/peanut-support.tsx (1)
  • PeanutSupportIcon (3-25)
src/components/Global/Icons/search.tsx (1)
  • SearchIcon (3-10)
src/components/Global/Icons/txn-off.tsx (1)
  • TxnOffIcon (3-34)
src/components/Global/Icons/bank.tsx (1)
  • BankIcon (3-10)
src/components/Global/Icons/wallet.tsx (1)
  • WalletIcon (3-12)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (23)
tailwind.config.js (1)

88-90: Good addition of a new success color shade.

Adding this light green shade (#C7F9C6) to the success color palette provides a nice option for success-related UI elements with lower emphasis.

src/components/0_Bruddle/Button.tsx (1)

15-15: Good call exporting ButtonSize type.

Exporting the ButtonSize type makes it reusable across components, improving type consistency for the new ActionButton components mentioned in the summary.

src/components/Global/Icons/arrow-up-right.tsx (1)

1-10: Nice implementation of the ArrowUpRightIcon component.

The SVG icon is well-implemented with:

  • Proper typing using FC<SVGProps<SVGSVGElement>>
  • Props spreading to allow customization
  • Using currentColor for the fill, enabling styling through parent color properties

This approach is consistent with modern React icon component patterns.

src/components/Home/HomeHistory.tsx (2)

13-13: Import reordering looks good.

The reordering of the formatPaymentStatus import maintains a clean and organized imports section.


129-130: Horizontal padding removal improves layout consistency.

Removing the px-4 padding class from these container divs is a good change if the padding is being handled at a higher level container or if you're aiming for a more edge-to-edge design in the mobile UI.

Also applies to: 139-140

src/components/Global/Icons/search.tsx (1)

1-10: LGTM – idiomatic, reusable icon component.

The implementation is clean, type-safe, and follows best practices for SVG icon components in React.

src/components/Global/Icons/open.tsx (1)

1-10: LGTM – clean, consistent icon component.

The code is idiomatic, flexible, and matches the established pattern for reusable SVG icons.

src/components/Global/Icons/exchange.tsx (1)

1-10: LGTM – well-structured, reusable icon component.

The implementation is clean, type-safe, and follows best practices for SVG icon components in React.

src/components/Global/Icons/bank.tsx (1)

1-10: LGTM – clean, reusable icon component.

The implementation is idiomatic, flexible, and matches the established pattern for SVG icons.

src/components/Global/Icons/check.tsx (1)

1-10: LGTM – idiomatic, flexible icon component.

The code is clean, type-safe, and follows best practices for reusable SVG icons in React.

src/components/Global/Icons/peanut-support.tsx (1)

3-25: Looks good! Well-structured icon component.

The PeanutSupportIcon follows best practices with proper typing, the use of currentColor for fills and strokes, and appropriate SVG attributes.

src/components/Global/Icons/txn-off.tsx (1)

3-34: Looks good! Properly implemented icon component.

The TxnOffIcon follows best practices with proper typing, the use of currentColor for fills, and appropriate SVG attributes.

src/components/Global/Icons/Icon.tsx (2)

1-16: Imports are well organized

All icon components are imported individually, which is good for maintainability and clarity.


17-33: Type-safe icon name definition is a good practice

Using a union type for IconName ensures type safety when using the Icon component, preventing typos and ensuring only valid icon names are used.

src/components/Home/TransactionCard.tsx (1)

1-14: Well-defined types and props interface

The type definitions for TransactionType, TransactionStatus, and TransactionCardProps are clear and comprehensive.

src/app/(mobile-ui)/home/page.tsx (8)

3-4: All good with these imports.
No issues detected here, and the usage of Button, ButtonSize, and ButtonVariant from @/components/0_Bruddle looks appropriate.


8-8: Import for TransactionCard is fine.
The direct import of TransactionCard seems consistent with the new Home page design changes.


11-14: Utility, Link, and hooks imports look correct.
No pressing concerns; these utility and React imports are standard for the revised Home component.


17-17: No issues in accessing peanutWalletDetails.
Using peanutWalletDetails?.balance ?? BigInt(0) ensures a safe fallback for balance calculations.


35-35: Double-check the isLoading logic.
const isLoading = isFetchingUser && !username may ignore scenarios where username is missing, but user fetching has completed. Confirm if this condition aligns with your intended behavior (e.g., does a missing username after fetching represent an error state?).

Would you like me to run a script to check how this logic is used in other parts of the codebase?


37-38: Loading state handling is straightforward.
Returning <PeanutLoading coverFullScreen /> is a sensible approach for a full-screen loading indicator.


95-97: Integration of HomeHistory and RewardsModal looks sound.
No issues here; referencing these existing components harmonizes with the new layout.


184-186: ActionButtonGroup container is clear and concise.
No concerns here; using a dedicated grouping component is a nice modular approach.

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: 5

♻️ Duplicate comments (1)
src/components/Home/TransactionCard.tsx (1)

50-50: ⚠️ Potential issue

Invalid Tailwind class max-w-40 should be replaced with arbitrary value.

The class max-w-40 is not a standard Tailwind class as noted in the previous review. Use arbitrary value syntax for custom widths.

-<div className="max-w-40 truncate font-roboto text-sm font-medium">{name}</div>
+<div className="max-w-[10rem] truncate font-roboto text-sm font-medium">{name}</div>
🧹 Nitpick comments (9)
src/components/Global/WalletNavigation/index.tsx (2)

96-97: Fix inconsistent padding between navigation items.

There's a styling inconsistency: the Home span (line 81) has the pl-1 padding removed, but the Support span still has it. This will cause the text alignment to be inconsistent between these items.

-<span className="mx-auto mt-1 block pl-1 text-center text-xs font-medium">Support</span>
+<span className="mx-auto mt-1 block text-center text-xs font-medium">Support</span>

2-5: Consider unifying icon component usage.

The code uses two different icon components:

  • Desktop navigation uses Icon component (line 57)
  • Mobile navigation uses NavIcon component (lines 80, 96)

This inconsistency could make the codebase harder to maintain in the future. Consider standardizing on one icon component throughout the file.

Also applies to: 57-57, 80-80, 96-96

src/components/Global/Card/index.tsx (2)

6-12: Props interface looks good, but consider additional accessibility props.

The Card component props interface is well-structured. However, since this component can be interactive (with the onClick handler), consider adding accessibility-related props like aria-label or role to enhance screen reader support.

interface CardProps {
    children: React.ReactNode
    position?: CardPosition
    className?: string
    onClick?: () => void
    border?: boolean
+   role?: string
+   'aria-label'?: string
}

30-45: Consider making border color customizable for better reusability.

The border color is currently hardcoded as black, which limits the component's flexibility. Consider making the border color customizable through props for better themability.

interface CardProps {
    children: React.ReactNode
    position?: CardPosition
    className?: string
    onClick?: () => void
    border?: boolean
+   borderColor?: string
}

const Card: React.FC<CardProps> = ({ 
    children, 
    position = 'single', 
    className = '', 
    onClick, 
    border = true,
+   borderColor = 'black' 
}) => {
    // ...

    const getBorder = () => {
        if (!border) return ''

        switch (position) {
            case 'single':
-               return 'border border-black'
+               return `border border-${borderColor}`
            case 'first':
-               return 'border border-black'
+               return `border border-${borderColor}`
            case 'middle':
-               return 'border border-black border-t-0'
+               return `border border-${borderColor} border-t-0`
            case 'last':
-               return 'border border-black border-t-0'
+               return `border border-${borderColor} border-t-0`
            default:
-               return 'border border-black'
+               return `border border-${borderColor}`
        }
    }
}
src/components/Home/TransactionCard.tsx (3)

28-36: Improve transaction amount handling logic.

The current approach for determining the display format for transaction amounts could be simplified and made more maintainable.

-    // determine if amount should be displayed as positive or negative
-    const isNegative = type === 'send' || type === 'withdraw'
-    const displayAmount = isNegative
-        ? `-$${formatExtendedNumber(printableUsdc(amount))}`
-        : `+$${formatExtendedNumber(printableUsdc(amount))}`
-
-    // for request and send type, show the raw amount without sign
-    const finalAmount =
-        type === 'request' || type === 'send' ? `$${formatExtendedNumber(printableUsdc(amount))}` : displayAmount
+    // Format the amount based on transaction type
+    const formattedAmount = formatExtendedNumber(printableUsdc(amount))
+    const finalAmount = (() => {
+        switch(type) {
+            case 'send':
+            case 'request':
+                return `$${formattedAmount}`
+            case 'withdraw':
+                return `-$${formattedAmount}`
+            case 'add':
+                return `+$${formattedAmount}`
+            default:
+                return `$${formattedAmount}`
+        }
+    })()

74-86: Consolidate duplicate cases in getIconBackgroundColor.

The 'add' and 'withdraw' cases have identical result. You can combine them to make the code more maintainable.

function getIconBackgroundColor(type: TransactionType): string {
    switch (type) {
        case 'send':
        case 'request':
            return 'bg-success-1 text-black'
-        case 'withdraw':
-            return 'bg-black text-white'
-        case 'add':
+        case 'withdraw':
+        case 'add':
            return 'bg-black text-white'
        default:
            return 'bg-gray-200'
    }
}

39-69: Use semantic HTML structure for better accessibility.

The transaction card represents a unit of content that would benefit from using more semantic HTML elements like <article> for better accessibility.

return (
-    <Card position={position} onClick={onClick}>
+    <Card position={position} onClick={onClick} as="article">
        <div className="flex items-center justify-between">
            {/* Rest of the component */}
        </div>
    </Card>
)

This suggestion requires updating the Card component to support rendering as different HTML elements using the as prop:

interface CardProps {
    as?: React.ElementType
    // other props
}

const Card: React.FC<CardProps> = ({ 
    as: Component = 'div',
    // other props 
}) => {
    return (
        <Component 
            className={/* classes */}
            onClick={onClick}
        >
            {children}
        </Component>
    )
}
src/app/(mobile-ui)/home/page.tsx (2)

187-200: Icon sizes may be too small for touch targets and visibility.

The ActionButton component uses 8px icons which may be too small for proper visibility and touch target sizes (which should be at least 44x44px for mobile).

switch (action) {
    case 'send':
-       return <Icon name="arrow-up-right" size={8} fill="currentColor" />
+       return <Icon name="arrow-up-right" size={12} fill="currentColor" />
    case 'withdraw':
-       return <Icon name="arrow-down" size={8} fill="currentColor" />
+       return <Icon name="arrow-down" size={12} fill="currentColor" />
    case 'add':
-       return <Icon name="arrow-up" size={8} fill="currentColor" />
+       return <Icon name="arrow-up" size={12} fill="currentColor" />
    case 'request':
-       return <Icon name="arrow-down-left" size={8} fill="currentColor" />
+       return <Icon name="arrow-down-left" size={12} fill="currentColor" />
    default:
        return null
}

41-52: Handle errors more gracefully when fetching rewards balance.

The current implementation logs errors to the console but doesn't update the UI or retry the operation if the rewards balance fetch fails.

useEffect(() => {
    const fetchRewardsBalance = async () => {
        try {
            const balance = await getRewardWalletBalance()
            setRewardsBalance(balance)
        } catch (error) {
            console.error('Failed to fetch rewards balance:', error)
+           setRewardsBalance('0') // Set a default value on error
+           // Consider implementing a retry mechanism or showing an error state
        }
    }

    fetchRewardsBalance()
}, [getRewardWalletBalance])
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 589411b and 35f2590.

📒 Files selected for processing (7)
  • src/app/(mobile-ui)/home/page.tsx (2 hunks)
  • src/components/Global/Card/index.tsx (1 hunks)
  • src/components/Global/Icons/Icon.tsx (1 hunks)
  • src/components/Global/Icons/eye-slash.tsx (1 hunks)
  • src/components/Global/Icons/eye.tsx (1 hunks)
  • src/components/Global/WalletNavigation/index.tsx (3 hunks)
  • src/components/Home/TransactionCard.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/components/Global/Icons/eye.tsx
  • src/components/Global/Icons/eye-slash.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Global/Icons/Icon.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Deploy-Preview

Comment on lines +14 to +55
const Card: React.FC<CardProps> = ({ children, position = 'single', className = '', onClick, border = true }) => {
const getBorderRadius = () => {
switch (position) {
case 'single':
return 'rounded-sm'
case 'first':
return 'rounded-t-sm'
case 'last':
return 'rounded-b-sm'
case 'middle':
return ''
default:
return 'rounded-sm'
}
}

const getBorder = () => {
if (!border) return ''

switch (position) {
case 'single':
return 'border border-black'
case 'first':
return 'border border-black'
case 'middle':
return 'border border-black border-t-0'
case 'last':
return 'border border-black border-t-0'
default:
return 'border border-black'
}
}

return (
<div
className={twMerge('w-full overflow-hidden bg-white px-4 py-2', getBorderRadius(), getBorder(), className)}
onClick={onClick}
>
{children}
</div>
)
}
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 when using onClick handlers.

When a div is used with an onClick handler, it becomes an interactive element but lacks proper keyboard accessibility. This makes it inaccessible to keyboard-only users.

return (
    <div
        className={twMerge('w-full overflow-hidden bg-white px-4 py-2', getBorderRadius(), getBorder(), className)}
        onClick={onClick}
+       tabIndex={onClick ? 0 : undefined}
+       role={onClick ? 'button' : undefined}
+       onKeyDown={onClick ? (e) => e.key === 'Enter' && onClick() : undefined}
    >
        {children}
    </div>
)
📝 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 Card: React.FC<CardProps> = ({ children, position = 'single', className = '', onClick, border = true }) => {
const getBorderRadius = () => {
switch (position) {
case 'single':
return 'rounded-sm'
case 'first':
return 'rounded-t-sm'
case 'last':
return 'rounded-b-sm'
case 'middle':
return ''
default:
return 'rounded-sm'
}
}
const getBorder = () => {
if (!border) return ''
switch (position) {
case 'single':
return 'border border-black'
case 'first':
return 'border border-black'
case 'middle':
return 'border border-black border-t-0'
case 'last':
return 'border border-black border-t-0'
default:
return 'border border-black'
}
}
return (
<div
className={twMerge('w-full overflow-hidden bg-white px-4 py-2', getBorderRadius(), getBorder(), className)}
onClick={onClick}
>
{children}
</div>
)
}
const Card: React.FC<CardProps> = ({ children, position = 'single', className = '', onClick, border = true }) => {
const getBorderRadius = () => {
switch (position) {
case 'single':
return 'rounded-sm'
case 'first':
return 'rounded-t-sm'
case 'last':
return 'rounded-b-sm'
case 'middle':
return ''
default:
return 'rounded-sm'
}
}
const getBorder = () => {
if (!border) return ''
switch (position) {
case 'single':
return 'border border-black'
case 'first':
return 'border border-black'
case 'middle':
return 'border border-black border-t-0'
case 'last':
return 'border border-black border-t-0'
default:
return 'border border-black'
}
}
return (
<div
className={twMerge('w-full overflow-hidden bg-white px-4 py-2', getBorderRadius(), getBorder(), className)}
onClick={onClick}
tabIndex={onClick ? 0 : undefined}
role={onClick ? 'button' : undefined}
onKeyDown={onClick ? (e) => e.key === 'Enter' && onClick() : undefined}
>
{children}
</div>
)
}

Comment on lines 60 to 66
<span
className={`rounded-full px-2 py-0.5 font-roboto text-[10px] font-semibold ${
status === 'completed' ? 'bg-success-2 text-success-1' : 'bg-secondary-4 text-secondary-1'
}`}
>
{status === 'completed' ? 'Completed' : 'Pending'}
</span>
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Ensure color contrast for accessibility in status badges.

The status badges use color combinations that may not provide sufficient contrast for users with visual impairments. Ensure that the color combinations meet WCAG standards for text contrast.


🏁 Script executed:

#!/bin/bash
# Check if the color variables in tailwind.config.js have sufficient contrast

# Find and display the success and secondary color definitions
echo "Checking color definitions in Tailwind config:"
fd -t f "tailwind.config" -x grep -A 15 -E "success|secondary" {} \;

Length of output: 6961


Action Required: Update Status Badge Colors for Accessibility

After verifying the Tailwind configuration, the current color pairs for both statuses do not appear to meet WCAG contrast standards for small text. In particular:

  • Completed Badge:

    • Background: bg-success-2#C7F9C6
    • Text: text-success-1#16B413
  • Pending Badge:

    • Background: bg-secondary-4#FFF4CC
    • Text: text-secondary-1#FFC900

Preliminary contrast estimates indicate that both pairs provide a contrast ratio well below the 4.5:1 minimum requirement for body text (especially given the 10px font size used). This is most notable for the pending badge, where the background and text colors are very similar.

Recommended next steps:

  • Revisit the color choices for both statuses and adjust them to achieve sufficient contrast.
  • Validate the updated colors with a WCAG contrast checker to ensure compliance.

Comment on lines 88 to 124
<div className="mt-6 space-y-3">
<h2 className="font-bold">Transactions</h2>
<div>
<TransactionCard
type="send"
name="Hugo Montenegro"
amount={BigInt(6969000000)}
status="completed"
initials="HM"
position="first"
/>

<TransactionCard
type="withdraw"
name="Bank Account #1"
amount={BigInt(6969000000)}
status="completed"
position="middle"
/>

<TransactionCard
type="add"
name="peanut.ens"
amount={BigInt(6969000000)}
status="completed"
position="middle"
/>

<TransactionCard
type="request"
name="dasdasdasdsa Montenegro"
amount={BigInt(6969000000)}
status="pending"
initials="HM"
position="last"
/>
</div>
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

Replace hardcoded transaction examples with dynamic data.

The current implementation uses static transaction examples with identical amounts. Consider implementing real transaction data loading or at least varying the example data to better reflect real-world usage.

/* Transaction cards - temporary */
<div className="mt-6 space-y-3">
    <h2 className="font-bold">Transactions</h2>
+   {transactions.length > 0 ? (
    <div>
-       <TransactionCard
-           type="send"
-           name="Hugo Montenegro"
-           amount={BigInt(6969000000)}
-           status="completed"
-           initials="HM"
-           position="first"
-       />
-
-       <TransactionCard
-           type="withdraw"
-           name="Bank Account #1"
-           amount={BigInt(6969000000)}
-           status="completed"
-           position="middle"
-       />
-
-       <TransactionCard
-           type="add"
-           name="peanut.ens"
-           amount={BigInt(6969000000)}
-           status="completed"
-           position="middle"
-       />
-
-       <TransactionCard
-           type="request"
-           name="dasdasdasdsa Montenegro"
-           amount={BigInt(6969000000)}
-           status="pending"
-           initials="HM"
-           position="last"
-       />
+       {transactions.map((tx, index) => (
+           <TransactionCard
+               key={tx.id || index}
+               type={tx.type}
+               name={tx.name}
+               amount={tx.amount}
+               status={tx.status}
+               initials={tx.initials}
+               position={getPositionForIndex(index, transactions.length)}
+           />
+       ))}
    </div>
+   ) : (
+       <EmptyState
+           message="No transactions yet"
+           actionText="Make your first transaction"
+           actionLink="/send"
+       />
+   )}
</div>

Add a helper function to determine position:

function getPositionForIndex(index: number, total: number): CardPosition {
    if (total === 1) return 'single';
    if (index === 0) return 'first';
    if (index === total - 1) return 'last';
    return 'middle';
}

Comment on lines +161 to +165

<button onClick={onToggleBalanceVisibility}>
<Icon name={isBalanceHidden ? 'eye-slash' : 'eye'} className={'h-6 w-6'} fill={'black'} />
</button>
</div>
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 proper ARIA label for the visibility toggle button.

The button for toggling balance visibility doesn't have a descriptive aria-label, making it unclear for screen reader users what the button does.

-<button onClick={onToggleBalanceVisibility}>
+<button 
+   onClick={onToggleBalanceVisibility}
+   aria-label={isBalanceHidden ? "Show balance" : "Hide balance"}
+>
    <Icon name={isBalanceHidden ? 'eye-slash' : 'eye'} className={'h-6 w-6'} fill={'black'} />
</button>
📝 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
<button onClick={onToggleBalanceVisibility}>
<Icon name={isBalanceHidden ? 'eye-slash' : 'eye'} className={'h-6 w-6'} fill={'black'} />
</button>
</div>
<button
onClick={onToggleBalanceVisibility}
aria-label={isBalanceHidden ? "Show balance" : "Hide balance"}
>
<Icon name={isBalanceHidden ? 'eye-slash' : 'eye'} className={'h-6 w-6'} fill={'black'} />
</button>
</div>

Comment on lines +144 to 149
return (
<span className="inline-flex items-center">
<span className="relative top-1">* * * *</span>
</span>
)
}
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

Improve screen reader accessibility for masked balance.

Using asterisks for masked balance is not accessible for screen readers. Consider adding proper aria attributes for better screen reader support.

if (isBalanceHidden) {
    return (
-       <span className="inline-flex items-center">
-           <span className="relative top-1">* * * *</span>
+       <span className="inline-flex items-center" aria-label="Balance hidden">
+           <span className="relative top-1" aria-hidden="true">* * * *</span>
        </span>
    )
}
📝 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
return (
<span className="inline-flex items-center">
<span className="relative top-1">* * * *</span>
</span>
)
}
if (isBalanceHidden) {
return (
<span className="inline-flex items-center" aria-label="Balance hidden">
<span className="relative top-1" aria-hidden="true">* * * *</span>
</span>
)
}

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: 4

🧹 Nitpick comments (4)
src/components/Global/Badges/StatusBadge.tsx (1)

29-44: Good text mapping implementation.

The text mapping for different statuses is clear and provides good defaults. Consider adding a capitalization utility function if more status types are added in the future to maintain consistent casing.

src/components/Profile/ProfileHeader.tsx (1)

55-65: Remove unnecessary Fragment wrapper.

The Fragment wrapper around the QRBottomDrawer is unnecessary since it contains only one child element.

-            {isQRScannerOpen && (
-                <>
-                    <QRBottomDrawer
-                        url={profileUrl}
-                        collapsedTitle="Your Peanut profile is public"
-                        expandedTitle="Your Peanut profile is public"
-                        text="Let others scan this to see your profile"
-                        buttonText="Share Profile Link"
-                    />
-                </>
-            )}
+            {isQRScannerOpen && (
+                <QRBottomDrawer
+                    url={profileUrl}
+                    collapsedTitle="Your Peanut profile is public"
+                    expandedTitle="Your Peanut profile is public"
+                    text="Let others scan this to see your profile"
+                    buttonText="Share Profile Link"
+                />
+            )}
🧰 Tools
🪛 Biome (1.9.4)

[error] 56-64: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

src/components/Profile/index.tsx (2)

78-87: disabled prop might receive a non‑boolean value

isLoading is compared to the string 'Idle' in your loading context, so here disabled can unintentionally become a string rather than a boolean.
Convert it explicitly to a boolean for correct HTML semantics and accessibility.

-<Button
-    disabled={isLoading}
+<Button
+    disabled={Boolean(isLoading)}

85-86: Provide accessible text for the logout icon

Screen‑reader users will only “hear” the icon when it has an accessible label. Either use aria-hidden on the icon and rely on the adjacent text, or add aria-label.

-<Icon name="logout" size={20} fill="black" />
+<Icon
+    name="logout"
+    size={20}
+    fill="black"
+    aria-hidden="true"
/>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 35f2590 and 78cd147.

📒 Files selected for processing (25)
  • src/app/(mobile-ui)/profile/page.tsx (1 hunks)
  • src/components/Global/Badges/AchievementsBadge.tsx (1 hunks)
  • src/components/Global/Badges/StatusBadge.tsx (1 hunks)
  • src/components/Global/Icons/Icon.tsx (1 hunks)
  • src/components/Global/Icons/achievements.tsx (1 hunks)
  • src/components/Global/Icons/chevron-up.tsx (1 hunks)
  • src/components/Global/Icons/currency.tsx (1 hunks)
  • src/components/Global/Icons/logout.tsx (1 hunks)
  • src/components/Global/Icons/share.tsx (1 hunks)
  • src/components/Global/Icons/smile.tsx (1 hunks)
  • src/components/Global/Icons/user.tsx (1 hunks)
  • src/components/Global/NavHeader/index.tsx (1 hunks)
  • src/components/Home/TransactionCard.tsx (1 hunks)
  • src/components/Profile/Components/OptionsComponent.tsx (0 hunks)
  • src/components/Profile/Components/ProfileHeader.tsx (0 hunks)
  • src/components/Profile/Components/ProfileSection.tsx (0 hunks)
  • src/components/Profile/Components/ProfileWalletBalance.tsx (0 hunks)
  • src/components/Profile/Components/SkeletonPage.tsx (0 hunks)
  • src/components/Profile/Components/TableComponent.tsx (0 hunks)
  • src/components/Profile/Components/Tabs.tsx (0 hunks)
  • src/components/Profile/Components/index.ts (0 hunks)
  • src/components/Profile/ProfileHeader.tsx (1 hunks)
  • src/components/Profile/ProfileMenuItem.tsx (1 hunks)
  • src/components/Profile/index.tsx (1 hunks)
  • tailwind.config.js (5 hunks)
💤 Files with no reviewable changes (8)
  • src/components/Profile/Components/index.ts
  • src/components/Profile/Components/ProfileWalletBalance.tsx
  • src/components/Profile/Components/Tabs.tsx
  • src/components/Profile/Components/OptionsComponent.tsx
  • src/components/Profile/Components/ProfileHeader.tsx
  • src/components/Profile/Components/ProfileSection.tsx
  • src/components/Profile/Components/TableComponent.tsx
  • src/components/Profile/Components/SkeletonPage.tsx
✅ Files skipped from review due to trivial changes (9)
  • src/app/(mobile-ui)/profile/page.tsx
  • src/components/Global/Icons/currency.tsx
  • src/components/Global/Icons/achievements.tsx
  • src/components/Global/Icons/chevron-up.tsx
  • src/components/Global/Icons/logout.tsx
  • src/components/Global/Icons/share.tsx
  • src/components/Global/Icons/user.tsx
  • src/components/Global/Badges/AchievementsBadge.tsx
  • src/components/Global/Icons/smile.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • tailwind.config.js
  • src/components/Global/Icons/Icon.tsx
  • src/components/Home/TransactionCard.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/components/Global/NavHeader/index.tsx (2)
src/components/0_Bruddle/Button.tsx (1)
  • Button (63-94)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (81-90)
src/components/Profile/index.tsx (3)
src/context/authContext.tsx (1)
  • useAuth (317-323)
src/components/0_Bruddle/Button.tsx (1)
  • Button (63-94)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (81-90)
🪛 Biome (1.9.4)
src/components/Profile/ProfileHeader.tsx

[error] 56-64: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

🔇 Additional comments (17)
src/components/Global/NavHeader/index.tsx (5)

3-3: Great use of the centralized Icon component.

The switch from the previous icon implementation to the new centralized Icon component is a good change that promotes consistency across the UI.


6-9: Good API improvements with optional props.

Making the title optional and adding the new hideLabel prop increases the component's flexibility, allowing it to be used in more contexts throughout the application.


11-11: LGTM: Good use of default parameter value.

Setting a default value for the hideLabel prop ensures backward compatibility with existing usages of the component.


16-16: Icon looks good with proper sizing and rotation.

The icon update with the new centralized component and proper sizing/rotation attributes provides a consistent appearance.


19-23: Good conditional rendering implementation.

The conditional rendering of the title label is well-implemented, properly using the hideLabel prop to control visibility.

src/components/Global/Badges/StatusBadge.tsx (4)

4-10: Well-defined types and props interface.

Good use of TypeScript for type safety with explicit status types and prop definitions. The component has a clean API with all necessary options.


12-28: Well-structured component with clear helper functions.

The component efficiently uses helper functions to organize logic for different aspects of rendering. The status styles are appropriately mapped to semantic color classes.


46-58: LGTM: Size classes are well organized.

The size classes are well-defined with appropriate spacing and text sizes for each option. The default case appropriately falls back to the small size.


59-71: Clean rendering with proper class composition.

Good use of twMerge to combine the various class sets. The component properly applies base styling, size-specific classes, and status-specific styles in the correct order.

src/components/Profile/ProfileMenuItem.tsx (4)

1-15: Good component imports and props interface.

The component has a clean props interface with appropriate types. Good use of the IconName type from the centralized icon system and proper integration with other components like StatusBadge and Card.


16-23: Well-structured component with good defaults.

The component has sensible defaults for the position and comingSoon props, making it easier to use while maintaining flexibility.


24-39: Clean and consistent content layout.

The content section has a good layout with proper alignment and spacing. The conditional rendering of either the status badge or the chevron icon is well-implemented.


41-56: Good conditional rendering based on props.

The component intelligently renders either a clickable card (wrapped in a Next.js Link) or a non-interactive card based on the comingSoon flag and the presence of an href. This provides good flexibility for different menu item states.

src/components/Profile/ProfileHeader.tsx (4)

1-15: Good component structure and props interface.

The component has a clean props interface with appropriate types for user profile data. Good integration with other components like AchievementsBadge and QRBottomDrawer.


16-20: Clean state management for QR drawer.

Good use of React hooks for managing the QR scanner drawer state. The profile URL formation is straightforward and reusable.


24-35: Good avatar implementation with verification badge.

The avatar with initials is well-implemented with proper styling and conditional rendering of the verification badge.


41-54: Good implementation of the share button.

The share button has good UX by automatically copying the profile URL to clipboard and opening the QR code drawer. The button styling is consistent with the design system.

Comment on lines +38 to +40
const fullName = user?.user.full_name || user?.user?.username || 'Anonymous User'
const username = user?.user.username || 'anonymous'
const initials = getInitials(fullName)
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

Avoid double nesting on user?.user & derive verification dynamically

useAuth() already returns the user object; nesting another user property is easy to overlook and may break on API changes.
Also, isVerified is hard‑coded to true, which could expose unverified users as verified.

-const fullName = user?.user.full_name || user?.user?.username || 'Anonymous User'
-const username = user?.user.username || 'anonymous'
-const initials = getInitials(fullName)
+const fullName = user?.full_name ?? user?.username ?? 'Anonymous User'
+const username = user?.username ?? 'anonymous'
+const initials = getInitials(fullName)
+const isVerified = Boolean(user?.isVerified)

Remember to pass isVerified={isVerified} to ProfileHeader.

📝 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 fullName = user?.user.full_name || user?.user?.username || 'Anonymous User'
const username = user?.user.username || 'anonymous'
const initials = getInitials(fullName)
const fullName = user?.full_name ?? user?.username ?? 'Anonymous User'
const username = user?.username ?? 'anonymous'
const initials = getInitials(fullName)
const isVerified = Boolean(user?.isVerified)

Comment on lines +14 to 16
const { setLoadingState, isLoading } = useContext(loadingStateContext)
const { logoutUser, user } = useAuth()

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against missing providers

Both loadingStateContext and useAuth() throw/return undefined when the surrounding provider is absent, which would crash the tree.
A simple runtime assertion helps surface the problem early and avoids hard‑to‑trace blank screens in production.

-const { setLoadingState, isLoading } = useContext(loadingStateContext)
-const { logoutUser, user } = useAuth()
+const loadingCtx = useContext(loadingStateContext)
+if (!loadingCtx) {
+    throw new Error('Profile must be rendered inside <LoadingStateProvider>')
+}
+const { setLoadingState, isLoading } = loadingCtx
+
+const { logoutUser, user } = useAuth()  // will throw if provider missing
📝 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 { setLoadingState, isLoading } = useContext(loadingStateContext)
const { logoutUser, user } = useAuth()
const loadingCtx = useContext(loadingStateContext)
if (!loadingCtx) {
throw new Error('Profile must be rendered inside <LoadingStateProvider>')
}
const { setLoadingState, isLoading } = loadingCtx
const { logoutUser, user } = useAuth() // will throw if provider missing

Comment on lines +29 to +36
const getInitials = (name: string) => {
return name
.split(' ')
.map((part) => part[0])
.join('')
.toUpperCase()
.substring(0, 2)
}
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

Harden getInitials against edge‑cases

split(' ') leaves empty strings when the name contains double spaces and returns [''] for an empty string, leading to part[0] being undefined.
Filtering falsy parts prevents undefined from creeping into the UI.

-return name
-    .split(' ')
-    .map((part) => part[0])
+return name
+    .trim()
+    .split(/\s+/)          // collapse multiple spaces
+    .filter(Boolean)       // remove empty chunks
+    .map((part) => part[0])
     .join('')
     .toUpperCase()
     .substring(0, 2)

Comment on lines 13 to 16
export const Profile = () => {
const { address } = useWallet()
const { setLoadingState, loadingState, isLoading } = useContext(context.loadingStateContext)
const { signMessageAsync } = useSignMessage()
const { user, fetchUser, isFetchingUser, logoutUser } = useAuth()

const [_isLoading, _setIsLoading] = useState(false)
const [modalVisible, setModalVisible] = useState(false)
const [modalType, setModalType] = useState<'Boost' | 'Invites' | undefined>(undefined)
const [errorState, setErrorState] = useState<{
showError: boolean
errorMessage: string
}>({ showError: false, errorMessage: '' })

const handleSiwe = async () => {
try {
_setIsLoading(true)
setErrorState({
showError: false,
errorMessage: '',
})
if (!address) return

const userIdResponse = await fetchWithSentry('/api/peanut/user/get-user-id', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
accountIdentifier: address,
}),
})

const response = await userIdResponse.json()

const siwemsg = createSiweMessage({
address: address ?? '',
statement: `Sign in to peanut.to. This is your unique user identifier! ${response.userId}`,
})

const signature = await signMessageAsync({
message: siwemsg,
})

await fetchWithSentry('/api/peanut/user/get-jwt-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
signature: signature,
message: siwemsg,
}),
})

fetchUser()
} catch (error) {
console.error('Authentication error:', error)
setErrorState({
showError: true,
errorMessage: 'Error while authenticating. Please try again later.',
})
Sentry.captureException(error)
} finally {
_setIsLoading(false)
}
}
const { setLoadingState, isLoading } = useContext(loadingStateContext)
const { logoutUser, user } = useAuth()

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 an explicit return type and memo‑ised callbacks to improve type‑safety & re‑renders

Profile is exported without a return‑type annotation and handleLogout is re‑created on every render.
Adding an explicit JSX.Element (or ReactElement) return type together with useCallback avoids accidental type regressions and prevents needless re‑renders of memoised children (e.g. NavHeader, ProfileHeader).

-import { useContext } from 'react'
+import { useContext, useCallback } from 'react'
 ...
-export const Profile = () => {
+export const Profile = (): JSX.Element => {
     const { setLoadingState, isLoading } = useContext(loadingStateContext)
     const { logoutUser, user } = useAuth()
 
-    const handleLogout = async () => {
+    const handleLogout = useCallback(async () => {
         ...
-    }
+    }, [logoutUser, setLoadingState])
📝 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
export const Profile = () => {
const { address } = useWallet()
const { setLoadingState, loadingState, isLoading } = useContext(context.loadingStateContext)
const { signMessageAsync } = useSignMessage()
const { user, fetchUser, isFetchingUser, logoutUser } = useAuth()
const [_isLoading, _setIsLoading] = useState(false)
const [modalVisible, setModalVisible] = useState(false)
const [modalType, setModalType] = useState<'Boost' | 'Invites' | undefined>(undefined)
const [errorState, setErrorState] = useState<{
showError: boolean
errorMessage: string
}>({ showError: false, errorMessage: '' })
const handleSiwe = async () => {
try {
_setIsLoading(true)
setErrorState({
showError: false,
errorMessage: '',
})
if (!address) return
const userIdResponse = await fetchWithSentry('/api/peanut/user/get-user-id', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
accountIdentifier: address,
}),
})
const response = await userIdResponse.json()
const siwemsg = createSiweMessage({
address: address ?? '',
statement: `Sign in to peanut.to. This is your unique user identifier! ${response.userId}`,
})
const signature = await signMessageAsync({
message: siwemsg,
})
await fetchWithSentry('/api/peanut/user/get-jwt-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
signature: signature,
message: siwemsg,
}),
})
fetchUser()
} catch (error) {
console.error('Authentication error:', error)
setErrorState({
showError: true,
errorMessage: 'Error while authenticating. Please try again later.',
})
Sentry.captureException(error)
} finally {
_setIsLoading(false)
}
}
const { setLoadingState, isLoading } = useContext(loadingStateContext)
const { logoutUser, user } = useAuth()
import { useContext, useCallback } from 'react'
...
export const Profile = (): JSX.Element => {
const { setLoadingState, isLoading } = useContext(loadingStateContext)
const { logoutUser, user } = useAuth()
const handleLogout = useCallback(async () => {
...
}, [logoutUser, setLoadingState])
// …
}

@kushagrasarathe kushagrasarathe changed the title [WIP] feat: contacts UI feat: new home page ui Apr 17, 2025
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: 1

🧹 Nitpick comments (3)
src/components/Global/BottomDrawer/index.tsx (1)

245-248: Consider using semantic HTML hierarchy for the subtitle

While the implementation works correctly, using <h2> for both title and subtitle doesn't maintain proper HTML heading hierarchy.

 <div className="mb-8 space-y-1">
     {handleTitle && <h2 className="text-lg font-extrabold">{handleTitle}</h2>}
-    {handleSubtitle && <h2 className="mb-8">{handleSubtitle}</h2>}
+    {handleSubtitle && <h3 className="mb-8 text-base">{handleSubtitle}</h3>}
 </div>
src/components/Profile/ProfileHeader.tsx (2)

49-52: Add error handling for clipboard operations

The clipboard API might not be available in all browsers or contexts. Consider adding error handling.

onClick={() => {
-   navigator.clipboard.writeText(profileUrl)
-   setIsQRScannerOpen(true)
+   try {
+     navigator.clipboard.writeText(profileUrl)
+     setIsQRScannerOpen(true)
+   } catch (error) {
+     console.error('Failed to copy to clipboard:', error)
+     // Still open the QR code so the user can copy manually
+     setIsQRScannerOpen(true)
+   }
}

54-56: Enhance button accessibility

Add an aria-label to improve accessibility for screen readers.

<Button
    size="small"
    variant="primary-soft"
    shadowSize="4"
    className="flex w-fit items-center justify-center gap-2 rounded-full px-4 py-2"
+   aria-label="Copy and share profile URL"
    onClick={() => {
        navigator.clipboard.writeText(profileUrl)
        setIsQRScannerOpen(true)
    }}
>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 78cd147 and 7d08f04.

📒 Files selected for processing (2)
  • src/components/Global/BottomDrawer/index.tsx (4 hunks)
  • src/components/Profile/ProfileHeader.tsx (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/components/Profile/ProfileHeader.tsx

[error] 59-77: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

🔇 Additional comments (3)
src/components/Global/BottomDrawer/index.tsx (2)

13-13: Good addition of the handleSubtitle prop

Adding the optional subtitle to the interface will enhance the flexibility of the drawer component.


27-27: LGTM - Appropriate default value

The empty string default for handleSubtitle is consistent with the handleTitle default.

src/components/Profile/ProfileHeader.tsx (1)

61-64: Great use of the new handleSubtitle prop

The implementation leverages the newly added subtitle capability in the BottomDrawer component effectively.

Comment on lines 59 to 77
<>
<BottomDrawer
initialPosition="collapsed"
handleTitle={'Your Peanut profile is public'}
handleSubtitle="Share it to receive payments!"
collapsedHeight={80}
expandedHeight={90}
isOpen={isQRScannerOpen}
onClose={() => setIsQRScannerOpen(false)}
>
<div className="space-y-6">
<QRCodeWrapper url={profileUrl} />
<Divider className="text-gray-500" text="or" />
<ShareButton url={profileUrl} title="Share your profile">
Your Peanut profile is public
</ShareButton>
</div>
</BottomDrawer>
</>
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

Remove unnecessary Fragment

The Fragment wrapper is redundant since it contains only one child element.

-{isQRScannerOpen && (
-    <>
-        <BottomDrawer
-            initialPosition="collapsed"
-            handleTitle={'Your Peanut profile is public'}
-            handleSubtitle="Share it to receive payments!"
-            collapsedHeight={80}
-            expandedHeight={90}
-            isOpen={isQRScannerOpen}
-            onClose={() => setIsQRScannerOpen(false)}
-        >
-            <div className="space-y-6">
-                <QRCodeWrapper url={profileUrl} />
-                <Divider className="text-gray-500" text="or" />
-                <ShareButton url={profileUrl} title="Share your profile">
-                    Your Peanut profile is public
-                </ShareButton>
-            </div>
-        </BottomDrawer>
-    </>
-)}
+{isQRScannerOpen && (
+    <BottomDrawer
+        initialPosition="collapsed"
+        handleTitle={'Your Peanut profile is public'}
+        handleSubtitle="Share it to receive payments!"
+        collapsedHeight={80}
+        expandedHeight={90}
+        isOpen={isQRScannerOpen}
+        onClose={() => setIsQRScannerOpen(false)}
+    >
+        <div className="space-y-6">
+            <QRCodeWrapper url={profileUrl} />
+            <Divider className="text-gray-500" text="or" />
+            <ShareButton url={profileUrl} title="Share your profile">
+                Your Peanut profile is public
+            </ShareButton>
+        </div>
+    </BottomDrawer>
+)}
📝 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
<>
<BottomDrawer
initialPosition="collapsed"
handleTitle={'Your Peanut profile is public'}
handleSubtitle="Share it to receive payments!"
collapsedHeight={80}
expandedHeight={90}
isOpen={isQRScannerOpen}
onClose={() => setIsQRScannerOpen(false)}
>
<div className="space-y-6">
<QRCodeWrapper url={profileUrl} />
<Divider className="text-gray-500" text="or" />
<ShareButton url={profileUrl} title="Share your profile">
Your Peanut profile is public
</ShareButton>
</div>
</BottomDrawer>
</>
{isQRScannerOpen && (
<BottomDrawer
initialPosition="collapsed"
handleTitle={'Your Peanut profile is public'}
handleSubtitle="Share it to receive payments!"
collapsedHeight={80}
expandedHeight={90}
isOpen={isQRScannerOpen}
onClose={() => setIsQRScannerOpen(false)}
>
<div className="space-y-6">
<QRCodeWrapper url={profileUrl} />
<Divider className="text-gray-500" text="or" />
<ShareButton url={profileUrl} title="Share your profile">
Your Peanut profile is public
</ShareButton>
</div>
</BottomDrawer>
)}
🧰 Tools
🪛 Biome (1.9.4)

[error] 59-77: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

Copy link

Implement home scree ui

Copy link
Contributor

@jjramirezn jjramirezn left a comment

Choose a reason for hiding this comment

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

left some comments

Copy link
Contributor

Choose a reason for hiding this comment

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

question: is this different than the Card in 0_Bruddle?

Does this serve another purpose? If not, then we should only use one in the application, if they serve different purposes then one of them should have the name changed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jjramirezn yes the new card is different and serves different purpose, and it's being commonly used in the new designs, so created a new component.

kept the name similar, cuz, the old card will be removed very soon, once the flows are updated

Copy link
Contributor

Choose a reason for hiding this comment

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

issue: lots of these icons are already in the Global/Icons component

why not use it? if this is an intended refactor then we should delete that other component

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yess, some icons are there, we now have many new icons in the designs, and using the icons the old way is time consuming, copying specific parts of svg's and then adjusting them (bad devX), so i moved to a new format, old ones are going to be removed once we finish updating the UI's. Dont want to scope it in this pr

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: 0

♻️ Duplicate comments (3)
src/app/(mobile-ui)/home/page.tsx (3)

89-127: Replace hardcoded transaction examples with dynamic data.

The current implementation uses static transaction examples with identical amounts. Consider implementing real transaction data loading or at least varying the example data to better reflect real-world usage.


147-152: Improve screen reader accessibility for masked balance.

Using asterisks for masked balance is not accessible for screen readers. Consider adding proper aria attributes for better screen reader support.


164-168: Add proper ARIA label for the visibility toggle button.

The button for toggling balance visibility doesn't have a descriptive aria-label, making it unclear for screen reader users what the button does.

🧹 Nitpick comments (5)
src/app/(mobile-ui)/home/page.tsx (5)

42-53: Consider adding wallet state dependency to rewards balance fetch.

The useEffect only runs once on component mount due to having only getRewardWalletBalance in the dependency array. If the wallet changes during the component's lifecycle, the rewards balance won't automatically update.

useEffect(() => {
    const fetchRewardsBalance = async () => {
        try {
            const balance = await getRewardWalletBalance()
            setRewardsBalance(balance)
        } catch (error) {
            console.error('Failed to fetch rewards balance:', error)
        }
    }

    fetchRewardsBalance()
-}, [getRewardWalletBalance])
+}, [getRewardWalletBalance, peanutWalletDetails?.address])

186-205: Optimize icon rendering with memoization.

The renderIcon function creates a new DOM structure on each render. Consider memoizing this function or extracting it to a separate component for better performance.

// Inside ActionButton component
+ const iconMap = useMemo(() => ({
+   'send': <Icon name="arrow-up-right" size={8} fill="currentColor" />,
+   'withdraw': <Icon name="arrow-down" size={8} fill="currentColor" />,
+   'add': <Icon name="arrow-up" size={8} fill="currentColor" />,
+   'request': <Icon name="arrow-down-left" size={8} fill="currentColor" />
+ }), []);

  const renderIcon = (): React.ReactNode => {
    return (
      <div className="flex size-5 items-center justify-center">
-        {(() => {
-            switch (action) {
-                case 'send':
-                    return <Icon name="arrow-up-right" size={8} fill="currentColor" />
-                case 'withdraw':
-                    return <Icon name="arrow-down" size={8} fill="currentColor" />
-                case 'add':
-                    return <Icon name="arrow-up" size={8} fill="currentColor" />
-                case 'request':
-                    return <Icon name="arrow-down-left" size={8} fill="currentColor" />
-                default:
-                    return null
-            }
-        })()}
+        {iconMap[action] || null}
      </div>
    )
  }

172-223: Add fallback for default case in action icon switch.

The switch statement returns null for the default case, which could lead to an empty icon container if an unexpected action type is provided.

switch (action) {
    case 'send':
        return <Icon name="arrow-up-right" size={8} fill="currentColor" />
    case 'withdraw':
        return <Icon name="arrow-down" size={8} fill="currentColor" />
    case 'add':
        return <Icon name="arrow-up" size={8} fill="currentColor" />
    case 'request':
        return <Icon name="arrow-down-left" size={8} fill="currentColor" />
    default:
-        return null
+        console.warn(`Unknown action type: ${action}`)
+        return <Icon name="question-mark" size={8} fill="currentColor" />
}

136-259: Consider moving component definitions to separate files.

The file contains multiple component definitions (WalletBalance, ActionButton, ActionButtonGroup, RewardsCard) which makes it lengthy and harder to maintain. Consider moving these components to separate files in appropriate directories.

These components could be organized like:

  • src/components/Home/WalletBalance.tsx
  • src/components/Home/ActionButton.tsx
  • src/components/Home/ActionButtonGroup.tsx
  • src/components/Home/RewardsCard.tsx

This would improve code organization, testability, and maintainability.


229-259: Add loading state for RewardsCard.

RewardsCard returns null if balance is undefined, but doesn't show a loading state while the balance is being fetched, which could lead to UI flickering if the balance loads after the initial render.

function RewardsCard({ balance }: { balance: string | undefined }) {
+   // Show skeleton loader while balance is undefined (loading)
+   if (balance === undefined) {
+       return (
+           <div className="mt-6 space-y-3">
+               <h2 className="font-bold">Rewards</h2>
+               <Card position="single">
+                   <div className="animate-pulse flex w-full items-center justify-between">
+                       <div className="h-8 w-24 bg-gray-200 rounded"></div>
+                       <div className="h-4 w-12 bg-gray-200 rounded"></div>
+                   </div>
+               </Card>
+           </div>
+       );
+   }

    if (!balance || balance === '0') return null

    return (
        // Existing code...
    )
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7d08f04 and 934ee26.

📒 Files selected for processing (7)
  • src/app/(mobile-ui)/home/page.tsx (2 hunks)
  • src/app/(mobile-ui)/profile/page.tsx (2 hunks)
  • src/components/0_Bruddle/PageContainer.tsx (1 hunks)
  • src/components/Global/BottomDrawer/index.tsx (3 hunks)
  • src/components/Global/WalletNavigation/index.tsx (3 hunks)
  • src/components/Home/HomeHistory.tsx (2 hunks)
  • tailwind.config.js (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/components/0_Bruddle/PageContainer.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/app/(mobile-ui)/profile/page.tsx
  • src/components/Home/HomeHistory.tsx
  • src/components/Global/BottomDrawer/index.tsx
  • src/components/Global/WalletNavigation/index.tsx
  • tailwind.config.js

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: 1

♻️ Duplicate comments (1)
src/components/Profile/ProfileHeader.tsx (1)

58-78: Remove unnecessary Fragment

The Fragment wrapper is redundant since it contains only one child element.

 {isDrawerOpen && (
-    <>
-        <BottomDrawer
-            initialPosition="collapsed"
-            handleTitle={'Your Peanut profile is public'}
-            handleSubtitle="Share it to receive payments!"
-            collapsedHeight={80}
-            expandedHeight={90}
-            isOpen={isDrawerOpen}
-            onClose={() => setIsDrawerOpen(false)}
-        >
-            <div className="space-y-6">
-                <QRCodeWrapper url={profileUrl} />
-                <Divider className="text-gray-500" text="or" />
-                <ShareButton url={profileUrl} title="Share your profile">
-                    Your Peanut profile is public
-                </ShareButton>
-            </div>
-        </BottomDrawer>
-    </>
+    <BottomDrawer
+        initialPosition="collapsed"
+        handleTitle={'Your Peanut profile is public'}
+        handleSubtitle="Share it to receive payments!"
+        collapsedHeight={80}
+        expandedHeight={90}
+        isOpen={isDrawerOpen}
+        onClose={() => setIsDrawerOpen(false)}
+    >
+        <div className="space-y-6">
+            <QRCodeWrapper url={profileUrl} />
+            <Divider className="text-gray-500" text="or" />
+            <ShareButton url={profileUrl} title="Share your profile">
+                Your Peanut profile is public
+            </ShareButton>
+        </div>
+    </BottomDrawer>
 )}
🧰 Tools
🪛 Biome (1.9.4)

[error] 59-77: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

🧹 Nitpick comments (3)
src/components/Profile/ProfileHeader.tsx (3)

49-52: Add visual feedback for clipboard operations

When copying the profile URL to clipboard, consider providing user feedback (like a toast notification) to confirm the copy was successful. Also, the clipboard API might not be supported in all browsers, so error handling would be beneficial.

 onClick={() => {
-    navigator.clipboard.writeText(profileUrl)
-    setIsDrawerOpen(true)
+    navigator.clipboard.writeText(profileUrl)
+        .then(() => {
+            // Show toast or feedback that URL was copied
+            // You could use an existing toast notification system
+        })
+        .catch(err => {
+            console.error('Failed to copy text: ', err);
+        })
+        .finally(() => {
+            setIsDrawerOpen(true);
+        });
 }}

22-22: Consider using a constant for the domain

The domain "peanut.me" is hardcoded. Consider extracting it to a constant or environment variable to make it easier to change in the future.

+const PEANUT_DOMAIN = 'peanut.me';
+
 const ProfileHeader: React.FC<ProfileHeaderProps> = ({ name, username, initials, isVerified = false, className }) => {
     const [isDrawerOpen, setIsDrawerOpen] = useState(false)
 
-    const profileUrl = `peanut.me/${username}`
+    const profileUrl = `${PEANUT_DOMAIN}/${username}`

24-80: Consider accessibility improvements

The component would benefit from some accessibility improvements:

  1. Add aria-label to the button for screen readers
  2. Ensure proper contrast ratio for all text elements
  3. Consider keyboard navigation for the drawer interaction
🧰 Tools
🪛 Biome (1.9.4)

[error] 59-77: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 934ee26 and 9d94faa.

📒 Files selected for processing (1)
  • src/components/Profile/ProfileHeader.tsx (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/components/Profile/ProfileHeader.tsx

[error] 59-77: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (4)
src/components/Profile/ProfileHeader.tsx (4)

1-9: Imports are well-organized and appropriate

The imported components and utilities are relevant for the profile header functionality, including UI components, icons, and sharing capabilities.


11-17: Well-defined interface with clear property types

The interface properly defines the component's props with appropriate TypeScript types. The optional properties are correctly marked with the ? operator.


19-23: Good use of functional component with TypeScript

The component is correctly defined as a functional component with proper TypeScript annotations and default prop values.


24-57: Profile header UI implementation looks good

The UI layout with avatar, name, and username button is well-structured with appropriate Tailwind classes for styling and spacing.

onClose={() => setIsDrawerOpen(false)}
>
<div className="space-y-6">
<QRCodeWrapper url={profileUrl} />
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify QR code content format

Ensure the QR code contains a properly formatted URL with protocol (https://). The current implementation passes peanut.me/{username} without a protocol, which might not work as expected with some QR code scanners.


🏁 Script executed:

#!/bin/bash
# Check the implementation of QRCodeWrapper to see how it handles URLs without protocols
rg -A 10 "QRCodeWrapper" --type tsx --type jsx

Length of output: 74


To locate how QRCodeWrapper handles URLs, let’s search its definition across the repo:


🏁 Script executed:

#!/bin/bash
# 1) Find any file exporting or defining QRCodeWrapper
rg -l "QRCodeWrapper" .

# 2) Look for its implementation (function or const) and show surrounding lines
rg -nE "function QRCodeWrapper|const QRCodeWrapper" $(rg -l "QRCodeWrapper" .) -A 30

Length of output: 1054


Let’s inspect the QRCodeWrapper implementation to see how it handles missing protocols:


🏁 Script executed:

#!/bin/bash
# Dump the first 200 lines of the QRCodeWrapper component
sed -n '1,200p' src/components/Global/QRCodeWrapper/index.tsx

Length of output: 2000


Let’s verify how profileUrl is constructed in ProfileHeader to check if it includes the protocol:


🏁 Script executed:

#!/bin/bash
rg -n "profileUrl" src/components/Profile/ProfileHeader.tsx -A 5 -B 5

Length of output: 1759


Prefix the profile URL with https:// for QR codes

Without a protocol, QR scanners may treat peanut.me/username as plain text rather than a link. Update the URL before passing it to QRCodeWrapper and other consumers.

• File: src/components/Profile/ProfileHeader.tsx
– Line 22: change

- const profileUrl = `peanut.me/${username}`
+ const profileUrl = `https://peanut.me/${username}`

• Also ensure the copy-to-clipboard and <ShareButton> use the updated profileUrl.

📝 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
<QRCodeWrapper url={profileUrl} />
// around line 22 in ProfileHeader.tsx
- const profileUrl = `peanut.me/${username}`
+ const profileUrl = `https://peanut.me/${username}`
// later usages will now pick up the full URL:
<QRCodeWrapper url={profileUrl} />
<ShareButton url={profileUrl} title="Share your profile">
Your Peanut profile is public
</ShareButton>

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: 5

♻️ Duplicate comments (4)
src/components/Profile/ProfileHeader.tsx (2)

49-69: Remove unnecessary Fragment.

The Fragment wrapper is redundant since it contains only one child element.

{isDrawerOpen && (
-    <>
-        <BottomDrawer
-            initialPosition="collapsed"
-            handleTitle={'Your Peanut profile is public'}
-            handleSubtitle="Share it to receive payments!"
-            collapsedHeight={80}
-            expandedHeight={90}
-            isOpen={isDrawerOpen}
-            onClose={() => setIsDrawerOpen(false)}
-        >
-            <div className="space-y-6">
-                <QRCodeWrapper url={profileUrl} />
-                <Divider className="text-gray-500" text="or" />
-                <ShareButton url={profileUrl} title="Share your profile">
-                    Your Peanut profile is public
-                </ShareButton>
-            </div>
-        </BottomDrawer>
-    </>
+    <BottomDrawer
+        initialPosition="collapsed"
+        handleTitle={'Your Peanut profile is public'}
+        handleSubtitle="Share it to receive payments!"
+        collapsedHeight={80}
+        expandedHeight={90}
+        isOpen={isDrawerOpen}
+        onClose={() => setIsDrawerOpen(false)}
+    >
+        <div className="space-y-6">
+            <QRCodeWrapper url={profileUrl} />
+            <Divider className="text-gray-500" text="or" />
+            <ShareButton url={profileUrl} title="Share your profile">
+                Your Peanut profile is public
+            </ShareButton>
+        </div>
+    </BottomDrawer>
)}
🧰 Tools
🪛 Biome (1.9.4)

[error] 50-68: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


23-23: ⚠️ Potential issue

Prefix the profile URL with https:// for QR codes.

Without a protocol, QR scanners may treat peanut.me/username as plain text rather than a link. This could lead to a suboptimal user experience when scanning the QR code.

- const profileUrl = `peanut.me/${username}`
+ const profileUrl = `https://peanut.me/${username}`
src/app/(mobile-ui)/home/page.tsx (2)

115-121: Improve screen reader accessibility for masked balance.

When the balance is hidden, the current implementation uses asterisks which aren't properly conveyed to screen readers. Add appropriate ARIA attributes to improve accessibility.

if (isBalanceHidden) {
    return (
-       <span className="inline-flex items-center">
-           <span className="relative top-1">* * * *</span>
+       <span className="inline-flex items-center" aria-label="Balance hidden">
+           <span className="relative top-1" aria-hidden="true">* * * *</span>
        </span>
    )
}

133-137: Add proper ARIA label for the visibility toggle button.

The button for toggling balance visibility needs a descriptive aria-label to clearly communicate its purpose to screen reader users.

-<button onClick={onToggleBalanceVisibility}>
+<button 
+   onClick={onToggleBalanceVisibility}
+   aria-label={isBalanceHidden ? "Show balance" : "Hide balance"}
+>
    <Icon name={isBalanceHidden ? 'eye-slash' : 'eye'} className={'h-6 w-6'} fill={'black'} />
</button>
🧹 Nitpick comments (8)
src/components/Profile/AvatarWithBadge.tsx (3)

19-24: Inconsistent size definitions for 'extra-small' and 'small'.

Both 'extra-small' and 'small' have the same dimensions (h-8 w-8) but different text sizes. This might be confusing for users of this component.

Consider either:

  1. Making the dimension differences more apparent
  2. Adding a comment explaining the intentional size similarity
const sizeClasses = {
-    'extra-small': 'h-8 w-8 text-sm',
-    small: 'h-8 w-8 text-lg',
+    'extra-small': 'h-6 w-6 text-sm',
+    small: 'h-8 w-8 text-lg',
    medium: 'h-16 w-16 text-2xl',
    large: 'h-24 w-24 text-3xl',
}

5-11: Consider using a numeric type for size categories.

Using string literals for size categories is clear, but considering you're already importing from a shared type (AchievementsBadgeSize), you might want to reuse that type or create a shared type for consistency across components.


36-36: Prop naming could be more descriptive.

If the badge specifically indicates verification (rather than general achievements), consider renaming the prop to better reflect its purpose, aligning it with the component that's being used.

- {isVerified && <AchievementsBadge size={achievementsBadgeSize} />}
+ {isVerified && <AchievementsBadge size={achievementsBadgeSize} />} {/* Verification badge */}
src/components/Profile/ProfileHeader.tsx (2)

22-22: Rename isDrawerOpen for better clarity.

The variable name isDrawerOpen could be more descriptive, such as isQRDrawerOpen or isProfileShareDrawerOpen, to better match its purpose as described in the component's comments.

- const [isDrawerOpen, setIsDrawerOpen] = useState(false)
+ const [isQRShareDrawerOpen, setIsQRShareDrawerOpen] = useState(false)

// And update all instances, like:
- setIsDrawerOpen(true)
+ setIsQRShareDrawerOpen(true)

Also applies to: 41-41


35-47: Add feedback for copy to clipboard action.

When the user clicks to copy the profile URL, there's no visual feedback to indicate the copy was successful. Consider adding a toast notification or some visual indication.

onClick={() => {
    navigator.clipboard.writeText(profileUrl)
+   // Add toast notification or state update to provide feedback
+   // Example: toast.success('Profile URL copied to clipboard!');
    setIsDrawerOpen(true)
}}
src/app/(mobile-ui)/home/page.tsx (3)

98-98: Consider dynamic transaction data for HomeHistory.

The component is included but appears to be using static data. Implementing real transaction data would provide a more authentic user experience.

HomeHistory should ideally display real user transactions rather than static examples. If real transaction data isn't available yet, consider using varied mock data to better simulate real-world usage.


252-255: Enhance profile link accessibility.

The link to the profile page lacks an aria-label, which would improve accessibility for screen reader users.

-<Link href={`/profile`} className="flex items-center gap-1.5">
+<Link href={`/profile`} className="flex items-center gap-1.5" aria-label={`View ${username}'s profile`}>
    <AvatarWithBadge size="extra-small" initials={initals} isVerified achievementsBadgeSize="extra-small" />
    <div className="text-sm font-bold">{username}</div>
</Link>

213-219: Make image alt text more descriptive.

The current alt text is basic. A more descriptive alt text would improve accessibility and provide better context.

<Image
-   src={PeanutArmHoldingBeer}
-   alt="Peanut arm holding beer"
+   src={PeanutArmHoldingBeer}
+   alt="Peanut mascot arm holding a beer - rewards icon"
    className={twMerge('size-6 object-contain')}
    width={24}
    height={24}
/>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9d94faa and 2de7ad3.

📒 Files selected for processing (10)
  • src/app/(mobile-ui)/home/page.tsx (2 hunks)
  • src/components/AddFunds/index.tsx (2 hunks)
  • src/components/Global/Badges/AchievementsBadge.tsx (1 hunks)
  • src/components/Global/CopyToClipboard/index.tsx (2 hunks)
  • src/components/Global/DirectSendQR/utils.ts (2 hunks)
  • src/components/Global/Icons/Icon.tsx (1 hunks)
  • src/components/Global/Icons/copy.tsx (1 hunks)
  • src/components/Home/HomeHistory.tsx (3 hunks)
  • src/components/Profile/AvatarWithBadge.tsx (1 hunks)
  • src/components/Profile/ProfileHeader.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/components/Global/DirectSendQR/utils.ts
  • src/components/Global/Icons/copy.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/Global/Icons/Icon.tsx
  • src/components/Global/Badges/AchievementsBadge.tsx
  • src/components/Home/HomeHistory.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/components/AddFunds/index.tsx (2)
src/components/0_Bruddle/Button.tsx (1)
  • Button (63-94)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (84-93)
src/components/Profile/AvatarWithBadge.tsx (1)
src/components/Global/Badges/AchievementsBadge.tsx (1)
  • AchievementsBadgeSize (4-4)
🪛 Biome (1.9.4)
src/components/Profile/ProfileHeader.tsx

[error] 50-68: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (4)
src/components/AddFunds/index.tsx (2)

19-19: Good addition of the optional cta prop.

This enhancement makes the component more flexible by allowing customization of the call-to-action element while maintaining the existing functionality.


50-59: Clean and effective conditional rendering.

The implementation nicely preserves the default UI when no custom CTA is provided while allowing full customization when needed.

src/components/Global/CopyToClipboard/index.tsx (2)

3-3: Updated import path for Icon component.

The import path has been updated to use the new centralized icon system.


25-26: Icon name updated and dynamic sizing implemented.

The icon name has been changed to match the new icon system, and dynamic sizing has been implemented using the new iconSize prop.

textToCopy: string
fill?: string
className?: string
iconSize?: '2' | '4' | '6' | '8'
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Beware of dynamic Tailwind classes with template literals.

Using string literals for iconSize and then interpolating them in Tailwind classes (h-${iconSize}) might not work as expected with Tailwind's purge process during build time, as Tailwind needs to see the complete class names statically.


🏁 Script executed:

#!/bin/bash
# Check if tailwind.config.js has safelist configured for these dynamic classes
grep -A 10 "safelist" $(find . -name "tailwind.config.js") 2>/dev/null

# Check for any other dynamic class usage patterns
rg -A 2 -B 2 '\$\{.*\}' --type tsx --type jsx | grep -E 'class|className'

Length of output: 181


🏁 Script executed:

#!/bin/bash
# Show tailwind.config.js to inspect any safelist or jit/content configuration
sed -n '1,200p' tailwind.config.js

# Check explicitly for a safelist configuration
grep -n "safelist" tailwind.config.js || echo ">> No safelist block found"

# Check for Tailwind's content (or purge) entries to ensure .tsx files are included
grep -n -E "content|purge" tailwind.config.js

# Search for the dynamic iconSize template literal in TSX/JSX files
rg 'h-\$\{iconSize\}' -g '*.tsx' -g '*.jsx' -n

Length of output: 6856


Ensure Tailwind generates all h‑*/w‑* classes for your iconSize values
Tailwind’s content scanner only picks up literal class names at build time. Interpolating h-${iconSize}/w-${iconSize} won’t produce the actual h-2, h-4, etc. classes, so they may be dropped in production.

Possible fixes:

  • Add a safelist in tailwind.config.js for all needed variants, e.g.:
    // tailwind.config.js
    module.exports = {
      // …
      safelist: [
        'h-2','h-4','h-6','h-8',
        'w-2','w-4','w-6','w-8',
      ],
    }
  • Or replace the template literal with a static lookup map in src/components/Global/CopyToClipboard/index.tsx, e.g.:
    const SIZE_CLASSES = {
      2: 'h-2 w-2',
      4: 'h-4 w-4',
      6: 'h-6 w-6',
      8: 'h-8 w-8',
    } as const;
    
    // …
    className={twMerge(SIZE_CLASSES[iconSize], className)}

Files to update:

  • tailwind.config.js → add safelist block (or JIT content options)
  • src/components/Global/CopyToClipboard/index.tsx → swap dynamic template for static class mapping

<AvatarWithBadge size="extra-small" initials={initals} isVerified achievementsBadgeSize="extra-small" />
<div className="text-sm font-bold">{username}</div>
</Link>
<CopyToClipboard textToCopy={`${BASE_URL}/${username}`} fill="black" iconSize={'4'} />
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

Improve clipboard copy functionality accessibility.

The CopyToClipboard component needs better accessibility attributes to clearly communicate its purpose to screen reader users.

-<CopyToClipboard textToCopy={`${BASE_URL}/${username}`} fill="black" iconSize={'4'} />
+<CopyToClipboard 
+   textToCopy={`${BASE_URL}/${username}`} 
+   fill="black" 
+   iconSize={'4'} 
+   ariaLabel={`Copy ${username}'s profile link to clipboard`}
+/>

This assumes that the CopyToClipboard component accepts an ariaLabel prop. If it doesn't, consider modifying that component to support this accessibility enhancement.

📝 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
<CopyToClipboard textToCopy={`${BASE_URL}/${username}`} fill="black" iconSize={'4'} />
<CopyToClipboard
textToCopy={`${BASE_URL}/${username}`}
fill="black"
iconSize={'4'}
ariaLabel={`Copy ${username}'s profile link to clipboard`}
/>

Comment on lines +70 to +101
<UserHeader username={username!} fullName={userFullName} />
<div className="space-y-4">
<ActionButtonGroup>
<AddFunds cta={<ActionButton label="Add money" action="add" size="small" />} />
<ActionButtonWithHref label="Withdraw" action="withdraw" href="/cashout" size="small" />
</ActionButtonGroup>

<WalletBalance
balance={peanutWalletDetails?.balance ?? BigInt(0)}
isBalanceHidden={isBalanceHidden}
onToggleBalanceVisibility={handleToggleBalanceVisibility}
/>

<ActionButtonGroup>
<ActionButtonWithHref label="Send" action="send" href="/send" variant="purple" size="large" />
<ActionButtonWithHref
label="Request"
action="request"
href="/request/create"
variant="purple"
size="large"
/>
</ActionButtonGroup>
</div>

if (targetIndex < wallets.length) {
const targetWallet = wallets[targetIndex]
dispatch(walletActions.setFocusedWallet(targetWallet))
{/* Rewards Card - only shows if balance is non-zero */}
<RewardsCard balance={rewardsBalance} />

// check wallet type and ID
const isValidPeanutWallet =
targetWallet.id.startsWith('peanut-wallet') &&
targetWallet.walletProviderType === WalletProviderType.PEANUT
const isValidRewardsWallet =
targetWallet.id === 'pinta-wallet' && targetWallet.walletProviderType === WalletProviderType.REWARDS
const isValidExternalWallet =
targetWallet.walletProviderType === WalletProviderType.BYOW && isWalletConnected(targetWallet)
<HomeHistory />
<RewardsModal />
</div>
</PageContainer>
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

Consider adding error handling for rewards balance fetching.

The code fetches the rewards balance but does not handle the UI implications of a failed fetch beyond logging to console.

Adding appropriate error handling would improve user experience when rewards data can't be loaded. Consider adding a retry mechanism or fallback UI state.

// In the useEffect hook (around line 50)
useEffect(() => {
    const fetchRewardsBalance = async () => {
        try {
            const balance = await getRewardWalletBalance()
            setRewardsBalance(balance)
        } catch (error) {
            console.error('Failed to fetch rewards balance:', error)
+           // Set an error state or use a default value
+           setRewardsBalance('0')
        }
    }

    fetchRewardsBalance()
}, [getRewardWalletBalance])

// Then modify the RewardsCard component to handle errors
function RewardsCard({ balance }: { balance: string | undefined }) {
    if (!balance || balance === '0') return null;

    return (
        // ... existing code
    )
}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +232 to +248
const initals = useMemo(() => {
if (fullName) {
return fullName
.split(' ')
.map((part) => part[0])
.join('')
.toUpperCase()
.substring(0, 2)
}

return username
.split(' ')
.map((part) => part[0])
.join('')
.toUpperCase()
.substring(0, 2)
}, [username])
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

Handle edge cases in initials calculation.

The initials calculation logic doesn't properly handle edge cases where fullName might be empty or contain no valid characters.

function UserHeader({ username, fullName }: { username: string; fullName?: string }) {
    const initals = useMemo(() => {
        if (fullName) {
-           return fullName
-               .split(' ')
-               .map((part) => part[0])
-               .join('')
-               .toUpperCase()
-               .substring(0, 2)
+           const parts = fullName.split(' ').filter(part => part.length > 0);
+           if (parts.length > 0) {
+               return parts
+                   .map((part) => part[0] || '')
+                   .join('')
+                   .toUpperCase()
+                   .substring(0, 2);
+           }
        }

-       return username
-           .split(' ')
-           .map((part) => part[0])
-           .join('')
-           .toUpperCase()
-           .substring(0, 2)
+       const parts = username.split(' ').filter(part => part.length > 0);
+       if (parts.length > 0) {
+           return parts
+               .map((part) => part[0] || '')
+               .join('')
+               .toUpperCase()
+               .substring(0, 2);
+       }
+       return 'UN'; // Default fallback if no valid initials can be generated
    }, [username, fullName])

Comment on lines +157 to +193
function ActionButton({ label, action, variant = 'primary-soft', size = 'small' }: Omit<ActionButtonProps, 'href'>) {
// get icon based on action type
const renderIcon = (): React.ReactNode => {
return (
<div className="flex size-5 items-center justify-center">
{(() => {
switch (action) {
case 'send':
return <Icon name="arrow-up-right" size={8} fill="currentColor" />
case 'withdraw':
return <Icon name="arrow-down" size={8} fill="currentColor" />
case 'add':
return <Icon name="arrow-up" size={8} fill="currentColor" />
case 'request':
return <Icon name="arrow-down-left" size={8} fill="currentColor" />
default:
return null
}
})()}
</div>
)
}

return (
<div className="h-full w-full">
<div className="flex h-full w-full flex-row justify-center overflow-hidden py-6 md:py-0">
<div className="flex w-[100%] flex-col gap-4 sm:w-[90%] md:w-[70%] lg:w-[50%]">
<div className="space-y-4 px-6">
<div className="flex items-center justify-between">
<div className="flex w-full items-center justify-between md:hidden">
<Image src={PEANUT_LOGO_BLACK} alt="Peanut Logo" className="w-20" />
<LogoutButton />
</div>
</div>
<ProfileSection />
</div>
<Button
variant={variant}
className={twMerge(
'flex cursor-pointer items-center justify-center rounded-full',
size === 'large' ? 'min-w-[145px] px-6 py-3' : 'min-w-[120px] px-4 py-2'
)}
shadowSize="4"
size={size}
>
{renderIcon()}
<span className={twMerge('font-bold', size === 'small' ? 'text-xs' : 'text-sm')}>{label}</span>
</Button>
)
}
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

Improve accessibility for action button icons.

The action icons in the buttons need proper ARIA attributes to ensure they're correctly interpreted by assistive technologies.

function ActionButton({ label, action, variant = 'primary-soft', size = 'small' }: Omit<ActionButtonProps, 'href'>) {
    // get icon based on action type
    const renderIcon = (): React.ReactNode => {
        return (
-           <div className="flex size-5 items-center justify-center">
+           <div className="flex size-5 items-center justify-center" aria-hidden="true">
                {(() => {
                    switch (action) {
                        case 'send':
                            return <Icon name="arrow-up-right" size={8} fill="currentColor" />
                        case 'withdraw':
                            return <Icon name="arrow-down" size={8} fill="currentColor" />
                        case 'add':
                            return <Icon name="arrow-up" size={8} fill="currentColor" />
                        case 'request':
                            return <Icon name="arrow-down-left" size={8} fill="currentColor" />
                        default:
                            return null
                    }
                })()}
            </div>
        )
    }
📝 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
function ActionButton({ label, action, variant = 'primary-soft', size = 'small' }: Omit<ActionButtonProps, 'href'>) {
// get icon based on action type
const renderIcon = (): React.ReactNode => {
return (
<div className="flex size-5 items-center justify-center">
{(() => {
switch (action) {
case 'send':
return <Icon name="arrow-up-right" size={8} fill="currentColor" />
case 'withdraw':
return <Icon name="arrow-down" size={8} fill="currentColor" />
case 'add':
return <Icon name="arrow-up" size={8} fill="currentColor" />
case 'request':
return <Icon name="arrow-down-left" size={8} fill="currentColor" />
default:
return null
}
})()}
</div>
)
}
return (
<div className="h-full w-full">
<div className="flex h-full w-full flex-row justify-center overflow-hidden py-6 md:py-0">
<div className="flex w-[100%] flex-col gap-4 sm:w-[90%] md:w-[70%] lg:w-[50%]">
<div className="space-y-4 px-6">
<div className="flex items-center justify-between">
<div className="flex w-full items-center justify-between md:hidden">
<Image src={PEANUT_LOGO_BLACK} alt="Peanut Logo" className="w-20" />
<LogoutButton />
</div>
</div>
<ProfileSection />
</div>
<Button
variant={variant}
className={twMerge(
'flex cursor-pointer items-center justify-center rounded-full',
size === 'large' ? 'min-w-[145px] px-6 py-3' : 'min-w-[120px] px-4 py-2'
)}
shadowSize="4"
size={size}
>
{renderIcon()}
<span className={twMerge('font-bold', size === 'small' ? 'text-xs' : 'text-sm')}>{label}</span>
</Button>
)
}
function ActionButton({ label, action, variant = 'primary-soft', size = 'small' }: Omit<ActionButtonProps, 'href'>) {
// get icon based on action type
const renderIcon = (): React.ReactNode => {
return (
<div className="flex size-5 items-center justify-center" aria-hidden="true">
{(() => {
switch (action) {
case 'send':
return <Icon name="arrow-up-right" size={8} fill="currentColor" />
case 'withdraw':
return <Icon name="arrow-down" size={8} fill="currentColor" />
case 'add':
return <Icon name="arrow-up" size={8} fill="currentColor" />
case 'request':
return <Icon name="arrow-down-left" size={8} fill="currentColor" />
default:
return null
}
})()}
</div>
)
}
return (
<Button
variant={variant}
className={twMerge(
'flex cursor-pointer items-center justify-center rounded-full',
size === 'large' ? 'min-w-[145px] px-6 py-3' : 'min-w-[120px] px-4 py-2'
)}
shadowSize="4"
size={size}
>
{renderIcon()}
<span className={twMerge('font-bold', size === 'small' ? 'text-xs' : 'text-sm')}>
{label}
</span>
</Button>
)
}

Copy link
Contributor

@jjramirezn jjramirezn left a comment

Choose a reason for hiding this comment

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

Lets gooooo

@kushagrasarathe kushagrasarathe merged commit b4f68aa into peanut-wallet-dev Apr 23, 2025
5 checks passed
@Hugo0 Hugo0 deleted the feat/contacts-ui branch July 3, 2025 18:24
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