-
Notifications
You must be signed in to change notification settings - Fork 13
feat: new home page ui #802
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis 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
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)
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
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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 addingaria-hidden="true"
if the icon is purely decorative, or allow passing atitle
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 addingaria-hidden="true"
if the icon is decorative, or support atitle
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 addingaria-hidden="true"
for decorative use, or support atitle
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 addingaria-hidden="true"
if decorative, or allow atitle
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 addingaria-hidden="true"
for decorative use, or support atitle
prop for screen readers.src/components/Global/Icons/wallet.tsx (1)
3-12
: Consider removing unnecessary fragment wrapperThe 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 documentationAdding 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 typeWhile 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 optimizationSince 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 logicThe 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 outcomesIn 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 initialsIn 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 statementThe
getActionIcon
function doesn't have a default case. While the current implementation covers all values in theTransactionType
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, ifpeanutWalletDetails
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
📒 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 propertiesThis 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 organizedAll icon components are imported individually, which is good for maintainability and clarity.
17-33
: Type-safe icon name definition is a good practiceUsing 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 interfaceThe 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 ofTransactionCard
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.
UsingpeanutWalletDetails?.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.
There was a problem hiding this 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 issueInvalid 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
orrole
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 ingetIconBackgroundColor
.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 theas
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
📒 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
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> | ||
) | ||
} |
There was a problem hiding this comment.
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.
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> | |
) | |
} |
<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> |
There was a problem hiding this comment.
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
- Background:
-
Pending Badge:
- Background:
bg-secondary-4
→#FFF4CC
- Text:
text-secondary-1
→#FFC900
- Background:
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.
src/app/(mobile-ui)/home/page.tsx
Outdated
<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> |
There was a problem hiding this comment.
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';
}
|
||
<button onClick={onToggleBalanceVisibility}> | ||
<Icon name={isBalanceHidden ? 'eye-slash' : 'eye'} className={'h-6 w-6'} fill={'black'} /> | ||
</button> | ||
</div> |
There was a problem hiding this comment.
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.
<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> |
return ( | ||
<span className="inline-flex items-center"> | ||
<span className="relative top-1">* * * *</span> | ||
</span> | ||
) | ||
} |
There was a problem hiding this comment.
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.
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> | |
) | |
} |
There was a problem hiding this 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 heredisabled
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 iconScreen‑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 addaria-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
📒 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 newhideLabel
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 likeStatusBadge
andCard
.
16-23
: Well-structured component with good defaults.The component has sensible defaults for the
position
andcomingSoon
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 anhref
. 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
andQRBottomDrawer
.
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.
const fullName = user?.user.full_name || user?.user?.username || 'Anonymous User' | ||
const username = user?.user.username || 'anonymous' | ||
const initials = getInitials(fullName) |
There was a problem hiding this comment.
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.
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) |
const { setLoadingState, isLoading } = useContext(loadingStateContext) | ||
const { logoutUser, user } = useAuth() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 |
const getInitials = (name: string) => { | ||
return name | ||
.split(' ') | ||
.map((part) => part[0]) | ||
.join('') | ||
.toUpperCase() | ||
.substring(0, 2) | ||
} |
There was a problem hiding this comment.
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)
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() | ||
|
There was a problem hiding this comment.
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.
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]) | |
// … | |
} |
There was a problem hiding this 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 subtitleWhile 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 operationsThe 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 accessibilityAdd 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
📒 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 propAdding the optional subtitle to the interface will enhance the flexibility of the drawer component.
27-27
: LGTM - Appropriate default valueThe 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 propThe implementation leverages the newly added subtitle capability in the BottomDrawer component effectively.
<> | ||
<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> | ||
</> |
There was a problem hiding this comment.
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.
<> | |
<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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left some comments
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this 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
📒 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
There was a problem hiding this 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 FragmentThe 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 operationsWhen 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 domainThe 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 improvementsThe component would benefit from some accessibility improvements:
- Add
aria-label
to the button for screen readers- Ensure proper contrast ratio for all text elements
- 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
📒 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 appropriateThe 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 typesThe 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 TypeScriptThe component is correctly defined as a functional component with proper TypeScript annotations and default prop values.
24-57
: Profile header UI implementation looks goodThe 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} /> |
There was a problem hiding this comment.
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.
<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> |
There was a problem hiding this 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 issuePrefix 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:
- Making the dimension differences more apparent
- 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
: RenameisDrawerOpen
for better clarity.The variable name
isDrawerOpen
could be more descriptive, such asisQRDrawerOpen
orisProfileShareDrawerOpen
, 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
📒 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 optionalcta
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' |
There was a problem hiding this comment.
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'} /> |
There was a problem hiding this comment.
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.
<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`} | |
/> |
<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> |
There was a problem hiding this comment.
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.
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]) |
There was a problem hiding this comment.
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])
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> | ||
) | ||
} |
There was a problem hiding this comment.
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.
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> | |
) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets gooooo
contributes to TASK-10527
Summary by CodeRabbit