Repository: peanutprotocol/peanut-ui
Pull request #831: feat: txns history details drawer 
Author: login: kushagrasarathe
Created: 2025-05-06T10:13:40Z | Merged: 2025-05-11T13:59:15Z

- contributes to TASK-10282
- also contributes to TASK-10283

## Logic overview
<img width="734" alt="image" src="https://github.com/user-attachments/assets/281be4bf-38dc-4335-821c-f884043f9891" />



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a transaction details drawer with a bottom sheet UI, providing detailed transaction information and QR code actions.
  - Added new transaction card component for displaying transaction history entries with contextual avatars and icons.
  - Implemented dynamic avatar badge rendering with icons, initials, and color logic based on transaction type and user.
  - Added utilities for grouping and formatting transaction history by date.

- **Enhancements**
  - Refactored transaction history and home history lists to use the new transaction card and grouping logic for improved clarity and consistency.
  - Status badges now support a new "processing" state and improved color styling.
  - Avatar components now support dynamic icons, background colors, and text/icon color customization.

- **Bug Fixes**
  - Fixed full-screen loading indicator positioning for improved visual consistency.

- **Chores**
  - Extended Tailwind CSS color palette for richer UI theming.
  - Removed deprecated components and utilities, including the old list item view and payment history components.

- **Refactor**
  - Simplified and streamlined transaction history rendering, removing complex nested mapping and manual formatting.
  - Modularized transaction data transformation and avatar color logic for maintainability.

- **Documentation**
  - Added comments and clarified prop types for new and updated components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/src/app/(mobile-ui)/history/page.tsx b/src/app/(mobile-ui)/history/page.tsx
index 2644cc940..a3ac07252 100644
--- a/src/app/(mobile-ui)/history/page.tsx
+++ b/src/app/(mobile-ui)/history/page.tsx
@@ -1,20 +1,28 @@
 'use client'
 
-import AddressLink from '@/components/Global/AddressLink'
+import { CardPosition } from '@/components/Global/Card'
 import NoDataEmptyState from '@/components/Global/EmptyStates/NoDataEmptyState'
-import { ListItemView } from '@/components/Global/ListItemView'
 import NavHeader from '@/components/Global/NavHeader'
 import PeanutLoading from '@/components/Global/PeanutLoading'
-import { TransactionBadge } from '@/components/Global/TransactionBadge'
-import { formatDate, getChainLogo, getHeaderTitle, getTokenLogo, getChainName } from '@/utils'
+import TransactionCard from '@/components/TransactionDetails/TransactionCard'
+import { mapTransactionDataForDrawer } from '@/components/TransactionDetails/transactionTransformer'
+import { useTransactionHistory } from '@/hooks/useTransactionHistory'
+import { useUserStore } from '@/redux/hooks'
+import { getHeaderTitle } from '@/utils'
+import { formatGroupHeaderDate, getDateGroup, getDateGroupKey } from '@/utils/dateGrouping.utils'
 import * as Sentry from '@sentry/nextjs'
 import { usePathname } from 'next/navigation'
-import { useEffect, useRef } from 'react'
-import { useTransactionHistory } from '@/hooks/useTransactionHistory'
+import React, { useEffect, useMemo, useRef } from 'react'
 
+/**
+ * displays the user's transaction history with infinite scrolling and date grouping.
+ */
 const HistoryPage = () => {
     const pathname = usePathname()
     const loaderRef = useRef<HTMLDivElement>(null)
+    const { user } = useUserStore()
+    const currentUserUsername = user?.user.username
+
     const {
         data: historyData,
         hasNextPage,
@@ -40,15 +48,20 @@ const HistoryPage = () => {
                 threshold: 0.1,
             }
         )
-
-        if (loaderRef.current) {
-            observer.observe(loaderRef.current)
+        const currentLoaderRef = loaderRef.current
+        if (currentLoaderRef) {
+            observer.observe(currentLoaderRef)
+        }
+        return () => {
+            if (currentLoaderRef) {
+                observer.unobserve(currentLoaderRef)
+            }
         }
-
-        return () => observer.disconnect()
     }, [hasNextPage, isFetchingNextPage, fetchNextPage])
 
-    if (isLoading && !historyData?.pages?.length) {
+    const allEntries = useMemo(() => historyData?.pages.flatMap((page) => page.entries) ?? [], [historyData])
+
+    if (isLoading && allEntries.length === 0) {
         return <PeanutLoading />
     }
 
@@ -58,60 +71,72 @@ const HistoryPage = () => {
         return <div className="w-full py-4 text-center">Error loading history: {error?.message}</div>
     }
 
-    if (!historyData || historyData.pages.length === 0) {
+    if (allEntries.length === 0) {
         return (
-            <div className="flex h-[80dvh] items-center justify-center">
-                <NoDataEmptyState animSize="lg" message="You haven't done any transactions" />
+            <div className="flex h-[80dvh] flex-col items-center justify-center">
+                <NavHeader title={getHeaderTitle(pathname)} />
+                <div className="flex flex-grow items-center justify-center">
+                    <NoDataEmptyState animSize="lg" message="You haven't done any transactions" />
+                </div>
             </div>
         )
     }
 
+    let lastGroupHeaderKey: string | null = null
+    const today = new Date()
+
     return (
         <div className="mx-auto w-full space-y-6 md:max-w-2xl md:space-y-3">
-            {!!historyData?.pages.length ? <NavHeader title={getHeaderTitle(pathname)} /> : null}
-            <div className="h-full w-full border-t border-n-1">
-                {!!historyData?.pages.length &&
-                    historyData?.pages.map((page, pageIndex) => (
-                        <div key={pageIndex}>
-                            {page.entries.map((item) => (
-                                <div key={item.uuid}>
-                                    <ListItemView
-                                        id={item.uuid}
-                                        variant="history"
-                                        primaryInfo={{
-                                            title: (
-                                                <div className="flex flex-col items-start gap-2 md:flex-row md:items-center ">
-                                                    <div className="font-bold">
-                                                        {item.type} {item.userRole}
-                                                    </div>
-                                                    <div className="flex flex-col items-end justify-end gap-2 text-end">
-                                                        <TransactionBadge status={item.status as string} />
-                                                    </div>
-                                                </div>
-                                            ),
-                                            subtitle: !!item.recipientAccount.identifier && (
-                                                <div
-                                                    className="text-xs text-gray-1"
-                                                    onClick={(e) => e.stopPropagation()}
-                                                >
-                                                    To: <AddressLink address={item.recipientAccount.identifier} />
-                                                </div>
-                                            ),
-                                        }}
-                                        secondaryInfo={{
-                                            mainText: item.extraData?.usdAmount,
-                                            subText: item.timestamp ? formatDate(new Date(item.timestamp)) : '',
-                                        }}
-                                        metadata={{
-                                            tokenLogo: getTokenLogo(item.tokenSymbol),
-                                            chainLogo: getChainLogo(getChainName(item.chainId) ?? ''),
-                                        }}
-                                        details={item}
-                                    />
+            <NavHeader title={getHeaderTitle(pathname)} />
+            <div className="h-full w-full">
+                {allEntries.map((item, index) => {
+                    const itemDate = new Date(item.timestamp)
+                    const group = getDateGroup(itemDate, today)
+                    const currentGroupHeaderKey = getDateGroupKey(itemDate, group)
+                    const showHeader = currentGroupHeaderKey !== lastGroupHeaderKey
+                    if (showHeader) {
+                        lastGroupHeaderKey = currentGroupHeaderKey
+                    }
+
+                    const { transactionDetails, transactionCardType } = mapTransactionDataForDrawer(
+                        item,
+                        currentUserUsername || ''
+                    )
+
+                    let position: CardPosition = 'middle'
+                    const isFirstOverall = index === 0
+                    const isLastOverall = index === allEntries.length - 1
+                    const isFirstInGroup = showHeader
+
+                    if (allEntries.length === 1) {
+                        position = 'single'
+                    } else if (isFirstInGroup && isLastOverall) {
+                        position = 'single'
+                    } else if (isFirstInGroup || isFirstOverall) {
+                        position = 'first'
+                    } else if (isLastOverall) {
+                        position = 'last'
+                    }
+
+                    return (
+                        <React.Fragment key={item.uuid}>
+                            {showHeader && (
+                                <div className="mb-2 mt-4 px-1 text-sm font-semibold capitalize">
+                                    {formatGroupHeaderDate(itemDate, group, today)}
                                 </div>
-                            ))}
-                        </div>
-                    ))}
+                            )}
+                            <TransactionCard
+                                type={transactionCardType}
+                                name={transactionDetails.userName}
+                                amount={transactionDetails.amount ? Number(transactionDetails.amount) : 0}
+                                status={transactionDetails.status}
+                                initials={transactionDetails.initials}
+                                transaction={transactionDetails}
+                                position={position}
+                            />
+                        </React.Fragment>
+                    )
+                })}
 
                 <div ref={loaderRef} className="w-full py-4">
                     {isFetchingNextPage && <div className="w-full text-center">Loading more...</div>}
diff --git a/src/app/(mobile-ui)/home/page.tsx b/src/app/(mobile-ui)/home/page.tsx
index 6b8fc70b2..3a123f1b9 100644
--- a/src/app/(mobile-ui)/home/page.tsx
+++ b/src/app/(mobile-ui)/home/page.tsx
@@ -25,7 +25,6 @@ export default function Home() {
     const { balance } = useWallet()
     const { rewardWalletBalance } = useWalletStore()
     const [isRewardsModalOpen, setIsRewardsModalOpen] = useState(false)
-
     const [isBalanceHidden, setIsBalanceHidden] = useState(() => {
         const prefs = getUserPreferences()
         return prefs?.balanceHidden ?? false
diff --git a/src/components/Global/Badges/StatusBadge.tsx b/src/components/Global/Badges/StatusBadge.tsx
index 62b8c3690..4de5c2582 100644
--- a/src/components/Global/Badges/StatusBadge.tsx
+++ b/src/components/Global/Badges/StatusBadge.tsx
@@ -1,7 +1,7 @@
 import React from 'react'
 import { twMerge } from 'tailwind-merge'
 
-export type StatusType = 'completed' | 'pending' | 'failed' | 'cancelled' | 'soon'
+export type StatusType = 'completed' | 'pending' | 'failed' | 'cancelled' | 'soon' | 'processing'
 
 interface StatusBadgeProps {
     status: StatusType
@@ -13,16 +13,17 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({ status, className, size = 'sm
     const getStatusStyles = () => {
         switch (status) {
             case 'completed':
-                return 'bg-success-2 text-success-1'
+                return 'bg-success-2 text-success-4 border border-success-5'
             case 'pending':
-                return 'bg-secondary-4 text-secondary-1'
+            case 'processing':
+                return 'bg-secondary-4 text-yellow-6 border border-yellow-7'
             case 'failed':
             case 'cancelled':
-                return 'bg-error-1 text-error'
+                return 'bg-error-1 text-error border border-error-2'
             case 'soon':
                 return 'bg-primary-3 text-primary-4'
             default:
-                return 'bg-gray-200 text-gray-700'
+                return 'bg-grey-2 text-grey-1'
         }
     }
 
@@ -32,6 +33,8 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({ status, className, size = 'sm
                 return 'Completed'
             case 'pending':
                 return 'Pending'
+            case 'processing':
+                return 'Processing'
             case 'failed':
                 return 'Failed'
             case 'cancelled':
@@ -59,7 +62,8 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({ status, className, size = 'sm
     return (
         <span
             className={twMerge(
-                'rounded-full font-roboto font-semibold',
+                'inline-block whitespace-nowrap rounded-full',
+                'font-roboto font-semibold',
                 getSizeClasses(),
                 getStatusStyles(),
                 className
diff --git a/src/components/Global/ListItemView/index.tsx b/src/components/Global/ListItemView/index.tsx
deleted file mode 100644
index 0eded30fb..000000000
--- a/src/components/Global/ListItemView/index.tsx
+++ /dev/null
@@ -1,232 +0,0 @@
-'use client'
-import Divider from '@/components/0_Bruddle/Divider'
-import Modal from '@/components/Global/Modal'
-import ShareButton from '@/components/Global/ShareButton'
-import { copyTextToClipboardWithFallback, getExplorerUrl } from '@/utils'
-import Image from 'next/image'
-import { useState } from 'react'
-import { twMerge } from 'tailwind-merge'
-import CopyToClipboard from '../CopyToClipboard'
-import Icon from '../Icon'
-import QRCodeWrapper from '../QRCodeWrapper'
-import { EHistoryEntryType, EHistoryUserRole } from '@/hooks/useTransactionHistory'
-import type { HistoryEntry } from '@/hooks/useTransactionHistory'
-
-interface TokenBalance {
-    chainId: string
-    address: string
-    name: string
-    symbol: string
-    decimals: number
-    price: number
-    amount: number
-    currency: string
-    logoURI: string
-    value: string
-}
-
-export type TransactionType = 'Link Sent' | 'Link Received' | 'Money Requested' | 'Request paid' | 'Cash Out'
-
-interface ListItemViewProps {
-    id: string
-    variant: 'history' | 'balance' | 'req_history'
-    primaryInfo: {
-        title: React.ReactNode
-        subtitle?: React.ReactNode
-    }
-    secondaryInfo: {
-        mainText: string
-        subText?: string
-    }
-    metadata: {
-        tokenLogo?: string
-        chainLogo?: string
-    }
-    details?: HistoryEntry | TokenBalance
-}
-
-export const ListItemView = ({ id, variant, primaryInfo, secondaryInfo, metadata, details }: ListItemViewProps) => {
-    const [modalVisible, setModalVisible] = useState(false)
-    const isHistory = variant === 'history'
-    const historyItem = isHistory ? (details as HistoryEntry) : null
-
-    return (
-        <div
-            className={twMerge(
-                'flex w-full flex-row items-center justify-between gap-2 border border-t-0 border-n-1 bg-white p-3 text-h8 font-normal dark:bg-black',
-                isHistory ? 'cursor-pointer' : ''
-            )}
-            key={id}
-            onClick={() => isHistory && setModalVisible(true)}
-        >
-            <div className="relative mr-2 min-w-fit">
-                {!!metadata.tokenLogo ? (
-                    <Image
-                        src={metadata.tokenLogo}
-                        alt="token logo"
-                        width={32}
-                        height={32}
-                        className="h-8 w-8 rounded-full"
-                    />
-                ) : (
-                    <Icon name="token_placeholder" className="h-8 w-8" fill="#999" />
-                )}
-
-                {metadata.chainLogo && (
-                    <Image
-                        src={metadata.chainLogo}
-                        alt="chain logo"
-                        width={16}
-                        height={16}
-                        className="absolute -right-2 bottom-0 size-4 rounded-full"
-                    />
-                )}
-            </div>
-
-            <div className="flex w-full items-start justify-between gap-2">
-                <div className={twMerge('flex flex-col gap-2 md:flex-col')}>
-                    {primaryInfo.title &&
-                        (typeof primaryInfo.subtitle === 'string' ? (
-                            <label className="font-bold">{primaryInfo.title}</label>
-                        ) : (
-                            (primaryInfo.title as React.ReactNode)
-                        ))}
-
-                    {primaryInfo.subtitle &&
-                        (typeof primaryInfo.subtitle === 'string' ? (
-                            <label className="text-xs text-n-3">{primaryInfo.subtitle}</label>
-                        ) : (
-                            primaryInfo.subtitle
-                        ))}
-                </div>
-
-                <div className="flex flex-col items-end justify-between gap-2">
-                    <label className="font-bold">{secondaryInfo.mainText}</label>
-                    {secondaryInfo.subText && <label className="text-xs text-n-3">{secondaryInfo.subText}</label>}
-                </div>
-            </div>
-
-            {/* modal only for history variant */}
-            {isHistory && historyItem && (
-                <Modal
-                    visible={modalVisible}
-                    onClose={() => setModalVisible(false)}
-                    title={
-                        (historyItem.type === EHistoryEntryType.REQUEST && !historyItem.txHash) ||
-                        (historyItem.type === EHistoryEntryType.SEND_LINK &&
-                            historyItem.userRole === EHistoryUserRole.SENDER &&
-                            historyItem?.status !== 'claimed')
-                            ? 'Share this transaction'
-                            : 'Options'
-                    }
-                    classWrap="bg-background"
-                >
-                    {historyItem.type !== EHistoryEntryType.SEND_LINK &&
-                        historyItem.type !== EHistoryEntryType.REQUEST &&
-                        historyItem.status === 'pending' && (
-                            <div
-                                onClick={() => {
-                                    historyItem.extraData?.link &&
-                                        window.open(historyItem.extraData?.link ?? '', '_blank', 'noopener,noreferrer')
-                                }}
-                                className="flex h-12 w-full items-center gap-2 px-4 text-h8 text-sm font-bold transition-colors last:mb-0 hover:bg-n-3/10 disabled:cursor-not-allowed disabled:bg-n-4 disabled:hover:bg-n-4/90 dark:hover:bg-white/20"
-                            >
-                                Refund
-                            </div>
-                        )}
-                    {((historyItem.type === EHistoryEntryType.REQUEST && !historyItem.txHash) ||
-                        (historyItem.type === EHistoryEntryType.SEND_LINK && historyItem.status !== 'claimed')) && (
-                        <div className="p-6">
-                            <QRCodeWrapper url={historyItem.extraData?.link} />
-                            <div className="mx-auto mt-4 w-full p-2 text-center text-base text-gray-500">
-                                Show this QR to your friends!
-                            </div>
-                            <Divider className="text-gray-500" text="or" />
-                            <ShareButton url={historyItem.extraData?.link} title="Share your transaction">
-                                Share this transaction
-                            </ShareButton>
-                        </div>
-                    )}
-                    {!(
-                        (historyItem.type === EHistoryEntryType.REQUEST && !historyItem.txHash) ||
-                        (historyItem.type === EHistoryEntryType.SEND_LINK && historyItem.status !== 'claimed')
-                    ) && (
-                        <>
-                            {historyItem.extraData?.link && (
-                                <div
-                                    onClick={() => {
-                                        copyTextToClipboardWithFallback(historyItem.extraData?.link ?? '')
-                                    }}
-                                    className="flex h-12 w-full cursor-pointer items-center justify-between gap-2 px-4 text-h8 font-bold transition-colors last:mb-0 hover:bg-n-3/10 disabled:cursor-not-allowed disabled:bg-n-4 disabled:hover:bg-n-4/90 dark:hover:bg-white/20"
-                                >
-                                    <label className="block">Copy link </label>
-                                    <CopyToClipboard
-                                        fill="black"
-                                        textToCopy={historyItem.extraData?.link}
-                                        className="h-5 w-5"
-                                    />
-                                </div>
-                            )}
-
-                            {(historyItem.type === EHistoryEntryType.SEND_LINK ||
-                                historyItem.type === EHistoryEntryType.REQUEST) &&
-                                !!historyItem?.txHash && (
-                                    <a
-                                        className="flex h-12 w-full cursor-pointer items-center justify-between gap-2 px-4 text-h8 font-bold transition-colors last:mb-0 hover:bg-n-3/10 disabled:cursor-not-allowed disabled:bg-n-4 disabled:hover:bg-n-4/90 dark:hover:bg-white/20"
-                                        href={historyItem.extraData?.link}
-                                        target="_blank"
-                                    >
-                                        <label className="block">See status</label>
-                                        <Icon name="arrow-up-right" className="h-5 w-5" />
-                                    </a>
-                                )}
-
-                            {historyItem?.txHash && (
-                                <div
-                                    onClick={() => {
-                                        const explorerUrl = getExplorerUrl(historyItem.chainId ?? '')
-                                        window.open(
-                                            `${explorerUrl}/tx/${historyItem.txHash ?? ''}`,
-                                            '_blank',
-                                            'noopener,noreferrer'
-                                        )
-                                    }}
-                                    className="flex h-12 w-full cursor-pointer items-center justify-between gap-2 px-4 text-h8 font-bold transition-colors last:mb-0 hover:bg-n-3/10 disabled:cursor-not-allowed disabled:bg-n-4 disabled:hover:bg-n-4/90 dark:hover:bg-white/20"
-                                >
-                                    <label className="block">Show in explorer </label>
-                                    <Icon name="arrow-up-right" className="h-5 w-5" />
-                                </div>
-                            )}
-                            {historyItem?.attachmentUrl && !!historyItem?.txHash && (
-                                <div
-                                    onClick={() => {
-                                        window.open(historyItem.attachmentUrl, '_blank', 'noopener,noreferrer')
-                                    }}
-                                    className="flex h-12 w-full items-center gap-2 px-4 text-h8 text-sm font-bold transition-colors last:mb-0 hover:bg-n-3/10 disabled:cursor-not-allowed disabled:bg-n-4 disabled:hover:bg-n-4/90 dark:hover:bg-white/20"
-                                >
-                                    Download attachment
-                                </div>
-                            )}
-                            {/* TODO: cashout
-                            {historyItem?.type === 'Offramp Claim' &&
-                                historyItem.status !== 'claimed' && (
-                                    <a
-                                        href={(() => {
-                                            const url = new URL(historyItem?.link ?? '')
-                                            url.pathname = '/cashout/status'
-                                            return url.toString()
-                                        })()}
-                                        target="_blank"
-                                        className="flex h-12 w-full items-center gap-2 px-4 text-h8 text-sm font-bold transition-colors last:mb-0 hover:bg-n-3/10 disabled:cursor-not-allowed disabled:bg-n-4 disabled:hover:bg-n-4/90 dark:hover:bg-white/20"
-                                    >
-                                        Check Status
-                                    </a>
-                                )}
-                            */}
-                        </>
-                    )}
-                </Modal>
-            )}
-        </div>
-    )
-}
diff --git a/src/components/Global/PeanutLoading/index.tsx b/src/components/Global/PeanutLoading/index.tsx
index c408806d2..96e17cc84 100644
--- a/src/components/Global/PeanutLoading/index.tsx
+++ b/src/components/Global/PeanutLoading/index.tsx
@@ -6,7 +6,8 @@ export default function PeanutLoading({ coverFullScreen = false }: { coverFullSc
         <div
             className={twMerge(
                 'relative flex w-full items-center justify-center self-center',
-                coverFullScreen && 'fixed top-0 z-50 flex h-screen w-full items-center justify-center bg-background'
+                coverFullScreen &&
+                    'fixed left-0 top-0 z-50 flex h-screen w-full items-center justify-center bg-background'
             )}
         >
             <div className={twMerge('animate-spin')}>
diff --git a/src/components/Home/HomeHistory.tsx b/src/components/Home/HomeHistory.tsx
index be891aa4f..db4eed728 100644
--- a/src/components/Home/HomeHistory.tsx
+++ b/src/components/Home/HomeHistory.tsx
@@ -1,34 +1,45 @@
 'use client'
 
-import AddressLink from '@/components/Global/AddressLink'
 import Icon from '@/components/Global/Icon'
-import { ListItemView } from '@/components/Global/ListItemView'
 import PeanutLoading from '@/components/Global/PeanutLoading'
-import { TransactionBadge } from '@/components/Global/TransactionBadge'
-import { formatDate, getChainLogo, getTokenLogo, getChainName } from '@/utils'
+import TransactionCard from '@/components/TransactionDetails/TransactionCard'
+import { mapTransactionDataForDrawer } from '@/components/TransactionDetails/transactionTransformer'
+import { useTransactionHistory } from '@/hooks/useTransactionHistory'
+import { useUserStore } from '@/redux/hooks'
 import * as Sentry from '@sentry/nextjs'
 import Link from 'next/link'
-import { useTransactionHistory } from '@/hooks/useTransactionHistory'
+import { CardPosition } from '../Global/Card'
 
+/**
+ * component to display a preview of the most recent transactions on the home page.
+ */
 const HomeHistory = () => {
-    const { data: historyData, isLoading, isError, error } = useTransactionHistory({ mode: 'latest', limit: 3 })
+    // fetch current user's username for context in data mapping
+    const { user } = useUserStore()
+    const currentUserUsername = user?.user.username
+
+    // fetch the latest 5 transaction history entries
+    const { data: historyData, isLoading, isError, error } = useTransactionHistory({ mode: 'latest', limit: 5 }) // Updated limit to 5
 
+    // show loading state
     if (isLoading) {
         return <PeanutLoading />
     }
 
+    // show error state
     if (isError) {
         console.error(error)
         Sentry.captureException(error)
-        return <div className="w-full py-4 text-center">Error loading history: {error?.message}</div>
+        return <div className="w-full py-4 text-center">error loading history: {error?.message}</div>
     }
 
+    // show empty state if no transactions exist
     if (!historyData?.entries.length) {
         return (
             <div className="mx-auto mt-6 w-full space-y-2 md:max-w-2xl md:space-y-3">
-                <h2 className="text-baes font-bold">Recent Transactions</h2>
+                <h2 className="text-base font-bold">transactions</h2> {/* use lowercase consistent with history page */}
                 <div className="h-full w-full border-t border-n-1 py-8 text-center">
-                    <p className="text-sm text-gray-500">No transactions yet</p>
+                    <p className="text-sm text-gray-500">no transactions yet</p>
                 </div>
             </div>
         )
@@ -36,46 +47,44 @@ const HomeHistory = () => {
 
     return (
         <div className="mx-auto w-full space-y-3 pb-28 md:max-w-2xl md:space-y-3">
+            {/* link to the full history page */}
             <Link href="/history" className="flex items-center justify-between">
                 <h2 className="text-base font-bold">Transactions</h2>
                 <Icon width={30} height={30} name="arrow-next" />
             </Link>
-            <div className="h-full w-full border-t border-n-1">
-                {!!historyData?.entries.length &&
-                    historyData!.entries.map((item) => (
-                        <div key={item.uuid}>
-                            <ListItemView
-                                id={item.uuid}
-                                variant="history"
-                                primaryInfo={{
-                                    title: (
-                                        <div className="flex flex-col items-start gap-2 md:flex-row md:items-center ">
-                                            <div className="font-bold">
-                                                {item.type} {item.userRole}
-                                            </div>
-                                            <div className="flex flex-col items-end justify-end gap-2 text-end">
-                                                <TransactionBadge status={item.status as string} />
-                                            </div>
-                                        </div>
-                                    ),
-                                    subtitle: !!item.recipientAccount.identifier && (
-                                        <div className="text-xs text-gray-1" onClick={(e) => e.stopPropagation()}>
-                                            To: <AddressLink address={item.recipientAccount.identifier} />
-                                        </div>
-                                    ),
-                                }}
-                                secondaryInfo={{
-                                    mainText: item.extraData?.usdAmount,
-                                    subText: item.timestamp ? formatDate(new Date(item.timestamp)) : '',
-                                }}
-                                metadata={{
-                                    tokenLogo: getTokenLogo(item.tokenSymbol),
-                                    chainLogo: getChainLogo(getChainName(item.chainId) ?? ''),
-                                }}
-                                details={item}
-                            />
-                        </div>
-                    ))}
+            {/* container for the transaction cards */}
+            <div className="h-full w-full">
+                {/* map over the latest entries and render transactioncard */}
+                {historyData.entries.map((item, index) => {
+                    // map the raw history entry to the format needed by the ui components
+                    const { transactionDetails, transactionCardType } = mapTransactionDataForDrawer(
+                        item,
+                        currentUserUsername || ''
+                    )
+
+                    // determine card position for styling (first, middle, last, single)
+                    let position: CardPosition = 'middle'
+                    if (historyData.entries.length === 1) {
+                        position = 'single'
+                    } else if (index === 0) {
+                        position = 'first'
+                    } else if (index === historyData.entries.length - 1) {
+                        position = 'last'
+                    }
+
+                    return (
+                        <TransactionCard
+                            key={item.uuid}
+                            type={transactionCardType}
+                            name={transactionDetails.userName}
+                            amount={Number(transactionDetails.amount)}
+                            status={transactionDetails.status}
+                            initials={transactionDetails.initials}
+                            transaction={transactionDetails}
+                            position={position}
+                        />
+                    )
+                })}
             </div>
         </div>
     )
diff --git a/src/components/Home/TransactionCard.tsx b/src/components/Home/TransactionCard.tsx
deleted file mode 100644
index f20d5e444..000000000
--- a/src/components/Home/TransactionCard.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import StatusBadge, { StatusType } from '@/components/Global/Badges/StatusBadge'
-import Card, { CardPosition } from '@/components/Global/Card'
-import { Icon } from '@/components/Global/Icons/Icon'
-import { formatExtendedNumber, printableUsdc } from '@/utils'
-import React from 'react'
-
-export type TransactionType = 'send' | 'withdraw' | 'add' | 'request'
-
-interface TransactionCardProps {
-    type: TransactionType
-    name: string
-    amount: bigint
-    status?: StatusType
-    initials?: string
-    position?: CardPosition
-    onClick?: () => void
-}
-
-const TransactionCard: React.FC<TransactionCardProps> = ({
-    type,
-    name,
-    amount,
-    status,
-    initials = '',
-    position = 'middle',
-    onClick,
-}) => {
-    // 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
-
-    return (
-        <Card position={position} onClick={onClick}>
-            <div className="flex items-center justify-between">
-                <div className="flex items-center gap-3">
-                    {/* Icon or Initials based on transaction type */}
-                    <div
-                        className={`flex h-8 w-8 items-center justify-center rounded-full p-2 text-xs font-bold ${getIconBackgroundColor(type)}`}
-                    >
-                        {renderIcon(type, initials)}
-                    </div>
-
-                    <div className="flex flex-col">
-                        <div className="max-w-40 truncate font-roboto text-sm font-medium">{name}</div>
-                        <div className="flex items-center gap-1 text-gray-500">
-                            {getActionIcon(type)}
-                            <span className="text-[10px] capitalize">{type}</span>
-                        </div>
-                    </div>
-                </div>
-
-                <div className="flex flex-col items-end space-y-0.5">
-                    <span className="font-roboto text-xs font-medium">{finalAmount}</span>
-                    {status && <StatusBadge status={status} />}
-                </div>
-            </div>
-        </Card>
-    )
-}
-
-// helper functions
-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':
-            return 'bg-black text-white'
-        default:
-            return 'bg-gray-200'
-    }
-}
-
-function renderIcon(type: TransactionType, initials: string): React.ReactNode {
-    switch (type) {
-        case 'send':
-        case 'request':
-            return initials.substring(0, 2).toUpperCase()
-        case 'withdraw':
-            return <Icon name="bank" size={16} fill="white" />
-        case 'add':
-            return <Icon name="arrow-down" size={14} fill="white" />
-        default:
-            return null
-    }
-}
-
-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" />
-    }
-}
-
-export default TransactionCard
diff --git a/src/components/Payment/History/index.tsx b/src/components/Payment/History/index.tsx
deleted file mode 100644
index 607e83d05..000000000
--- a/src/components/Payment/History/index.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import AddressLink from '@/components/Global/AddressLink'
-import { ListItemView } from '@/components/Global/ListItemView'
-import { RecipientType } from '@/lib/url-parser/types/payment'
-import { TRequestHistory } from '@/services/services.types'
-import { formatAmount, getChainName, getTokenLogo } from '@/utils'
-
-interface PaymentHistoryProps {
-    recipient: { identifier: string; recipientType: RecipientType; resolvedAddress: string }
-    history: TRequestHistory[]
-}
-
-export default function PaymentHistory({ recipient, history }: PaymentHistoryProps) {
-    if (history.length === 0 || !recipient) return null
-
-    return (
-        <div className="space-y-3">
-            <div className="text-base font-semibold">
-                Payment history to <AddressLink address={recipient.identifier} />
-            </div>
-            <div className="border-t border-t-black">
-                {[...history]
-                    .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
-                    .slice(0, 5)
-                    .map((entry, idx) => (
-                        <ListItemView
-                            key={entry.uuid}
-                            id={`${entry.chainId}-${idx}`}
-                            variant="req_history"
-                            primaryInfo={{
-                                title: (
-                                    <div>
-                                        <label className="font-bold">{entry.tokenSymbol}</label>
-                                        {entry.chainId && (
-                                            <label className="text-xs text-n-3">
-                                                {' '}
-                                                on {getChainName(entry.chainId)}
-                                            </label>
-                                        )}
-                                    </div>
-                                ),
-                                subtitle: (
-                                    <div className="text-xs text-n-3">
-                                        {entry.senderAccount ? (
-                                            <div>
-                                                Paid by:{' '}
-                                                <AddressLink
-                                                    address={
-                                                        entry.senderAccount.username || entry.senderAccount.identifier
-                                                    }
-                                                />{' '}
-                                                <br className="md:hidden" /> <span className="hidden md:inline">|</span>{' '}
-                                                Status: {entry.status}
-                                            </div>
-                                        ) : (
-                                            `Status: ${entry.status}`
-                                        )}
-                                    </div>
-                                ),
-                            }}
-                            secondaryInfo={{
-                                mainText: `$${formatAmount(entry.amount)}`,
-                                subText: new Date(entry.timestamp).toLocaleDateString(),
-                            }}
-                            metadata={{
-                                tokenLogo: getTokenLogo(entry.tokenSymbol),
-                            }}
-                        />
-                    ))}
-            </div>
-        </div>
-    )
-}
diff --git a/src/components/Profile/AvatarWithBadge.tsx b/src/components/Profile/AvatarWithBadge.tsx
index aeb488edc..a077993ea 100644
--- a/src/components/Profile/AvatarWithBadge.tsx
+++ b/src/components/Profile/AvatarWithBadge.tsx
@@ -1,39 +1,78 @@
 import React from 'react'
 import { twMerge } from 'tailwind-merge'
 import AchievementsBadge, { AchievementsBadgeSize } from '../Global/Badges/AchievementsBadge'
+import { Icon, IconName } from '../Global/Icons/Icon'
 
+type AvatarSize = 'extra-small' | 'small' | 'medium' | 'large'
+
+/**
+ * props for the avatarwithbadge component.
+ */
 interface AvatarWithBadgeProps {
-    initials: string
+    initials?: string
+    icon?: IconName
     isVerified?: boolean
     className?: string
-    size?: 'extra-small' | 'small' | 'medium' | 'large'
+    size?: AvatarSize
     achievementsBadgeSize?: AchievementsBadgeSize
+    inlineStyle?: React.CSSProperties // for dynamic background colors based on username (hex codes)
+    textColor?: string
+    iconFillColor?: string
 }
+
+/**
+ * component to display an avatar circle with either initials or an icon,
+ * and optionally a verification badge.
+ */
 const AvatarWithBadge: React.FC<AvatarWithBadgeProps> = ({
     initials,
+    icon,
     isVerified = false,
     className,
     size = 'medium',
     achievementsBadgeSize = 'small',
+    inlineStyle,
+    textColor,
+    iconFillColor,
 }) => {
-    const sizeClasses = {
-        'extra-small': 'h-8 w-8 text-sm',
-        small: 'h-8 w-8 text-lg',
+    const sizeClasses: Record<AvatarSize, string> = {
+        'extra-small': 'h-8 w-8 text-xs',
+        small: 'h-10 w-10 text-sm',
         medium: 'h-16 w-16 text-2xl',
         large: 'h-24 w-24 text-3xl',
     }
 
+    const iconSizeMap: Record<AvatarSize, number> = {
+        'extra-small': 16,
+        small: 18,
+        medium: 32,
+        large: 48,
+    }
+
     return (
         <div className={'relative'}>
+            {/* the main avatar circle */}
             <div
                 className={twMerge(
-                    `flex items-center justify-center rounded-full bg-yellow-5 font-bold`,
+                    `flex items-center justify-center rounded-full font-bold`,
                     sizeClasses[size],
                     className
                 )}
+                // apply dynamic styles (e.g., background color)
+
+                style={{
+                    ...inlineStyle,
+                    color: !icon ? textColor : undefined,
+                }}
             >
-                {initials}
+                {/* display icon if provided, otherwise display initials */}
+                {icon ? (
+                    <Icon name={icon} size={iconSizeMap[size]} fill={iconFillColor} style={{ color: textColor }} />
+                ) : (
+                    initials
+                )}
             </div>
+            {/* display verification badge if isverified is true */}
             {isVerified && <AchievementsBadge size={achievementsBadgeSize} />}
         </div>
     )
diff --git a/src/components/Profile/components/PublicProfile.tsx b/src/components/Profile/components/PublicProfile.tsx
index 26ff5cadb..a2a765776 100644
--- a/src/components/Profile/components/PublicProfile.tsx
+++ b/src/components/Profile/components/PublicProfile.tsx
@@ -7,7 +7,6 @@ import { Button } from '@/components/0_Bruddle'
 import Card from '@/components/Global/Card'
 import { Icon } from '@/components/Global/Icons/Icon'
 import NavHeader from '@/components/Global/NavHeader'
-import TransactionCard from '@/components/Home/TransactionCard'
 import { useAppDispatch } from '@/redux/hooks'
 import { paymentActions } from '@/redux/slices/payment-slice'
 import { formatExtendedNumber } from '@/utils'
@@ -140,8 +139,9 @@ const PublicProfile: React.FC<PublicProfileProps> = ({
                                 </Link>
                             </div>
 
+                            {/* todo: show real transactions from the user */}
                             {/* Example transaction - todo: replace with actual transaction data */}
-                            <div>
+                            {/* <div>
                                 <TransactionCard
                                     type="send"
                                     name="Hugo Montenegro"
@@ -171,7 +171,7 @@ const PublicProfile: React.FC<PublicProfileProps> = ({
                                     initials="HM"
                                     position="last"
                                 />
-                            </div>
+                            </div> */}
                         </div>
                     )}
                 </div>
diff --git a/src/components/TransactionDetails/TransactionAvatarBadge.tsx b/src/components/TransactionDetails/TransactionAvatarBadge.tsx
new file mode 100644
index 000000000..bfacbcadd
--- /dev/null
+++ b/src/components/TransactionDetails/TransactionAvatarBadge.tsx
@@ -0,0 +1,109 @@
+import { IconName } from '@/components/Global/Icons/Icon'
+import AvatarWithBadge from '@/components/Profile/AvatarWithBadge'
+import { TransactionType } from '@/components/TransactionDetails/TransactionCard'
+import {
+    AVATAR_LINK_BG,
+    AVATAR_TEXT_DARK,
+    AVATAR_TEXT_LIGHT,
+    AVATAR_WALLET_BG,
+    getColorForUsername,
+} from '@/utils/color.utils'
+import React from 'react'
+import { isAddress } from 'viem'
+
+type PeerAvatarSize = 'extra-small' | 'small' | 'medium' | 'large'
+
+interface TransactionAvatarBadgeProps {
+    initials?: string
+    userName?: string
+    isLinkTransaction?: boolean
+    isVerified?: boolean
+    size?: PeerAvatarSize
+    transactionType: TransactionType
+    context: 'card' | 'header'
+}
+
+/**
+ * displays an appropriate avatar for a transaction entry.
+ * handles showing initials, link icon, wallet icon, or bank icon based on context.
+ */
+const TransactionAvatarBadge: React.FC<TransactionAvatarBadgeProps> = ({
+    initials,
+    userName,
+    isLinkTransaction = false,
+    isVerified = false,
+    size = 'medium',
+    transactionType,
+    context,
+}) => {
+    let displayIconName: IconName | undefined = undefined
+    let displayInitials: string | undefined = initials
+    let calculatedBgColor = AVATAR_WALLET_BG
+    let iconFillColor = AVATAR_TEXT_DARK
+    let textColor = AVATAR_TEXT_DARK
+
+    // determine if the userName represents a user (not address or specific strings)
+    const isValidUser = userName ? !isAddress(userName) : false
+
+    // determine Icon, background, and colors based on type and context
+    switch (transactionType) {
+        case 'withdraw':
+        case 'cashout':
+            displayIconName = 'bank'
+            displayInitials = undefined
+            calculatedBgColor = context === 'card' ? AVATAR_TEXT_DARK : AVATAR_WALLET_BG
+            textColor = context === 'card' ? AVATAR_TEXT_LIGHT : AVATAR_TEXT_DARK
+            break
+        case 'add':
+            displayIconName = 'wallet-outline'
+            displayInitials = undefined
+            calculatedBgColor = context === 'card' ? AVATAR_TEXT_DARK : AVATAR_WALLET_BG
+            iconFillColor = context === 'card' ? AVATAR_TEXT_LIGHT : AVATAR_TEXT_DARK
+            textColor = context === 'card' ? AVATAR_TEXT_LIGHT : AVATAR_TEXT_DARK
+            break
+        case 'send':
+        case 'request':
+            if (isLinkTransaction) {
+                displayIconName = 'link'
+                displayInitials = undefined
+                calculatedBgColor = AVATAR_LINK_BG
+                iconFillColor = AVATAR_TEXT_DARK
+            } else if (!isValidUser) {
+                displayIconName = 'wallet-outline'
+                displayInitials = undefined
+                calculatedBgColor = AVATAR_WALLET_BG
+                iconFillColor = AVATAR_TEXT_DARK
+            } else if (displayInitials) {
+                const colors = getColorForUsername(userName)
+                calculatedBgColor = colors.backgroundColor
+                textColor = AVATAR_TEXT_DARK
+                displayIconName = undefined
+            } else {
+                // fallback for send/request if no initials and not link/address
+                displayIconName = 'wallet-outline'
+                calculatedBgColor = AVATAR_WALLET_BG
+                iconFillColor = AVATAR_TEXT_DARK
+            }
+            break
+        default:
+            displayIconName = 'wallet-outline'
+            calculatedBgColor = AVATAR_WALLET_BG
+            iconFillColor = AVATAR_TEXT_DARK
+            break
+    }
+
+    return (
+        <AvatarWithBadge
+            isVerified={isVerified && !!displayInitials}
+            initials={displayInitials}
+            icon={displayIconName}
+            size={size}
+            achievementsBadgeSize={size === 'medium' || size === 'large' ? 'small' : 'extra-small'}
+            inlineStyle={{ backgroundColor: calculatedBgColor }}
+            textColor={textColor}
+            iconFillColor={iconFillColor}
+        />
+    )
+}
+
+export default TransactionAvatarBadge
diff --git a/src/components/TransactionDetails/TransactionCard.tsx b/src/components/TransactionDetails/TransactionCard.tsx
new file mode 100644
index 000000000..5d7287062
--- /dev/null
+++ b/src/components/TransactionDetails/TransactionCard.tsx
@@ -0,0 +1,129 @@
+import StatusBadge, { StatusType } from '@/components/Global/Badges/StatusBadge'
+import Card, { CardPosition } from '@/components/Global/Card'
+import { Icon, IconName } from '@/components/Global/Icons/Icon'
+import TransactionAvatarBadge from '@/components/TransactionDetails/TransactionAvatarBadge'
+import { TransactionDetailsDrawer } from '@/components/TransactionDetails/TransactionDetailsDrawer'
+import { TransactionDetails } from '@/components/TransactionDetails/transactionTransformer'
+import { useTransactionDetailsDrawer } from '@/hooks/useTransactionDetailsDrawer'
+import { formatAmount, printableAddress } from '@/utils'
+import React from 'react'
+import { isAddress } from 'viem'
+
+export type TransactionType = 'send' | 'withdraw' | 'add' | 'request' | 'cashout'
+
+interface TransactionCardProps {
+    type: TransactionType
+    name: string
+    amount: number
+    status?: StatusType
+    initials?: string
+    position?: CardPosition
+    transaction: TransactionDetails
+}
+
+/**
+ * component to display a single transaction entry in a list format.
+ * it handles displaying the avatar/icon, name, amount, status,
+ * and opens a transaction details drawer when clicked.
+ */
+const TransactionCard: React.FC<TransactionCardProps> = ({
+    type,
+    name,
+    amount,
+    status,
+    initials = '',
+    position = 'middle',
+    transaction,
+}) => {
+    // hook to manage the state of the details drawer (open/closed, selected transaction)
+    const { isDrawerOpen, selectedTransaction, openTransactionDetails, closeTransactionDetails } =
+        useTransactionDetailsDrawer()
+
+    // determine if amount should be positive or negative based on card type
+    const isNegative = type === 'send' || type === 'withdraw'
+    const displayAmount = isNegative ? `-$${formatAmount(amount)}` : `+$${formatAmount(amount)}`
+
+    // for request/send types, always show positive amount with dollar sign
+    const finalAmount = type === 'request' || type === 'send' ? `$${formatAmount(amount)}` : displayAmount
+
+    const handleClick = () => {
+        openTransactionDetails(transaction)
+    }
+
+    const isLinkTx = transaction.extraDataForDrawer?.isLinkTransaction ?? false
+    const userNameForAvatar = transaction.userName // used by avatar for color hashing or type checking
+
+    return (
+        <>
+            {/* the clickable card */}
+            <Card position={position} onClick={handleClick}>
+                <div className="flex items-center justify-between">
+                    <div className="flex items-center gap-3">
+                        {/* txn avatar component handles icon/initials/colors */}
+                        <TransactionAvatarBadge
+                            initials={initials}
+                            userName={userNameForAvatar}
+                            isLinkTransaction={isLinkTx}
+                            transactionType={type}
+                            context="card"
+                            size="extra-small"
+                        />
+                        <div className="flex flex-col">
+                            {/* display formatted name (address or username) */}
+                            <div className="max-w-40 truncate font-roboto text-sm font-medium">
+                                {isAddress(name) ? printableAddress(name) : name}
+                            </div>
+                            {/* display the action icon and type text */}
+                            <div className="flex items-center gap-1 text-gray-500">
+                                {getActionIcon(type)}
+                                <span className="text-[10px] capitalize">{type}</span>
+                            </div>
+                        </div>
+                    </div>
+
+                    {/* amount and status on the right side */}
+                    <div className="flex flex-col items-end space-y-0.5">
+                        <span className="font-roboto text-xs font-medium">{finalAmount}</span>
+                        {status && <StatusBadge status={status} />}
+                    </div>
+                </div>
+            </Card>
+
+            {/* Transaction Details Drawer */}
+            <TransactionDetailsDrawer
+                isOpen={isDrawerOpen && selectedTransaction?.id === transaction.id}
+                onClose={closeTransactionDetails}
+                transaction={selectedTransaction}
+            />
+        </>
+    )
+}
+
+// helper functions
+function getActionIcon(type: TransactionType): React.ReactNode {
+    let iconName: IconName | null = null
+    let iconSize = 6
+
+    switch (type) {
+        case 'send':
+            iconName = 'arrow-up-right'
+            break
+        case 'request':
+            iconName = 'arrow-down-left'
+            break
+        case 'withdraw':
+        case 'cashout':
+            iconName = 'arrow-up'
+            iconSize = 8
+            break
+        case 'add':
+            iconName = 'arrow-down'
+            iconSize = 8
+            break
+        default:
+            return null
+    }
+    return <Icon name={iconName} size={iconSize} fill="currentColor" />
+}
+
+export default TransactionCard
diff --git a/src/components/TransactionDetails/TransactionDetailsDrawer.tsx b/src/components/TransactionDetails/TransactionDetailsDrawer.tsx
new file mode 100644
index 000000000..35b7e8de0
--- /dev/null
+++ b/src/components/TransactionDetails/TransactionDetailsDrawer.tsx
@@ -0,0 +1,152 @@
+import BottomDrawer from '@/components/Global/BottomDrawer'
+import Card from '@/components/Global/Card'
+import { PaymentInfoRow } from '@/components/Payment/PaymentInfoRow'
+import { TransactionDetails } from '@/components/TransactionDetails/transactionTransformer'
+import { useDynamicHeight } from '@/hooks/ui/useDynamicHeight'
+import { EHistoryEntryType, EHistoryUserRole } from '@/hooks/useTransactionHistory'
+import { formatAmount, formatDate, getInitialsFromName } from '@/utils'
+import Link from 'next/link'
+import React, { useCallback, useRef } from 'react'
+import { Button } from '../0_Bruddle'
+import { Icon } from '../Global/Icons/Icon'
+import QRCodeWrapper from '../Global/QRCodeWrapper'
+import ShareButton from '../Global/ShareButton'
+import { TransactionDetailsHeaderCard } from './TransactionDetailsHeaderCard'
+
+interface TransactionDetailsDrawerProps {
+    isOpen: boolean
+    onClose: () => void
+    /** the transaction data to display, or null if none selected. */
+    transaction: TransactionDetails | null
+}
+
+/**
+ * a bottom drawer component that displays detailed information about a specific transaction.
+ * includes header, details card, and conditional qr/sharing options for pending transactions.
+ */
+export const TransactionDetailsDrawer: React.FC<TransactionDetailsDrawerProps> = ({ isOpen, onClose, transaction }) => {
+    // ref for the main content area to calculate dynamic height
+    const contentRef = useRef<HTMLDivElement>(null)
+
+    // calculate drawer height based on content, with min/max constraints
+    const drawerHeightVh = useDynamicHeight(contentRef, {
+        maxHeightVh: 90, // max 90% of viewport height
+        minHeightVh: 30, // min 30% of viewport height
+        extraVhOffset: 10, // some extra padding to the calculated height
+    })
+
+    // determine the heights for the drawer states (expanded, half)
+    const currentExpandedHeight = drawerHeightVh ?? 85 // use calculated height or fallback
+    const currentHalfHeight = Math.min(60, drawerHeightVh ?? 60) // half height, capped at 60vh or calculated height
+
+    const handleClose = useCallback(() => {
+        if (onClose) {
+            onClose()
+        }
+    }, [onClose])
+
+    if (!transaction) return null
+
+    // format data for display
+    const amountDisplay = formatAmount(transaction.amount as number)
+    const dateDisplay = formatDate(transaction.date as Date)
+    const feeDisplay = transaction.fee !== undefined ? formatAmount(transaction.fee as number) : 'N/A'
+
+    // determine if the qr code and sharing section should be shown
+    // conditions: status is pending, there's a link, and it's a send_link/request sent by the user, or a request received by the user.
+    const shouldShowQrShare =
+        transaction.status === 'pending' &&
+        transaction.extraDataForDrawer?.link &&
+        ((transaction.extraDataForDrawer.originalType === EHistoryEntryType.SEND_LINK &&
+            transaction.extraDataForDrawer.originalUserRole === EHistoryUserRole.SENDER) ||
+            (transaction.extraDataForDrawer.originalType === EHistoryEntryType.REQUEST &&
+                transaction.extraDataForDrawer.originalUserRole === EHistoryUserRole.SENDER) ||
+            (transaction.extraDataForDrawer.originalType === EHistoryEntryType.REQUEST &&
+                transaction.extraDataForDrawer.originalUserRole === EHistoryUserRole.RECIPIENT))
+
+    return (
+        <BottomDrawer
+            isOpen={isOpen}
+            onClose={handleClose}
+            initialPosition="expanded"
+            collapsedHeight={5}
+            halfHeight={currentHalfHeight}
+            expandedHeight={currentExpandedHeight}
+            preventScroll={false}
+        >
+            <div ref={contentRef} className="space-y-4 pb-8">
+                {/* show qr code at the top if applicable */}
+                {shouldShowQrShare && transaction.extraDataForDrawer?.link && (
+                    <QRCodeWrapper url={transaction.extraDataForDrawer.link} />
+                )}
+
+                {/* transaction header card */}
+                <TransactionDetailsHeaderCard
+                    direction={transaction.direction}
+                    userName={transaction.userName}
+                    amountDisplay={amountDisplay}
+                    initials={getInitialsFromName(transaction.userName)}
+                    status={transaction.status}
+                    isVerified={transaction.isVerified}
+                    isLinkTransaction={transaction.extraDataForDrawer?.isLinkTransaction}
+                    transactionType={transaction.extraDataForDrawer?.transactionCardType}
+                />
+
+                {/* details card (date, fee, memo) */}
+                <Card position={shouldShowQrShare ? 'first' : 'single'} className="px-4 py-0" border={true}>
+                    <div className="space-y-0">
+                        {dateDisplay && (
+                            <PaymentInfoRow
+                                label="Date"
+                                value={dateDisplay}
+                                hideBottomBorder={!transaction.fee && !transaction.memo}
+                            />
+                        )}
+                        {transaction.fee !== undefined && (
+                            <PaymentInfoRow label="Fee" value={feeDisplay} hideBottomBorder={!transaction.memo} />
+                        )}
+                        {transaction.memo && <PaymentInfoRow label="Memo" value={transaction.memo} hideBottomBorder />}
+                    </div>
+                </Card>
+
+                {/* share and cancel buttons section (only if qr is shown) */}
+                {shouldShowQrShare && transaction.extraDataForDrawer?.link && (
+                    <div className="space-y-2">
+                        {' '}
+                        {/* added space-y for button separation */}
+                        <ShareButton url={transaction.extraDataForDrawer.link} title="share transaction">
+                            Share Link
+                        </ShareButton>
+                        {/* show cancel button only if the current user sent the link/request */}
+                        {(transaction.extraDataForDrawer.originalType === EHistoryEntryType.SEND_LINK ||
+                            transaction.extraDataForDrawer.originalType === EHistoryEntryType.REQUEST) &&
+                            transaction.extraDataForDrawer.originalUserRole === EHistoryUserRole.SENDER && (
+                                <Button
+                                    disabled // disabled for now
+                                    variant={'primary-soft'}
+                                    className="flex w-full items-center gap-1"
+                                    shadowSize="4"
+                                >
+                                    <div className="flex size-6 items-center gap-0">
+                                        <Icon name="cancel" />
+                                    </div>
+                                    <span>
+                                        Cancel link <span className="text-xs">(Coming soon)</span>
+                                    </span>
+                                </Button>
+                            )}
+                    </div>
+                )}
+
+                {/* support link section */}
+                <Link
+                    href={'/support'}
+                    className="flex items-center justify-center gap-2 text-sm font-medium text-grey-1 underline transition-colors hover:text-black"
+                >
+                    <Icon name="peanut-support" size={16} className="text-grey-1" />
+                    Issues with this transaction?
+                </Link>
+            </div>
+        </BottomDrawer>
+    )
+}
diff --git a/src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx b/src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx
new file mode 100644
index 000000000..a01453fed
--- /dev/null
+++ b/src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx
@@ -0,0 +1,107 @@
+'use client'
+
+import StatusBadge, { StatusType } from '@/components/Global/Badges/StatusBadge'
+import TransactionAvatarBadge from '@/components/TransactionDetails/TransactionAvatarBadge'
+import { TransactionType } from '@/components/TransactionDetails/TransactionCard'
+import { printableAddress } from '@/utils'
+import React from 'react'
+import { isAddress as isWalletAddress } from 'viem'
+import Card from '../Global/Card'
+
+export type TransactionDirection = 'send' | 'receive' | 'request_sent' | 'request_received' | 'withdraw' | 'add'
+
+interface TransactionDetailsHeaderCardProps {
+    direction: TransactionDirection
+    userName: string
+    amountDisplay: string
+    initials: string
+    status?: StatusType
+    isVerified?: boolean
+    isLinkTransaction?: boolean
+    transactionType?: TransactionType
+}
+
+const getTitle = (direction: TransactionDirection, userName: string, isLinkTransaction?: boolean): React.ReactNode => {
+    let titleText = userName
+
+    if (isLinkTransaction) {
+        switch (direction) {
+            case 'send':
+            case 'request_sent':
+                titleText = 'Sent via Link'
+                break
+            case 'receive':
+            case 'request_received':
+                titleText = 'Received via Link'
+                break
+            default:
+                titleText = 'Link Transaction'
+                break
+        }
+    } else {
+        const isAddress = isWalletAddress(userName)
+        const displayName = isAddress ? printableAddress(userName) : userName
+        switch (direction) {
+            case 'send':
+                titleText = `Sending to ${displayName}`
+                break
+            case 'request_received':
+                titleText = `Paid request to ${displayName}`
+                break
+            case 'receive':
+                titleText = `Received from ${displayName}`
+                break
+            case 'request_sent':
+                titleText = `Requested from ${displayName}`
+                break
+            case 'withdraw':
+                titleText = `Withdrawing to ${displayName}`
+                break
+            case 'add':
+                titleText = `Added from ${displayName}`
+                break
+            default:
+                titleText = displayName
+                break
+        }
+    }
+
+    return <span className="flex items-center gap-1">{titleText}</span>
+}
+
+export const TransactionDetailsHeaderCard: React.FC<TransactionDetailsHeaderCardProps> = ({
+    direction,
+    userName,
+    amountDisplay,
+    initials,
+    status,
+    isVerified = false,
+    isLinkTransaction = false,
+    transactionType,
+}) => {
+    const typeForAvatar =
+        transactionType ?? (direction === 'add' ? 'add' : direction === 'withdraw' ? 'withdraw' : 'send')
+
+    return (
+        <Card className="relative p-4 md:p-6" position="single">
+            <div className="flex items-center gap-3">
+                <TransactionAvatarBadge
+                    initials={initials}
+                    userName={userName}
+                    isLinkTransaction={isLinkTransaction}
+                    isVerified={isVerified}
+                    transactionType={typeForAvatar}
+                    context="header"
+                    size="medium"
+                />
+                <div className="space-y-1">
+                    <h2 className="text-sm font-medium text-grey-1">
+                        {getTitle(direction, userName, isLinkTransaction)}
+                    </h2>
+                    <h1 className="text-3xl font-extrabold md:text-4xl">{amountDisplay}</h1>
+                </div>
+            </div>
+            <div className="absolute bottom-4 right-4">{status && <StatusBadge status={status} size="small" />}</div>
+        </Card>
+    )
+}
diff --git a/src/components/TransactionDetails/transactionTransformer.ts b/src/components/TransactionDetails/transactionTransformer.ts
new file mode 100644
index 000000000..4923e77e9
--- /dev/null
+++ b/src/components/TransactionDetails/transactionTransformer.ts
@@ -0,0 +1,225 @@
+import { StatusType } from '@/components/Global/Badges/StatusBadge'
+import { TransactionType as TransactionCardType } from '@/components/TransactionDetails/TransactionCard'
+import { TransactionDirection } from '@/components/TransactionDetails/TransactionDetailsHeaderCard'
+import { EHistoryEntryType, EHistoryUserRole, HistoryEntry } from '@/hooks/useTransactionHistory'
+import { getExplorerUrl, getInitialsFromName } from '@/utils/general.utils'
+
+/**
+ * @fileoverview maps raw transaction history data from the api/hook to the format needed by ui components.
+ */
+
+/**
+ * defines the structure of the data expected by the transaction details drawer component.
+ * includes ui-specific fields derived from the original history entry.
+ */
+export interface TransactionDetails {
+    id: string
+    direction: TransactionDirection
+    userName: string
+    amount: number | bigint
+    currencySymbol?: string
+    tokenSymbol?: string
+    initials: string
+    status?: StatusType
+    isVerified?: boolean
+    date: string | Date
+    fee?: number | string
+    memo?: string
+    txHash?: string
+    explorerUrl?: string
+    extraDataForDrawer?: {
+        originalType: EHistoryEntryType
+        originalUserRole: EHistoryUserRole
+        link?: string
+        isLinkTransaction?: boolean
+        transactionCardType?: TransactionCardType
+    }
+}
+
+/**
+ * defines the output structure of the mapping function.
+ */
+interface MappedTransactionData {
+    /** data structured for the transactiondetailsdrawer. */
+    transactionDetails: TransactionDetails
+    /** the visual type for the transactioncard component. */
+    transactionCardType: TransactionCardType
+}
+
+/**
+ * maps a raw historyentry from the api/hook to the structured data needed by ui components.
+ *
+ * @param entry the raw history entry object.
+ * @param currentuserusername the username of the currently logged-in user.
+ * @returns an object containing structured transactiondetails and the transactioncardtype.
+ */
+export function mapTransactionDataForDrawer(entry: HistoryEntry, currentUserUsername?: string): MappedTransactionData {
+    // initialize variables
+    let direction: TransactionDirection
+    let transactionCardType: TransactionCardType
+    let nameForDetails = ''
+    let uiStatus: StatusType = 'pending'
+    let isLinkTx = false
+    let isPeerActuallyUser = false
+
+    // determine if the current user was the sender or recipient in the original entry
+    const isCurrentUserSender = entry.senderAccount?.isUser && entry.senderAccount?.username === currentUserUsername
+    const isCurrentUserRecipient =
+        entry.recipientAccount?.isUser && entry.recipientAccount?.username === currentUserUsername
+
+    // determine direction, card type, peer name, and flags based on original type and user role
+    switch (entry.type) {
+        case EHistoryEntryType.SEND_LINK:
+            isLinkTx = true
+            if (isCurrentUserSender) {
+                direction = 'send'
+                transactionCardType = 'send'
+                nameForDetails =
+                    entry.recipientAccount?.username || entry.recipientAccount?.identifier || 'Sent via Link'
+                isPeerActuallyUser = !!entry.recipientAccount?.isUser
+            } else {
+                direction = 'receive'
+                transactionCardType = 'add'
+                nameForDetails = entry.senderAccount?.username || entry.senderAccount?.identifier || 'Received via Link'
+                isPeerActuallyUser = !!entry.senderAccount?.isUser
+            }
+            break
+        case EHistoryEntryType.REQUEST:
+            isLinkTx = !entry.txHash
+            if (isCurrentUserSender) {
+                direction = 'request_sent'
+                transactionCardType = 'request'
+                nameForDetails =
+                    entry.recipientAccount?.username || entry.recipientAccount?.identifier || 'Requested To'
+                isPeerActuallyUser = !!entry.recipientAccount?.isUser
+            } else {
+                if (entry.status?.toUpperCase() === 'NEW' || entry.status?.toUpperCase() === 'PENDING') {
+                    direction = 'request_received'
+                    transactionCardType = 'request'
+                    nameForDetails =
+                        entry.senderAccount?.username ||
+                        entry.senderAccount?.identifier ||
+                        `Request From ${entry.recipientAccount?.username || entry.recipientAccount?.identifier}`
+                    isPeerActuallyUser = !!entry.senderAccount?.isUser
+                } else {
+                    direction = 'send'
+                    transactionCardType = 'send'
+                    nameForDetails =
+                        entry.senderAccount?.username || entry.senderAccount?.identifier || 'Paid Request To'
+                    isPeerActuallyUser = !!entry.senderAccount?.isUser
+                }
+            }
+            break
+        case EHistoryEntryType.CASHOUT:
+            direction = 'withdraw'
+            transactionCardType = 'withdraw'
+            nameForDetails = entry.recipientAccount?.identifier || 'Bank Account'
+            isPeerActuallyUser = false
+            break
+        case EHistoryEntryType.DEPOSIT:
+            direction = 'add'
+            transactionCardType = 'add'
+            nameForDetails = entry.senderAccount?.identifier || 'Deposit Source'
+            isPeerActuallyUser = false
+            break
+        default:
+            direction = 'send'
+            transactionCardType = 'send'
+            nameForDetails = entry.recipientAccount?.identifier || 'Unknown'
+            isPeerActuallyUser = !!entry.recipientAccount?.isUser
+            break
+    }
+
+    if (!isPeerActuallyUser) {
+        isPeerActuallyUser = false
+    } else if (entry.recipientAccount?.isUser === false && entry.senderAccount?.isUser === false) {
+        isPeerActuallyUser = false
+    }
+
+    // map the raw status string to the defined ui status types
+    switch (entry.status?.toUpperCase()) {
+        case 'NEW':
+        case 'PENDING':
+            uiStatus = 'pending'
+            break
+        case 'COMPLETED':
+        case 'SUCCESSFUL':
+        case 'CLAIMED':
+        case 'PAID':
+            uiStatus = 'completed'
+            break
+        case 'FAILED':
+        case 'ERROR':
+            uiStatus = 'failed'
+            break
+        case 'CANCELLED':
+        case 'EXPIRED':
+            uiStatus = 'cancelled'
+            break
+        default:
+            {
+                const knownStatuses: StatusType[] = [
+                    'completed',
+                    'pending',
+                    'failed',
+                    'cancelled',
+                    'soon',
+                    'processing',
+                ]
+                if (entry.status && knownStatuses.includes(entry.status.toLowerCase() as StatusType)) {
+                    uiStatus = entry.status.toLowerCase() as StatusType
+                } else {
+                    uiStatus = 'pending'
+                }
+            }
+            break
+    }
+
+    // parse the amount from the usdamount string in extradata
+    const amount = entry.extraData?.usdAmount
+        ? parseFloat(String(entry.extraData.usdAmount).replace(/[^\d.-]/g, ''))
+        : 0
+
+    // construct explorer url if possible
+    let explorerUrlWithTx: string | undefined = undefined
+    if (entry.txHash && entry.chainId) {
+        const baseUrl = getExplorerUrl(entry.chainId)
+        if (baseUrl) {
+            explorerUrlWithTx = `${baseUrl}/tx/${entry.txHash}`
+        }
+    }
+
+    // build the final transactiondetails object for the ui
+    const transactionDetails: TransactionDetails = {
+        id: entry.uuid,
+        direction: direction,
+        userName: nameForDetails,
+        amount: amount,
+        currencySymbol: '$',
+        tokenSymbol: entry.tokenSymbol,
+        initials: getInitialsFromName(nameForDetails),
+        status: uiStatus,
+        isVerified: entry.recipientAccount?.isUser || entry.senderAccount?.isUser || false,
+        date: entry.timestamp,
+        fee: undefined,
+        memo: entry.attachmentUrl
+            ? entry.attachmentUrl
+            : entry.extraData?.link && uiStatus === 'pending'
+              ? entry.extraData.link
+              : undefined,
+        txHash: entry.txHash,
+        explorerUrl: explorerUrlWithTx,
+        extraDataForDrawer: {
+            originalType: entry.type as EHistoryEntryType,
+            originalUserRole: entry.userRole as EHistoryUserRole,
+            link: entry.extraData?.link,
+            isLinkTransaction: isLinkTx,
+            transactionCardType: transactionCardType,
+        },
+    }
+
+    return {
+        transactionDetails,
+        transactionCardType,
+    }
+}
diff --git a/src/hooks/useTransactionDetailsDrawer.ts b/src/hooks/useTransactionDetailsDrawer.ts
new file mode 100644
index 000000000..38e21283f
--- /dev/null
+++ b/src/hooks/useTransactionDetailsDrawer.ts
@@ -0,0 +1,22 @@
+import { TransactionDetails } from '@/components/TransactionDetails/transactionTransformer'
+import { useState } from 'react'
+export const useTransactionDetailsDrawer = () => {
+    const [isDrawerOpen, setIsDrawerOpen] = useState(false)
+    const [selectedTransaction, setSelectedTransaction] = useState<TransactionDetails | null>(null)
+
+    const openTransactionDetails = (transaction: TransactionDetails) => {
+        setSelectedTransaction(transaction)
+        setIsDrawerOpen(true)
+    }
+
+    const closeTransactionDetails = () => {
+        setIsDrawerOpen(false)
+    }
+
+    return {
+        isDrawerOpen,
+        selectedTransaction,
+        openTransactionDetails,
+        closeTransactionDetails,
+    }
+}
diff --git a/src/utils/color.utils.ts b/src/utils/color.utils.ts
new file mode 100644
index 000000000..8db46d86c
--- /dev/null
+++ b/src/utils/color.utils.ts
@@ -0,0 +1,47 @@
+/**
+ * @fileoverview utility functions for generating colors based on strings (e.g., usernames).
+ */
+
+// NOTE: using hex codes cuz tailwind classes works weird with dynamic colors and doesnt really apply all the time, even when using twMerge
+const COLORS_MAPPING = {
+    peanut_pink: '#FF90E8', // peanut pink
+    yellow: '#FFC900', // yellow
+    purple: '#9D7EFE', // purple
+    cyan: '#4CCCEF', // cyan
+    red: '#FF4A4A', // red
+    orange: '#FE8E3E', // orange
+    green: '#29CC6A', // green
+}
+
+// specific colors for different avatar types/contexts
+export const AVATAR_LINK_BG = COLORS_MAPPING.peanut_pink // peanut pink for links
+export const AVATAR_WALLET_BG = COLORS_MAPPING.yellow // yellow for address/non-user/add/withdraw header
+export const AVATAR_DARK_BG = '#000000' // black for add/withdraw card context
+
+// text/icon colors
+export const AVATAR_TEXT_LIGHT = '#FFFFFF' // white
+export const AVATAR_TEXT_DARK = '#000000' // black
+
+/**
+ * Generates a deterministic background color from a predefined list based on a username.
+ * @param username The username string.
+ * @returns An object with { backgroundColor }
+ */
+export function getColorForUsername(username?: string): { backgroundColor: string } {
+    const colors = Object.values(COLORS_MAPPING)
+    if (!username) {
+        // default colors if no username is provided
+        return { backgroundColor: colors[1] } // Default to yellow bg, black text
+    }
+
+    let hash = 0
+    for (let i = 0; i < username.length; i++) {
+        hash = username.charCodeAt(i) + ((hash << 5) - hash)
+        hash = hash & hash
+    }
+
+    const index = Math.abs(hash) % colors.length
+    const backgroundColor = colors[index]
+
+    return { backgroundColor }
+}
diff --git a/src/utils/dateGrouping.utils.ts b/src/utils/dateGrouping.utils.ts
new file mode 100644
index 000000000..bb49558a7
--- /dev/null
+++ b/src/utils/dateGrouping.utils.ts
@@ -0,0 +1,148 @@
+/**
+ * @fileoverview utility functions for grouping and formatting dates for transaction history.
+ */
+
+/**
+ * represents the possible grouping categories for history entries.
+ */
+export enum DateGroup {
+    Today = 'today',
+    Yesterday = 'yesterday',
+    Last7Days = 'last7days',
+    LastMonth = 'lastmonth',
+    Older = 'older', // older means different month but same year
+    OlderYear = 'olderyear', // different year
+}
+
+/**
+ * options for date formatting (e.g., March 30, 2025).
+ */
+const DATE_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    month: 'long',
+    day: 'numeric',
+    year: 'numeric',
+}
+
+const MONTH_YEAR_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    month: 'long',
+    year: 'numeric',
+}
+
+const YEAR_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    year: 'numeric',
+}
+
+/**
+ * checks if two dates are on the same day, ignoring time.
+ * @param date1 first date.
+ * @param date2 second date.
+ * @returns true if they are the same day, false otherwise.
+ */
+function isSameDay(date1: Date, date2: Date): boolean {
+    return (
+        date1.getFullYear() === date2.getFullYear() &&
+        date1.getMonth() === date2.getMonth() &&
+        date1.getDate() === date2.getDate()
+    )
+}
+
+/**
+ * calculates the difference in days between two dates.
+ * @param date1 first date.
+ * @param date2 second date.
+ * @returns the number of days between the two dates.
+ */
+function daysDifference(date1: Date, date2: Date): number {
+    const oneDay = 24 * 60 * 60 * 1000 // hours*minutes*seconds*milliseconds
+    // reset time to midnight for accurate day difference
+    const date1Midnight = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate())
+    const date2Midnight = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())
+    return Math.round(Math.abs((date1Midnight.getTime() - date2Midnight.getTime()) / oneDay))
+}
+
+/**
+ * classifies a date into a specific group relative to today.
+ * @param date the date to classify.
+ * @param today the current date (passed for consistency, defaults to new date()).
+ * @returns the date group category.
+ */
+export function getDateGroup(date: Date, today: Date = new Date()): DateGroup {
+    const daysDiff = daysDifference(today, date)
+
+    if (isSameDay(date, today)) {
+        return DateGroup.Today
+    }
+    if (daysDiff === 1) {
+        return DateGroup.Yesterday
+    }
+    if (daysDiff <= 7) {
+        return DateGroup.Last7Days
+    }
+    // check if it's in the previous month but same year
+    const prevMonth = (today.getMonth() + 11) % 12
+    const sameCalYear = date.getFullYear() === today.getFullYear()
+    if (sameCalYear && date.getMonth() === prevMonth) {
+        return DateGroup.LastMonth
+    }
+    // check if it's in the same year but different month (and not last month)
+    if (date.getFullYear() === today.getFullYear()) {
+        return DateGroup.Older
+    }
+    // otherwise, it's in a different year
+    return DateGroup.OlderYear
+}
+
+/**
+ * formats a date into a string suitable for a group header.
+ * @param date the date to format.
+ * @param group the date group category it belongs to.
+ * @param today the current date (optional).
+ * @returns a formatted date string for the header.
+ */
+export function formatGroupHeaderDate(date: Date, group: DateGroup, today: Date = new Date()): string {
+    switch (group) {
+        case DateGroup.Today:
+            return 'today'
+        case DateGroup.Yesterday:
+            return 'yesterday'
+        case DateGroup.Last7Days:
+        case DateGroup.LastMonth:
+        case DateGroup.Older:
+            // show full date (month day, year) for all other past dates within the same or previous years
+            return date.toLocaleDateString('en-us', DATE_FORMAT_OPTIONS)
+        case DateGroup.OlderYear:
+            // can still optionally show only year for very old entries, or use full date too
+            return date.toLocaleDateString('en-us', DATE_FORMAT_OPTIONS) // using full date for consistency now
+        default:
+            // fallback to full date format
+            return date.toLocaleDateString('en-us', DATE_FORMAT_OPTIONS)
+    }
+}
+
+/**
+ * generates a unique key for a date group to detect changes.
+ * @param date the date.
+ * @param group the date group category.
+ * @returns a string key representing the group.
+ */
+export function getDateGroupKey(date: Date, group: DateGroup): string {
+    switch (group) {
+        case DateGroup.Today:
+        case DateGroup.Yesterday:
+            return group // these are unique enough
+        case DateGroup.Last7Days:
+            // key by day within last 7 days
+            return `${date.getFullYear()}-${(date.getMonth() + 1)
+                .toString()
+                .padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}` // yyyy-mm-dd
+        case DateGroup.LastMonth:
+        case DateGroup.Older:
+            // key by month and year
+            return date.toISOString().slice(0, 7) // yyyy-mm
+        case DateGroup.OlderYear:
+            // key by year
+            return date.getFullYear().toString()
+        default:
+            return date.toISOString() // full iso string as fallback
+    }
+}
diff --git a/src/utils/general.utils.ts b/src/utils/general.utils.ts
index ad3b2c627..7f2c4dfeb 100644
--- a/src/utils/general.utils.ts
+++ b/src/utils/general.utils.ts
@@ -1,12 +1,11 @@
-import { TransactionType } from '@/components/Global/ListItemView'
 import * as consts from '@/constants'
 import {
+    PEANUT_WALLET_SUPPORTED_TOKENS,
     PINTA_WALLET_CHAIN,
     PINTA_WALLET_TOKEN,
     PINTA_WALLET_TOKEN_DECIMALS,
     PINTA_WALLET_TOKEN_NAME,
     PINTA_WALLET_TOKEN_SYMBOL,
-    PEANUT_WALLET_SUPPORTED_TOKENS,
     STABLE_COINS,
 } from '@/constants'
 import * as interfaces from '@/interfaces'
@@ -1046,25 +1045,6 @@ export function isStableCoin(tokenSymbol: string): boolean {
     return STABLE_COINS.includes(tokenSymbol.toUpperCase())
 }
 
-export const getHistoryTransactionStatus = (type: TransactionType | undefined, status: string | undefined): string => {
-    if (!status || !type) return 'pending'
-
-    switch (type) {
-        case 'Link Sent':
-            return ['claimed', 'pending', 'unclaimed'].includes(status.toLowerCase()) ? status : 'pending'
-        case 'Link Received':
-            return ['claimed', 'pending'].includes(status.toLowerCase()) ? status : 'pending'
-        case 'Money Requested':
-            return ['claimed', 'paid', 'canceled'].includes(status.toLowerCase()) ? status : 'pending'
-        case 'Request paid':
-            return ['claimed', 'paid'].includes(status.toLowerCase()) ? status : 'pending'
-        case 'Cash Out':
-            return ['pending', 'successful', 'error'].includes(status.toLowerCase()) ? status : 'pending'
-        default:
-            return status
-    }
-}
-
 export const saveRedirectUrl = () => {
     const currentUrl = new URL(window.location.href)
     saveToLocalStorage('redirect', `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`)
diff --git a/tailwind.config.js b/tailwind.config.js
index 9b354ac7a..8b2b0cf45 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -45,6 +45,7 @@ module.exports = {
                     3: '#fffae8',
                     4: '#AE7AFF',
                     5: '#EDE4FD',
+                    6: '#9D7EFE',
                 },
                 yellow: {
                     1: '#FFC900',
@@ -52,6 +53,8 @@ module.exports = {
                     3: '#fbfdd8',
                     4: '#FAE8A4',
                     5: '#FFD25C',
+                    6: '#885B00',
+                    7: '#FFE6B3',
                 },
                 pink: {
                     1: '#FF90E8',
@@ -83,12 +86,17 @@ module.exports = {
                     9: '#F1EBF8',
                 },
                 cyan: {
-                    8: '#A0E6E0',
+                    1: '#4CCCEF',
+                },
+                orange: {
+                    1: '#FE8E3E',
                 },
                 success: {
                     1: '#16B413',
                     2: '#C7F9C6',
                     3: '#29CC6A',
+                    4: '#1C6A50',
+                    5: '#88D987',
                 },
                 white: '#FFFFFF',
                 red: '#FF0000',
@@ -103,6 +111,8 @@ module.exports = {
                 error: {
                     DEFAULT: '#B3261E',
                     1: '#FFD8D8',
+                    2: '#EA8282',
+                    3: '#FF4A4A',
                 },
             },
             zIndex: {


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>src/components/Global/Badges/StatusBadge.tsx (1)</summary><blockquote>

`9-9`: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Potential breaking change in size prop values**

The size prop type has changed from `'sm' | 'md' | 'lg'` to `'small' | 'medium' | 'large'`, which could break existing usage.


Run the following script to check if there are any existing usages of the old size prop values:

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for any instances of size='sm', size='md', or size='lg' in StatusBadge usage
rg "StatusBadge.*size=['\"](?:sm|md|lg)['\"]" --type ts --type tsx
```

Length of output: 100

---

To account for the unrecognized `tsx` type, let’s broaden the search across all relevant extensions:

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for any instances of size='sm', size='md', or size='lg' in TS/TSX/JS/JSX files
rg -n "size=['\"](?:sm|md|lg)['\"]" -g "*.ts" -g "*.tsx" -g "*.js" -g "*.jsx"
```

Length of output: 83

---

Let’s narrow the search to only usages of `<StatusBadge>` with a size attribute:

---

🏁 Script executed:

```shell
#!/bin/bash
# Find any <StatusBadge … size="sm|md|lg" /> in TS/JS files
rg -n "<StatusBadge[^>]*size\s*=\s*['\"](?:sm|md|lg)['\"]" -g "*.ts" -g "*.tsx" -g "*.js" -g "*.jsx"
```

Length of output: 106

---



</details>

**Address breaking change for StatusBadge size prop**

Our searches across `.ts`, `.tsx`, `.js`, and `.jsx` files didn’t find any internal usages of the old values (`'sm'`, `'md'`, `'lg'`). However, changing a public API from shorthand to full-size strings is a breaking change for any external consumers. Please:

- Update the project’s CHANGELOG or migration guide to note that `size` now accepts `'small' | 'medium' | 'large'` instead of `'sm' | 'md' | 'lg'`.
- Bump the package’s major version to reflect this breaking change.
- (Optional) Consider adding a backward‐compatibility layer or alias mapping so existing code doesn’t immediately break.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (10)</summary><blockquote>

<details>
<summary>src/components/Home/MockHistoryEntries.tsx (2)</summary><blockquote>

`9-37`: **Transaction mapping logic could be simplified**

The type determination logic in the component is somewhat complex with nested ternary operators. Consider extracting this logic to a separate function for better readability and maintainability.

```diff
 const MockHistoryEntries: React.FC<MockHistoryEntriesProps> = ({ transactions }) => {
+    const getTransactionType = (direction: string): 'send' | 'request' | 'add' => {
+        if (['send', 'withdraw', 'request_received'].includes(direction)) {
+            return 'send'
+        } else if (direction === 'request_sent') {
+            return 'request'
+        } else {
+            return 'add'
+        }
+    }
+
     return (
         <div className="mt-6 space-y-3">
             <h2 className="font-bold">Recent Activity (Mock)</h2>
             <div className="space-y-0">
                 {transactions.map((tx, index) => (
                     <TransactionCard
                         key={tx.id}
-                        type={
-                            tx.direction === 'send' ||
-                            tx.direction === 'withdraw' ||
-                            tx.direction === 'request_received'
-                                ? 'send'
-                                : tx.direction === 'request_sent'
-                                  ? 'request'
-                                  : 'add'
-                        }
+                        type={getTransactionType(tx.direction)}
                         name={tx.userName}
                         amount={Number(tx.amount)}
                         status={tx.status}
                         initials={tx.initials}
                         transaction={tx}
                         position={index === 0 ? 'first' : index === transactions.length - 1 ? 'last' : 'middle'}
                     />
                 ))}
             </div>
         </div>
     )
 }
```

---

`11-12`: **Consider using a more descriptive heading**

The heading "Recent Activity (Mock)" clearly indicates this is mock data, but in a production environment, you might want to make this configurable or remove the "(Mock)" part.

</blockquote></details>
<details>
<summary>src/hooks/useTransactionDetailsDrawer.ts (1)</summary><blockquote>

`13-15`: **Consider adding a reset functionality**

The `closeTransactionDetails` function only closes the drawer but doesn't reset the selected transaction. Consider adding an option to clear the selected transaction data when the drawer is closed for cleaner state management.

```diff
 const closeTransactionDetails = () => {
     setIsDrawerOpen(false)
+    // Optionally reset the transaction data after a delay to allow for exit animations
+    // setTimeout(() => setSelectedTransaction(null), 300)
 }
```

Alternatively, you might want to create a separate function that explicitly clears both states:

```javascript
const resetTransactionDetails = () => {
    setIsDrawerOpen(false)
    setSelectedTransaction(null)
}
```

</blockquote></details>
<details>
<summary>src/app/(mobile-ui)/home/page.tsx (2)</summary><blockquote>

`182-184`: **Add TODO comment for removing mock data**

The comment indicates this is temporary, but there's no clear tracking for when it should be removed.


Consider adding a more specific TODO comment with tracking information:

```diff
-                {/* temp mock history entries */}
+                {/* TODO: Remove mock history entries when API integration is complete (JIRA-XXX) */}
                 <MockHistoryEntries transactions={mockTransactions} />
```

---

`37-37`: **Fix mismatch of transaction Date object usage in mock data**

You're using `new Date()` objects in your mock data, which is good for realism, but the future date (2025-03-31) may cause confusion when testing.


Consider using more realistic dates or adding a comment explaining the future date:

```diff
-        date: new Date('2025-03-31T16:20:00Z'),
+        date: new Date('2024-07-20T16:20:00Z'), // Recent date for testing
```

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)</summary><blockquote>

`13-29`: **Use TypeScript utility types for better maintainability**

The transaction interface is marked as temporary, but it has several optional properties. Consider using TypeScript utility types to better organize required vs optional fields.


```diff
-export interface TransactionDetails {
-    id: string
-    direction: TransactionDirection
-    userName: string
-    amount: number | bigint
-    currencySymbol?: string
-    tokenSymbol?: string
-    initials: string
-    status?: StatusType
-    isVerified?: boolean
-    date: string | Date
-    fee?: number | string
-    memo?: string
-    txHash?: string
-    explorerUrl?: string
-    issueReportLink?: string
-}
+// Base required properties
+export interface BaseTransactionDetails {
+    id: string
+    direction: TransactionDirection
+    userName: string
+    amount: number | bigint
+    initials: string
+    date: string | Date
+}
+
+// Optional properties
+export interface OptionalTransactionDetails {
+    currencySymbol: string
+    tokenSymbol: string
+    status: StatusType
+    isVerified: boolean
+    fee: number | string
+    memo: string
+    txHash: string
+    explorerUrl: string
+    issueReportLink: string
+}
+
+// Combined interface with required and optional properties
+export type TransactionDetails = BaseTransactionDetails & Partial<OptionalTransactionDetails>
```

</blockquote></details>
<details>
<summary>src/components/Home/TransactionCard.tsx (2)</summary><blockquote>

`35-43`: **Unnecessary type conversion between number and BigInt**

The code converts the number amount to BigInt for formatting, which adds unnecessary overhead.


Consider updating the formatting utility to handle both number and BigInt directly:

```diff
-    const displayAmount = isNegative
-        ? `-$${formatExtendedNumber(printableUsdc(BigInt(amount)))}`
-        : `+$${formatExtendedNumber(printableUsdc(BigInt(amount)))}`
+    const amountValue = typeof amount === 'bigint' ? amount : BigInt(amount)
+    const displayAmount = isNegative
+        ? `-$${formatExtendedNumber(printableUsdc(amountValue))}`
+        : `+$${formatExtendedNumber(printableUsdc(amountValue))}`

-    const finalAmount =
-        type === 'request' || type === 'send'
-            ? `$${formatExtendedNumber(printableUsdc(BigInt(amount)))}`
-            : displayAmount
+    const finalAmount =
+        type === 'request' || type === 'send'
+            ? `$${formatExtendedNumber(printableUsdc(amountValue))}`
+            : displayAmount
```

---

`79-82`: **Optimize drawer rendering condition**

The drawer is conditionally rendered based on two separate checks which could be combined for clarity.


```diff
-                isOpen={isDrawerOpen && selectedTransaction?.id === transaction.id}
-                onClose={closeTransactionDetails}
-                transaction={selectedTransaction}
+                isOpen={isDrawerOpen && selectedTransaction?.id === transaction.id}
+                onClose={closeTransactionDetails}
+                transaction={isDrawerOpen && selectedTransaction?.id === transaction.id ? selectedTransaction : null}
```

</blockquote></details>
<details>
<summary>src/components/Global/Badges/StatusBadge.tsx (1)</summary><blockquote>

`15-27`: **Inconsistent border styling across status types**

You've added border styling to most statuses, but the 'soon' status is missing the border property, creating inconsistency.


Add border styling to the 'soon' status for consistency:

```diff
            case 'soon':
-                return 'bg-primary-3 text-primary-4'
+                return 'bg-primary-3 text-primary-4 border border-primary-5'
```

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (1)</summary><blockquote>

`18-50`: **Inconsistency in userName display for some transaction types.**

For 'withdraw' and 'add' cases, the userName isn't included in the returned text, unlike the other cases.

Consider updating the 'withdraw' and 'add' cases to include the userName for consistency:

```diff
        case 'withdraw':
            return (
                <span className="flex items-center gap-1">
-                    <Icon name="arrow-up" size={12} className="text-grey-1" /> Withdrawing to
+                    <Icon name="arrow-up" size={12} className="text-grey-1" /> Withdrawing to {userName}
                </span>
            )
        case 'add':
            return (
                <span className="flex items-center gap-1">
-                    <Icon name="arrow-down" size={12} className="text-grey-1" /> Added from
+                    <Icon name="arrow-down" size={12} className="text-grey-1" /> Added from {userName}
                </span>
            )
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 5cc4fe912edbbe8d9080ce7f768b1ccbc85bb18d and bcdf8f03e44f706718970f7edd4177d093d13b44.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `src/app/(mobile-ui)/home/page.tsx` (3 hunks)
* `src/components/Global/Badges/StatusBadge.tsx` (4 hunks)
* `src/components/Global/PeanutLoading/index.tsx` (1 hunks)
* `src/components/Home/MockHistoryEntries.tsx` (1 hunks)
* `src/components/Home/TransactionCard.tsx` (3 hunks)
* `src/components/TransactionDetails/TransactionDetailsDrawer.tsx` (1 hunks)
* `src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx` (1 hunks)
* `src/hooks/useTransactionDetailsDrawer.ts` (1 hunks)
* `tailwind.config.js` (3 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>src/hooks/useTransactionDetailsDrawer.ts (1)</summary><blockquote>

<details>
<summary>src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)</summary>

* `TransactionDetails` (13-29)

</details>

</blockquote></details>
<details>
<summary>src/app/(mobile-ui)/home/page.tsx (1)</summary><blockquote>

<details>
<summary>src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)</summary>

* `TransactionDetails` (13-29)

</details>

</blockquote></details>
<details>
<summary>src/components/Home/MockHistoryEntries.tsx (1)</summary><blockquote>

<details>
<summary>src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)</summary>

* `TransactionDetails` (13-29)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Deploy-Preview

</details>

<details>
<summary>🔇 Additional comments (16)</summary><blockquote>

<details>
<summary>src/components/Global/PeanutLoading/index.tsx (1)</summary>

`9-10`: **Positioning enhancement looks good**

The addition of `left-0` class ensures proper horizontal positioning of the full-screen loading overlay, anchoring it correctly to the left edge of the viewport.

</details>
<details>
<summary>tailwind.config.js (3)</summary>

`55-56`: **New yellow color variants added correctly**

These new yellow color shades (a dark brown and light beige) will likely be used for transaction status indicators or other UI elements in the transaction history feature.

---

`94-95`: **New success color variants added correctly**

These additional success color shades provide more options for indicating successful transaction states in the new transaction history feature.

---

`110-110`: **New error color variant added correctly**

This medium-tone error color provides an intermediate option between the default error color and the lighter error.1 shade, useful for various error states in the transaction history UI.

</details>
<details>
<summary>src/components/Home/MockHistoryEntries.tsx (2)</summary>

`1-4`: **Imports are appropriate and complete**

The component imports the necessary dependencies including React, TransactionCard component, and the TransactionDetails type.

---

`5-7`: **Props interface is well-defined**

The `MockHistoryEntriesProps` interface clearly defines that this component expects an array of transaction objects.

</details>
<details>
<summary>src/hooks/useTransactionDetailsDrawer.ts (2)</summary>

`1-3`: **Imports are minimal and appropriate**

The hook correctly imports only what it needs: useState from React and the TransactionDetails type.

---

`4-23`: **Hook implementation is clean and functional**

The hook follows React best practices with proper state management and clear function interfaces. It effectively separates concerns by handling only the drawer state and transaction selection logic.

</details>
<details>
<summary>src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)</summary>

`79-79`: **Using initials from utils may not match transaction initials**

You're calculating initials from the username using `getInitialsFromName`, but the transaction object already has an `initials` property that might contain a custom value.


Verify whether you should use the transaction's existing initials property or recalculate them:

```diff
-                    initials={getInitialsFromName(transaction.userName)}
+                    initials={transaction.initials || getInitialsFromName(transaction.userName)}
```

</details>
<details>
<summary>src/components/Home/TransactionCard.tsx (1)</summary>

`14-14`: **Type change from bigint to number may cause inconsistencies**

The `amount` prop type has been changed from `bigint` to `number` which could cause inconsistencies across the application.


Verify that all consumers of this component pass numbers instead of bigints. Also consider using a union type to support both:

```diff
-    amount: number
+    amount: number | bigint
```

</details>
<details>
<summary>src/components/Global/Badges/StatusBadge.tsx (1)</summary>

`18-19`: **Good consistency between 'pending' and 'processing' states**

Using the same visual styling for both 'pending' and 'processing' statuses maintains a consistent user experience for in-progress transactions.

</details>
<details>
<summary>src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (5)</summary>

`1-6`: **Imports look good.**

The component correctly imports necessary dependencies and maintains good organization.

---

`7-8`: **Well-defined transaction direction type.**

Good use of TypeScript union type to enforce specific transaction direction values.

---

`9-16`: **Props interface is well structured.**

The interface clearly defines all required and optional props with appropriate types.

---

`52-77`: **Clean component implementation with good layout structure.**

The component has good organization with a clear card layout. The absolute positioning of the status badge in the bottom right corner is an effective design choice for this use case.

---

`61-75`: **Consider handling long transaction amounts.**

For very long transaction amounts, the current layout might encounter spacing issues with the avatar and status badge.

Could you verify how the component handles large transaction amounts? Consider adding overflow handling or text truncation for the amount display to maintain the layout integrity.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T10:17:41Z
user: login: coderabbitai[bot]
body: > [!CAUTION]
> Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.  If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

**Actionable comments posted: 9**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)</summary><blockquote>

`145-150`: **Use transaction-specific support link (repeat feedback)**

The drawer still hard-codes `/support`; previous review suggested using `transaction.issueReportLink` if present.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (11)</summary><blockquote>

<details>
<summary>src/components/Profile/components/PublicProfile.tsx (1)</summary><blockquote>

`142-175`: **Remove large commented-out JSX block once no longer needed**

The multi-line comment that starts at line 144 wraps an entire `<div>` containing four `<TransactionCard>` examples. While comments are harmless at runtime, leaving >30 lines of dead JSX in production code complicates maintenance and increases merge-conflict surface. If this sample is only for reference, consider moving it to a Storybook story, a markdown design doc, or delete it altogether once the real data source is in place.

```diff
-{/* <div>
-   … demo <TransactionCard>s …
-</div> */}
```

Short-term, at least add a `TODO(kushagrasarathe): remove sample cards once live data wired` to avoid it becoming stale.

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/TransactionAvatarBadge.tsx (2)</summary><blockquote>

`7-15`: **Reuse the shared `AvatarSize` type to avoid silent divergence**

`PeerAvatarSize` duplicates the literal union already exported as `AvatarSize` in `AvatarWithBadge`. Keeping two parallel unions invites drift (e.g. one file adds `'extra-large'` and the other forgets). Consider importing and re-exporting the same alias.

```diff
-import type { AvatarSize as PeerAvatarSize } from '@/components/Profile/AvatarWithBadge'
-type PeerAvatarSize = 'extra-small' | 'small' | 'medium' | 'large'
+import type { AvatarSize as PeerAvatarSize } from '@/components/Profile/AvatarWithBadge'
```

---

`54-63`: **Prop drilling: rely on AvatarWithBadge default background when color not set**

If neither `inlineStyle` nor Tailwind `bg-*` classes are provided, the avatar becomes transparent, potentially invisible on light containers. Either:

* keep the previous neutral background (`bg-neutral-200`) as a fallback in `AvatarWithBadge`, or
* guarantee `calculatedBgColor` always has a value (e.g. default `#E5E7EB`).

Helps accessibility and design consistency.

</blockquote></details>
<details>
<summary>src/components/Profile/AvatarWithBadge.tsx (1)</summary><blockquote>

`34-47`: **Map avatar size to icon size once and memoise**

`iconSizeMap` is recreated on every render. Move it outside the component or wrap in `useMemo`:

```diff
-const iconSizeMap: Record<AvatarSize, number> = { … }
+const ICON_SIZE_MAP: Record<AvatarSize, number> = { … } // module-level
```

Micro-optimisation but free.

</blockquote></details>
<details>
<summary>src/components/Home/HomeHistory.tsx (2)</summary><blockquote>

`26-33`: **Typo in Tailwind class breaks font sizing**

`text-baes` should be `text-base`; the current class name will be ignored, resulting in inconsistent typography.

```diff
-<h2 className="text-baes font-bold">Recent Transactions</h2>
+<h2 className="text-base font-bold">Recent Transactions</h2>
```

---

`44-46`: **Avoid non-null assertion when the value has already been guarded**

`historyData!.entries` is safe here, but the `!` is unnecessary because the preceding `&&` check already guarantees `historyData` is defined. Dropping it makes the code a bit clearer.

```diff
-    historyData!.entries.map((item) => (
+    historyData.entries.map((item) => (
```

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/TransactionCard.tsx (1)</summary><blockquote>

`71-74`: **Duplicate address-validation logic**

`printableAddress` already performs an `isAddress` check internally. The additional `isAddress(name)` here is redundant.

```diff
-{isAddress(name) ? printableAddress(name) : name}
+{printableAddress(name)}
```

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/TransactionDetailsDrawer.tsx (1)</summary><blockquote>

`50-54`: **Guard against undefined amount/fee values**

`formatAmount` coerces `undefined` to `0`, which could mislead users. Add explicit checks to show “N/A” when `amount` or `fee` are missing.

```diff
-const amountDisplay = formatAmount(transaction.amount as number)
+const amountDisplay =
+    transaction.amount !== undefined ? formatAmount(transaction.amount as number) : 'N/A'
```

</blockquote></details>
<details>
<summary>src/components/Global/ListItemView/index.tsx (1)</summary><blockquote>

`156-166`: **Double copy: the click-handler already copies the link**

`onClick` triggers `copyTextToClipboardWithFallback`, and the child `CopyToClipboard` component will most likely do the same. Users therefore get **two copy attempts** (and possibly two toast notifications).

Consider removing one of them, e.g.:

```diff
- onClick={() => {
-     copyTextToClipboardWithFallback(historyItem.extraData?.link ?? '')
- }}
+ /* Let <CopyToClipboard/> handle the copy action itself */
```

This keeps UX clean and avoids duplicate analytics / Sentry events.

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/transactionTransformer.ts (1)</summary><blockquote>

`168-172`: **Amount parsing is fragile and loses precision**

`usdAmount` is parsed with `parseFloat`, converting potentially large / high-precision
values (e.g., “1234.567890”) to a JS `number`, introducing rounding errors.

If the UI only needs a user-friendly string, keep it as‐is; otherwise store the raw
`bigint` and format at render time:

```diff
-const amount = entry.extraData?.usdAmount
-    ? parseFloat(String(entry.extraData.usdAmount).replace(/[^\d.-]/g, ''))
-    : 0
+const amount = entry.extraData?.usdAmount
+    ? BigInt(
+          String(entry.extraData.usdAmount)
+              .replace(/[^\d.-]/g, '')
+              .replace('.', '')
+      )
+    : 0n
```

Or, if precision is not a concern, at least add `Number.isFinite(amount)` validation.

</blockquote></details>
<details>
<summary>src/hooks/useTransactionHistory.ts (1)</summary><blockquote>

`94-110`: **API errors lose status code context**

Only `response.statusText` is surfaced.  
Include the numeric `status` to help debugging and Sentry grouping:

```diff
-    throw new Error(`Failed to fetch history: ${response.statusText}`)
+    throw new Error(
+        `Failed to fetch history: ${response.status} ${response.statusText}`
+    )
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🛑 Comments failed to post (9)</summary><blockquote>

<details>
<summary>src/components/TransactionDetails/TransactionAvatarBadge.tsx (1)</summary><blockquote>

44-48: _⚠️ Potential issue_

**`getColorForUsername` receives `undefined` when `userName` is missing**

If `initials` are provided but `userName` is `undefined` (e.g. legacy records that only contain initials), we still call `getColorForUsername(userName)` which will receive `undefined`. Unless the util explicitly guards for this case, this will throw.

```diff
-const { backgroundColor } = getColorForUsername(userName)
+const { backgroundColor } = getColorForUsername(userName ?? '')
```

Add an early return or default string inside the util if you prefer.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
        // if we have initials
        // calculate a background color based on their username
-       const { backgroundColor } = getColorForUsername(userName)
+       const { backgroundColor } = getColorForUsername(userName ?? '')
        calculatedBgColor = backgroundColor
    } else {
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>src/components/Profile/AvatarWithBadge.tsx (1)</summary><blockquote>

51-62: _⚠️ Potential issue_

**Add default background colour to avoid invisible avatars**

`inlineStyle` is optional, and we removed the fixed Tailwind `bg-neutral-200` class. When parent components don’t supply a colour the circle becomes transparent. Restore a safe fallback:

```diff
-`flex items-center justify-center rounded-full font-bold text-black`,
+`flex items-center justify-center rounded-full font-bold text-black bg-neutral-200`,
```

or programmatically inject a default when `inlineStyle?.backgroundColor` is falsy.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
            <div
                className={twMerge(
-                   `flex items-center justify-center rounded-full font-bold text-black`,
+                   `flex items-center justify-center rounded-full font-bold text-black bg-neutral-200`,
                    sizeClasses[size],
                    className
                )}
                // apply dynamic styles (e.g., background color)
                style={{ ...inlineStyle }}
            >
                {/* display icon if provided, otherwise display initials */}
                {icon ? <Icon name={icon} size={iconSizeMap[size]} /> : initials}
            </div>
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>src/app/(mobile-ui)/history/page.tsx (2)</summary><blockquote>

115-116: _⚠️ Potential issue_

**Possible precision loss converting big-unit amounts with `Number()`**

`transactionDetails.amount` looks like a wei/BigInt amount. Casting to `Number` can silently truncate values > 2⁵³-1. Pass a `bigint` or string to the `TransactionCard` and let the card handle formatting.

```diff
-amount={Number(transactionDetails.amount)}
+amount={transactionDetails.amount}
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
                                    amount={transactionDetails.amount}
                                    status={transactionDetails.status}
`````

</details>

<!-- suggestion_end -->

---

92-100: _🛠️ Refactor suggestion_

**`O(n²)` work inside render loop – pre-compute once**

`totalItemsAcrossAllPages` and `currentItemAbsoluteIndex` are recomputed for every single entry, each time reducing across all pages. With large histories this degrades scrolling performance.

```diff
-const totalItemsAcrossAllPages = historyData.pages.reduce(…)
-const currentItemAbsoluteIndex = historyData.pages
-  .slice(0, pageIndex)
-  .reduce((acc, currPage) => acc + currPage.entries.length, 0) + itemIndex
+// compute once per render
+const pageOffsets = historyData.pages.reduce<number[]>(
+  (acc, p, i) => [...acc, (acc[i - 1] ?? 0) + p.entries.length],
+  []
+)
+const totalItemsAcrossAllPages = pageOffsets.at(-1) ?? 0
+const currentItemAbsoluteIndex = pageOffsets[pageIndex - 1] ?? 0 + itemIndex
```

Or simpler: keep a running counter while iterating pages.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
                            // compute once per render
                            const pageOffsets = historyData.pages.reduce<number[]>(
                              (acc, p, i) => [...acc, (acc[i - 1] ?? 0) + p.entries.length],
                              []
                            )
                            const totalItemsAcrossAllPages = pageOffsets.at(-1) ?? 0
                            const currentItemAbsoluteIndex = pageOffsets[pageIndex - 1] ?? 0 + itemIndex
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/TransactionCard.tsx (2)</summary><blockquote>

39-41: _🛠️ Refactor suggestion_

**One drawer instance per card causes unnecessary renders**

Every `TransactionCard` gets its own drawer state, so a list of 50 cards creates 50 hidden `BottomDrawer` components and 50 independent `useState` instances.  
Consider hoisting `useTransactionDetailsDrawer` to the list container (or a React Context) so there is exactly one drawer in the DOM.

---

42-48: _⚠️ Potential issue_

**Incorrect sign handling for “send” transactions**

`finalAmount` drops the negative sign for `send` even though the earlier logic treats it as negative. Users will see outgoing payments displayed as positive values.

```diff
-const finalAmount = type === 'request' || type === 'send' ? `$${formatAmount(amount)}` : displayAmount
+const finalAmount = type === 'request' ? `$${formatAmount(amount)}` : displayAmount
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
    // determine if amount should be positive or negative based on card type
    const isNegative = type === 'send' || type === 'withdraw'
    const displayAmount = isNegative ? `-$${formatAmount(amount)}` : `+$${formatAmount(amount)}`

    // for request/send types, always show positive amount with dollar sign
-   const finalAmount = type === 'request' || type === 'send' ? `$${formatAmount(amount)}` : displayAmount
+   const finalAmount = type === 'request' ? `$${formatAmount(amount)}` : displayAmount
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>src/components/Global/ListItemView/index.tsx (1)</summary><blockquote>

51-52: _🛠️ Refactor suggestion_

**Runtime safety: guard against `undefined` `details` before casting**

`details` is blindly cast to `HistoryEntry` when `variant === 'history'`.  
If the caller forgets to pass `details` (or passes `null`), every subsequent optional-chaining read still hits the *object itself* first and will throw (`Cannot convert undefined or null to object`).

```diff
-const historyItem = isHistory ? (details as HistoryEntry) : null
+const historyItem: HistoryEntry | null =
+    isHistory && details ? (details as HistoryEntry) : null
```

Either add this guard (or make `details` mandatory whenever `variant === 'history'`) to avoid a hard runtime crash.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
    const historyItem: HistoryEntry | null =
        isHistory && details ? (details as HistoryEntry) : null
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/transactionTransformer.ts (1)</summary><blockquote>

158-165: _⚠️ Potential issue_

**`const` inside `switch` without braces violates ES spec**

`knownStatuses` is declared directly in the `default` clause.  
Because `switch` does not create its own block scope, this declaration is leaked
to sibling cases and triggers `noSwitchDeclarations` (see Biome hint).

```diff
-        default:
-            const knownStatuses: StatusType[] = [
+        default: {
+            const knownStatuses: StatusType[] = [
                 'completed',
                 'pending',
                 'failed',
                 'cancelled',
                 'soon',
                 'processing',
             ]
-            if (entry.status && knownStatuses.includes(entry.status.toLowerCase() as StatusType)) {
+            if (
+                entry.status &&
+                knownStatuses.includes(entry.status.toLowerCase() as StatusType)
+            ) {
                 uiStatus = entry.status.toLowerCase() as StatusType
             } else {
                 uiStatus = 'pending'
             }
-            break
+            break
+        }
```

Wrap the case body in braces (or hoist the declaration outside the switch) to comply with the language rules and silence the linter.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
        default: {
            const knownStatuses: StatusType[] = [
                'completed',
                'pending',
                'failed',
                'cancelled',
                'soon',
                'processing',
            ]
            if (
                entry.status &&
                knownStatuses.includes(entry.status.toLowerCase() as StatusType)
            ) {
                uiStatus = entry.status.toLowerCase() as StatusType
            } else {
                uiStatus = 'pending'
            }
            break
        }
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 159-159: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.


(lint/correctness/noSwitchDeclarations)

</details>

</details>

</blockquote></details>
<details>
<summary>src/hooks/useTransactionHistory.ts (1)</summary><blockquote>

122-133: _⚠️ Potential issue_

**Block-scope declarations inside `switch` leak across cases**

Variables `password`, `contractVersion`, `depositIdx`, and `tokenDetails` are declared
directly inside the `SEND_LINK` case.  
This violates `noSwitchDeclarations` and can break in sloppy JS runtimes.

```diff
-        case 'SEND_LINK':
-            const password = getFromLocalStorage(`sendLink::password::${entry.uuid}`)
-            const { contractVersion, depositIdx } = extraData
-            link = `${BASE_URL}/claim?c=${entry.chainId}&v=${contractVersion}&i=${depositIdx}#p=${password}`
-            const tokenDetails = getTokenDetails({
-                tokenAddress: entry.tokenAddress as Hash,
-                chainId: entry.chainId,
-            })
-            usdAmount = formatUnits(BigInt(entry.amount), tokenDetails?.decimals ?? 6)
-            tokenSymbol = tokenDetails?.symbol ?? ''
-            break
+        case 'SEND_LINK': {
+            const password = getFromLocalStorage(`sendLink::password::${entry.uuid}`)
+            const { contractVersion, depositIdx } = extraData
+            link = `${BASE_URL}/claim?c=${entry.chainId}&v=${contractVersion}&i=${depositIdx}#p=${password}`
+
+            const tokenDetails = getTokenDetails({
+                tokenAddress: entry.tokenAddress as Hash,
+                chainId: entry.chainId,
+            })
+            usdAmount = formatUnits(BigInt(entry.amount), tokenDetails?.decimals ?? 6)
+            tokenSymbol = tokenDetails?.symbol ?? ''
+            break
+        }
```

Apply the same pattern to the other cases if you introduce lexical declarations.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
                switch (entry.type) {
                    case 'SEND_LINK': {
                        const password = getFromLocalStorage(`sendLink::password::${entry.uuid}`)
                        const { contractVersion, depositIdx } = extraData
                        link = `${BASE_URL}/claim?c=${entry.chainId}&v=${contractVersion}&i=${depositIdx}#p=${password}`

                        const tokenDetails = getTokenDetails({
                            tokenAddress: entry.tokenAddress as Hash,
                            chainId: entry.chainId,
                        })
                        usdAmount = formatUnits(BigInt(entry.amount), tokenDetails?.decimals ?? 6)
                        tokenSymbol = tokenDetails?.symbol ?? ''
                        break
                    }

                    // …other cases…
                }
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 124-124: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.


(lint/correctness/noSwitchDeclarations)

---

[error] 125-125: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.


(lint/correctness/noSwitchDeclarations)

---

[error] 127-130: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.


(lint/correctness/noSwitchDeclarations)

</details>

</details>

</blockquote></details>

</blockquote></details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T17:03:01Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 7**

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>src/app/(mobile-ui)/history/page.tsx (3)</summary><blockquote>

`40-59`: **Clean up the `IntersectionObserver` on unmount to avoid leaks**

You un-observe the element but the observer itself keeps its callback reference alive.  
Call `observer.disconnect()` in the cleanup for full GC friendliness:

```diff
-        return () => {
-            if (currentLoaderRef) {
-                observer.unobserve(currentLoaderRef)
-            }
-        }
+        return () => {
+            if (currentLoaderRef) observer.unobserve(currentLoaderRef)
+            observer.disconnect()
+        }
```

---

`62-65`: **`allEntries` recomputation dependencies**

`historyData` is sufficient; including `historyData?.pages` in the dep list is redundant but harmless.  
Consider guarding against `undefined` pages to avoid an extra array allocation:

```ts
const allEntries = useMemo(
  () => historyData?.pages.flatMap((p) => p.entries) ?? [],
  [historyData?.pages]  // tighter dependency
)
```

---

`85-100`: **`lastGroupHeaderKey` resets each render – OK but confusing**

Because it’s a plain `let`, grouping logic is recomputed every render.  
That works, yet the mutable outer variable is unintuitive; using the previous item in the loop is clearer and avoids side-effects:

```ts
const showHeader = index === 0 ||
  getDateGroupKey(itemDate, group) !== getDateGroupKey(new Date(allEntries[index - 1].timestamp), getDateGroup(itemDate, today))
```

No state required.

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/TransactionAvatarBadge.tsx (1)</summary><blockquote>

`46-46`: **Consider using a more robust user validation method**

The current approach of using `isAddress` to determine if a userName is a valid user might not be comprehensive. A username could be a non-address string but still not represent a valid user in your system.

Consider a more explicit validation method:

```diff
- const isValidUser = userName ? !isAddress(userName) : false
+ // Either check against a known user pattern or validate through a dedicated utility
+ const isValidUser = userName && !isAddress(userName) && userName !== 'Sent via Link' && userName !== 'Received via Link'
```

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/TransactionCard.tsx (1)</summary><blockquote>

`42-47`: **Simplify amount display logic**

The current approach to determine the final amount display is a bit complex with two separate steps.

Consider simplifying this logic:

```diff
- // determine if amount should be positive or negative based on card type
- const isNegative = type === 'send' || type === 'withdraw'
- const displayAmount = isNegative ? `-$${formatAmount(amount)}` : `+$${formatAmount(amount)}`
- 
- // for request/send types, always show positive amount with dollar sign
- const finalAmount = type === 'request' || type === 'send' ? `$${formatAmount(amount)}` : displayAmount

+ // Determine amount display format based on transaction type
+ let finalAmount: string
+ if (type === 'request') {
+   finalAmount = `$${formatAmount(amount)}`
+ } else if (type === 'send' || type === 'withdraw') {
+   finalAmount = `-$${formatAmount(amount)}`
+ } else {
+   finalAmount = `+$${formatAmount(amount)}`
+ }
```

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/transactionTransformer.ts (3)</summary><blockquote>

`133-137`: **Simplify redundant isPeerActuallyUser logic**

The current conditional block is redundant since it sets `isPeerActuallyUser` to false when it's already false or when both accounts are non-users.

This can be simplified to:

```diff
- if (!isPeerActuallyUser) {
-     isPeerActuallyUser = false
- } else if (entry.recipientAccount?.isUser === false && entry.senderAccount?.isUser === false) {
-     isPeerActuallyUser = false
- }

+ // Only check for the case where both accounts are non-users
+ if (isPeerActuallyUser && entry.recipientAccount?.isUser === false && entry.senderAccount?.isUser === false) {
+     isPeerActuallyUser = false
+ }
```

---

`170-172`: **Improve amount parsing robustness**

The current amount parsing logic might benefit from a more robust approach with error handling.

Consider using a dedicated parsing function:

```diff
- const amount = entry.extraData?.usdAmount
-     ? parseFloat(String(entry.extraData.usdAmount).replace(/[^\d.-]/g, ''))
-     : 0

+ const amount = parseAmount(entry.extraData?.usdAmount)

+ // Add this helper function
+ function parseAmount(rawAmount: any): number {
+     if (!rawAmount) return 0
+     try {
+         const cleanedAmount = String(rawAmount).replace(/[^\d.-]/g, '')
+         const parsed = parseFloat(cleanedAmount)
+         return isNaN(parsed) ? 0 : parsed
+     } catch (error) {
+         console.error('Error parsing amount:', error)
+         return 0
+     }
+ }
```

---

`194-200`: **Simplify nested ternary for memo**

The nested ternary expressions for determining the memo value can be difficult to read and maintain.

Consider using a more explicit approach with if statements or helper functions:

```diff
- memo: entry.attachmentUrl
-     ? entry.attachmentUrl
-     : entry.extraData?.link && uiStatus === 'pending'
-       ? entry.extraData.link
-       : undefined,

+ memo: getMemoValue(entry, uiStatus),

+ // Add this helper function
+ function getMemoValue(entry: HistoryEntry, status: StatusType): string | undefined {
+     if (entry.attachmentUrl) {
+         return entry.attachmentUrl
+     }
+     
+     if (entry.extraData?.link && status === 'pending') {
+         return entry.extraData.link
+     }
+     
+     return undefined
+ }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 60973c74d32ffa202b695c3e20443b706dd55c4f and d6c4c455c2cbac0388da9c8a07bdb18f598f7ff6.

</details>

<details>
<summary>📒 Files selected for processing (14)</summary>

* `src/app/(mobile-ui)/history/page.tsx` (3 hunks)
* `src/app/(mobile-ui)/home/page.tsx` (0 hunks)
* `src/components/Global/ListItemView/index.tsx` (0 hunks)
* `src/components/Home/HomeHistory.tsx` (1 hunks)
* `src/components/Payment/History/index.tsx` (0 hunks)
* `src/components/Profile/AvatarWithBadge.tsx` (1 hunks)
* `src/components/TransactionDetails/TransactionAvatarBadge.tsx` (1 hunks)
* `src/components/TransactionDetails/TransactionCard.tsx` (1 hunks)
* `src/components/TransactionDetails/TransactionDetailsDrawer.tsx` (1 hunks)
* `src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx` (1 hunks)
* `src/components/TransactionDetails/transactionTransformer.ts` (1 hunks)
* `src/utils/color.utils.ts` (1 hunks)
* `src/utils/dateGrouping.utils.ts` (1 hunks)
* `src/utils/general.utils.ts` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (3)</summary>

* src/app/(mobile-ui)/home/page.tsx
* src/components/Payment/History/index.tsx
* src/components/Global/ListItemView/index.tsx

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (5)</summary>

* src/components/Home/HomeHistory.tsx
* src/components/TransactionDetails/TransactionDetailsDrawer.tsx
* src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx
* src/utils/color.utils.ts
* src/components/Profile/AvatarWithBadge.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>src/components/TransactionDetails/TransactionAvatarBadge.tsx (3)</summary><blockquote>

<details>
<summary>src/components/TransactionDetails/TransactionCard.tsx (1)</summary>

* `TransactionType` (12-12)

</details>
<details>
<summary>src/components/Global/Icons/Icon.tsx (1)</summary>

* `IconName` (38-72)

</details>
<details>
<summary>src/utils/color.utils.ts (5)</summary>

* `AVATAR_WALLET_BG` (18-18)
* `AVATAR_TEXT_DARK` (23-23)
* `AVATAR_TEXT_LIGHT` (22-22)
* `AVATAR_LINK_BG` (17-17)
* `getColorForUsername` (30-47)

</details>

</blockquote></details>
<details>
<summary>src/components/TransactionDetails/transactionTransformer.ts (4)</summary><blockquote>

<details>
<summary>src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (1)</summary>

* `TransactionDirection` (11-11)

</details>
<details>
<summary>src/components/Global/Badges/StatusBadge.tsx (1)</summary>

* `StatusType` (4-4)

</details>
<details>
<summary>src/hooks/useTransactionHistory.ts (1)</summary>

* `HistoryEntry` (29-56)

</details>
<details>
<summary>src/utils/general.utils.ts (2)</summary>

* `getExplorerUrl` (696-704)
* `getInitialsFromName` (1085-1092)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>src/components/TransactionDetails/transactionTransformer.ts</summary>

[error] 160-160: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.


(lint/correctness/noSwitchDeclarations)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>src/utils/general.utils.ts (1)</summary>

`3-4`: **Unused import check**

`PEANUT_WALLET_SUPPORTED_TOKENS` is added here and used later in `isPeanutWalletToken`, so the import is justified.  
No action needed.

</details>
<details>
<summary>src/components/TransactionDetails/TransactionAvatarBadge.tsx (2)</summary>

`49-93`: **LGTM! Well-structured transaction type handling**

The switch statement logic clearly handles different transaction types with appropriate visual treatments for each context.

---

`97-105`: **Verification badge condition might need expansion**

The verification badge is only shown when initials are present, which might not capture all verification scenarios.

It's worth checking if there are cases where you'd want to show verification for icon-only avatars as well, especially for specific transaction types.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T18:39:52Z
user: login: kushagrasarathe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:51:55Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:52:18Z

Review comments:
diff_hunk: @@ -0,0 +1,39 @@
+import React from 'react'
+import TransactionCard from './TransactionCard'
+import { TransactionDetails } from '../TransactionDetails/TransactionDetailsDrawer'
+
+interface MockHistoryEntriesProps {
+    transactions: TransactionDetails[]
+}
+
+const MockHistoryEntries: React.FC<MockHistoryEntriesProps> = ({ transactions }) => {
+    return (
+        <div className="mt-6 space-y-3">
+            <h2 className="font-bold">Recent Activity (Mock)</h2>
+            <div className="space-y-0">
+                {transactions.map((tx, index) => (
+                    <TransactionCard
+                        key={tx.id}
+                        type={
+                            tx.direction === 'send' ||
+                            tx.direction === 'withdraw' ||
+                            tx.direction === 'request_received'
+                                ? 'send'
+                                : tx.direction === 'request_sent'
+                                  ? 'request'
+                                  : 'add'
+                        }
+                        name={tx.userName}
+                        amount={Number(tx.amount)}
+                        status={tx.status}
+                        initials={tx.initials}
+                        transaction={tx}
+                        position={index === 0 ? 'first' : index === transactions.length - 1 ? 'last' : 'middle'}
+                    />
+                ))}
+            </div>
path: src/components/Home/MockHistoryEntries.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Empty transaction list handling is missing**

The component doesn't handle the case when the transactions array is empty. Consider adding a conditional check to display a "No transactions" message when appropriate.

```diff
 <div className="mt-6 space-y-3">
     <h2 className="font-bold">Recent Activity (Mock)</h2>
     <div className="space-y-0">
+        {transactions.length === 0 ? (
+            <div className="py-4 text-center text-gray-500">No recent transactions</div>
+        ) : (
         {transactions.map((tx, index) => (
             <TransactionCard
                 key={tx.id}
                 type={
                     tx.direction === 'send' ||
                     tx.direction === 'withdraw' ||
                     tx.direction === 'request_received'
                         ? 'send'
                         : tx.direction === 'request_sent'
                           ? 'request'
                           : 'add'
                 }
                 name={tx.userName}
                 amount={Number(tx.amount)}
                 status={tx.status}
                 initials={tx.initials}
                 transaction={tx}
                 position={index === 0 ? 'first' : index === transactions.length - 1 ? 'last' : 'middle'}
             />
         ))}
+        )}
     </div>
 </div>
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
<div className="mt-6 space-y-3">
    <h2 className="font-bold">Recent Activity (Mock)</h2>
    <div className="space-y-0">
        {transactions.length === 0 ? (
            <div className="py-4 text-center text-gray-500">
                No recent transactions
            </div>
        ) : (
            {transactions.map((tx, index) => (
                <TransactionCard
                    key={tx.id}
                    type={
                        tx.direction === 'send' ||
                        tx.direction === 'withdraw' ||
                        tx.direction === 'request_received'
                            ? 'send'
                            : tx.direction === 'request_sent'
                              ? 'request'
                              : 'add'
                    }
                    name={tx.userName}
                    amount={Number(tx.amount)}
                    status={tx.status}
                    initials={tx.initials}
                    transaction={tx}
                    position={
                        index === 0
                            ? 'first'
                            : index === transactions.length - 1
                              ? 'last'
                              : 'middle'
                    }
                />
            ))}        
        )}
    </div>
</div>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-06T10:17:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,11 +23,95 @@ import Link from 'next/link'
 import { useMemo, useState } from 'react'
 import { twMerge } from 'tailwind-merge'
 
+const mockTransactions: TransactionDetails[] = [
+    {
+        id: '1',
+        direction: 'send',
+        userName: 'Hugo Montenegro',
+        amount: 6969.69,
+        currencySymbol: '$',
+        initials: 'HM',
+        status: 'processing',
+        isVerified: false,
+        date: new Date('2025-03-31T16:20:00Z'),
+        fee: 0,
+        memo: 'Freelance Design Job',
+        issueReportLink: '/support/1',
+    },
+    {
+        id: '2',
+        direction: 'receive',
+        userName: 'Alice Wonderland',
+        amount: 150.0,
+        currencySymbol: '$',
+        initials: 'AW',
+        status: 'completed',
+        isVerified: true,
+        date: new Date('2024-07-15T10:30:00Z'),
+        fee: 0,
+        memo: 'Coffee catch up',
+        txHash: '0x123456789abcdef',
+        explorerUrl: 'https://etherscan.io/tx/0x123456789abcdef',
+        issueReportLink: '/support/2',
+    },
+    {
+        id: '3',
+        direction: 'request_sent',
+        userName: 'Bob The Builder',
+        amount: 50.0,
+        currencySymbol: '$',
+        initials: 'BB',
+        status: 'pending',
+        isVerified: false,
+        date: new Date('2024-07-14T09:00:00Z'),
+        memo: 'Lunch money request',
+        issueReportLink: '/support/3',
+    },
+    {
+        id: '4',
+        direction: 'withdraw',
+        userName: 'My Bank Account',
+        amount: 500.0,
+        currencySymbol: '$',
+        initials: 'MB',
+        status: 'completed',
+        isVerified: false,
+        date: new Date('2024-07-13T18:45:00Z'),
+        fee: 1.5,
+        issueReportLink: '/support/4',
+    },
+    {
+        id: '5',
+        direction: 'add',
+        userName: 'External Wallet',
+        amount: 1000.0,
+        currencySymbol: '$',
+        initials: 'EW',
+        status: 'failed',
+        isVerified: false,
+        date: new Date('2024-07-12T12:00:00Z'),
+        memo: 'Funding account - network issue',
+        issueReportLink: '/support/5',
+    },
+    {
+        id: '5',
+        direction: 'withdraw',
+        userName: 'External Wallet',
+        amount: 1000.0,
+        currencySymbol: '$',
+        initials: 'EW',
+        status: 'completed',
+        isVerified: false,
+        date: new Date('2024-07-12T12:00:00Z'),
+        memo: 'Funding account - network issue',
+        issueReportLink: '/support/5',
+    },
+]
path: src/app/(mobile-ui)/home/page.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Duplicate transaction IDs in mock data**

There are two transactions with the same ID "5" at lines 84 and 97. This could cause issues with React keys and uniquely identifying transactions when interacting with them.


Fix the duplicate ID by updating one of them to a unique value:

```diff
-        id: '5',
+        id: '6',
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
const mockTransactions: TransactionDetails[] = [
    {
        id: '1',
        direction: 'send',
        userName: 'Hugo Montenegro',
        amount: 6969.69,
        currencySymbol: '$',
        initials: 'HM',
        status: 'processing',
        isVerified: false,
        date: new Date('2025-03-31T16:20:00Z'),
        fee: 0,
        memo: 'Freelance Design Job',
        issueReportLink: '/support/1',
    },
    {
        id: '2',
        direction: 'receive',
        userName: 'Alice Wonderland',
        amount: 150.0,
        currencySymbol: '$',
        initials: 'AW',
        status: 'completed',
        isVerified: true,
        date: new Date('2024-07-15T10:30:00Z'),
        fee: 0,
        memo: 'Coffee catch up',
        txHash: '0x123456789abcdef',
        explorerUrl: 'https://etherscan.io/tx/0x123456789abcdef',
        issueReportLink: '/support/2',
    },
    {
        id: '3',
        direction: 'request_sent',
        userName: 'Bob The Builder',
        amount: 50.0,
        currencySymbol: '$',
        initials: 'BB',
        status: 'pending',
        isVerified: false,
        date: new Date('2024-07-14T09:00:00Z'),
        memo: 'Lunch money request',
        issueReportLink: '/support/3',
    },
    {
        id: '4',
        direction: 'withdraw',
        userName: 'My Bank Account',
        amount: 500.0,
        currencySymbol: '$',
        initials: 'MB',
        status: 'completed',
        isVerified: false,
        date: new Date('2024-07-13T18:45:00Z'),
        fee: 1.5,
        issueReportLink: '/support/4',
    },
    {
        id: '5',
        direction: 'add',
        userName: 'External Wallet',
        amount: 1000.0,
        currencySymbol: '$',
        initials: 'EW',
        status: 'failed',
        isVerified: false,
        date: new Date('2024-07-12T12:00:00Z'),
        memo: 'Funding account - network issue',
        issueReportLink: '/support/5',
    },
    {
-        id: '5',
+        id: '6',
        direction: 'withdraw',
        userName: 'External Wallet',
        amount: 1000.0,
        currencySymbol: '$',
        initials: 'EW',
        status: 'completed',
        isVerified: false,
        date: new Date('2024-07-12T12:00:00Z'),
        memo: 'Funding account - network issue',
        issueReportLink: '/support/5',
    },
];
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit c0bcd86
created_at: 2025-05-06T10:17:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+import React, { useCallback, useRef } from 'react'
+import BottomDrawer from '@/components/Global/BottomDrawer'
+import { TransactionDetailsHeaderCard, TransactionDirection } from './TransactionDetailsHeaderCard'
+import Card from '@/components/Global/Card'
+import { PaymentInfoRow } from '@/components/Payment/PaymentInfoRow'
+import { StatusType } from '../Global/Badges/StatusBadge'
+import { Icon } from '../Global/Icons/Icon'
+import Link from 'next/link'
+import { formatAmount, formatDate, getInitialsFromName } from '@/utils'
+import { useDynamicHeight } from '@/hooks/ui/useDynamicHeight'
+
+// todo: temporary interface for transaction details, fix based on backend response
+export interface TransactionDetails {
+    id: string
+    direction: TransactionDirection
+    userName: string
+    amount: number | bigint
+    currencySymbol?: string
+    tokenSymbol?: string
+    initials: string
+    status?: StatusType
+    isVerified?: boolean
+    date: string | Date
+    fee?: number | string
+    memo?: string
+    txHash?: string
+    explorerUrl?: string
+    issueReportLink?: string
+}
+
+interface TransactionDetailsDrawerProps {
+    isOpen: boolean
+    onClose: () => void
+    transaction: TransactionDetails | null
+}
+
+export const TransactionDetailsDrawer: React.FC<TransactionDetailsDrawerProps> = ({ isOpen, onClose, transaction }) => {
+    const contentRef = useRef<HTMLDivElement>(null)
+
+    // calculate drawer height based on content
+    const drawerHeightVh = useDynamicHeight(contentRef, {
+        maxHeightVh: 90,
+        minHeightVh: 30,
+        extraVhOffset: 5,
+    })
+
+    // default heights with fallback values
+    const currentExpandedHeight = drawerHeightVh ?? 70
+    const currentHalfHeight = Math.min(60, drawerHeightVh ?? 60)
+
+    const handleClose = useCallback(() => {
+        if (onClose) {
+            onClose()
+        }
+    }, [onClose])
+
+    if (!transaction) return null
+
+    const amountDisplay = formatAmount(transaction.amount as number)
+    const dateDisplay = formatDate(transaction.date as Date)
+    const feeDisplay = transaction.fee !== undefined ? formatAmount(transaction.fee as number) : 'N/A'
+
+    return (
+        <BottomDrawer
+            isOpen={isOpen}
+            onClose={handleClose}
+            initialPosition="expanded"
+            collapsedHeight={5}
+            halfHeight={currentHalfHeight}
+            expandedHeight={currentExpandedHeight}
+            preventScroll={true}
+        >
+            <div ref={contentRef} className="space-y-4 pb-8">
+                {/* Top Header Card */}
+                <TransactionDetailsHeaderCard
+                    direction={transaction.direction}
+                    userName={transaction.userName}
+                    amountDisplay={amountDisplay}
+                    initials={getInitialsFromName(transaction.userName)}
+                    status={transaction.status}
+                    isVerified={transaction.isVerified}
+                />
+                {/* Details Card */}
+                <Card position="single" className="px-4 py-0" border={true}>
+                    <div className="space-y-0">
+                        <PaymentInfoRow label="Date" value={dateDisplay} />
+                        {transaction.fee !== undefined && (
+                            <PaymentInfoRow label="Fee" value={feeDisplay} hideBottomBorder={!transaction.memo} />
+                        )}
+                        {transaction.memo && <PaymentInfoRow label="Memo" value={transaction.memo} hideBottomBorder />}
+                    </div>
+                </Card>
+                {/* support Link */}
+                <div className="mt-4 flex items-center justify-center text-center">
+                    <Link
+                        href={'/support'}
+                        className="inline-flex items-center gap-2 text-sm font-medium text-grey-1 underline transition-colors hover:text-black"
path: src/components/TransactionDetails/TransactionDetailsDrawer.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Use transaction-specific support link**

The transaction interface has an `issueReportLink` property, but you're using a hardcoded '/support' path.


```diff
-                        href={'/support'}
+                        href={transaction.issueReportLink || '/support'}
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-06T10:17:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,106 @@
+import React, { useCallback, useRef } from 'react'
+import BottomDrawer from '@/components/Global/BottomDrawer'
+import { TransactionDetailsHeaderCard, TransactionDirection } from './TransactionDetailsHeaderCard'
+import Card from '@/components/Global/Card'
+import { PaymentInfoRow } from '@/components/Payment/PaymentInfoRow'
+import { StatusType } from '../Global/Badges/StatusBadge'
+import { Icon } from '../Global/Icons/Icon'
+import Link from 'next/link'
+import { formatAmount, formatDate, getInitialsFromName } from '@/utils'
+import { useDynamicHeight } from '@/hooks/ui/useDynamicHeight'
+
+// todo: temporary interface for transaction details, fix based on backend response
+export interface TransactionDetails {
+    id: string
+    direction: TransactionDirection
+    userName: string
+    amount: number | bigint
+    currencySymbol?: string
+    tokenSymbol?: string
+    initials: string
+    status?: StatusType
+    isVerified?: boolean
+    date: string | Date
+    fee?: number | string
+    memo?: string
+    txHash?: string
+    explorerUrl?: string
+    issueReportLink?: string
+}
+
+interface TransactionDetailsDrawerProps {
+    isOpen: boolean
+    onClose: () => void
+    transaction: TransactionDetails | null
+}
+
+export const TransactionDetailsDrawer: React.FC<TransactionDetailsDrawerProps> = ({ isOpen, onClose, transaction }) => {
+    const contentRef = useRef<HTMLDivElement>(null)
+
+    // calculate drawer height based on content
+    const drawerHeightVh = useDynamicHeight(contentRef, {
+        maxHeightVh: 90,
+        minHeightVh: 30,
+        extraVhOffset: 5,
+    })
+
+    // default heights with fallback values
+    const currentExpandedHeight = drawerHeightVh ?? 70
+    const currentHalfHeight = Math.min(60, drawerHeightVh ?? 60)
+
+    const handleClose = useCallback(() => {
+        if (onClose) {
+            onClose()
+        }
+    }, [onClose])
+
+    if (!transaction) return null
+
+    const amountDisplay = formatAmount(transaction.amount as number)
+    const dateDisplay = formatDate(transaction.date as Date)
+    const feeDisplay = transaction.fee !== undefined ? formatAmount(transaction.fee as number) : 'N/A'
+
path: src/components/TransactionDetails/TransactionDetailsDrawer.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add error handling or warning for edge cases**

The amount formatting may fail if the transaction amount is undefined or not a valid number.


```diff
-    const amountDisplay = formatAmount(transaction.amount as number)
+    const amountDisplay = transaction.amount !== undefined 
+        ? formatAmount(transaction.amount as number)
+        : 'N/A'
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
    const amountDisplay = transaction.amount !== undefined
        ? formatAmount(transaction.amount as number)
        : 'N/A'
    const dateDisplay = formatDate(transaction.date as Date)
    const feeDisplay = transaction.fee !== undefined
        ? formatAmount(transaction.fee as number)
        : 'N/A'
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-06T10:17:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,144 @@
+/**
+ * @fileoverview utility functions for grouping and formatting dates for transaction history.
+ */
+
+/**
+ * represents the possible grouping categories for history entries.
+ */
+export enum DateGroup {
+    Today = 'today',
+    Yesterday = 'yesterday',
+    Last7Days = 'last7days',
+    LastMonth = 'lastmonth',
+    Older = 'older', // older means different month but same year
+    OlderYear = 'olderyear', // different year
+}
+
+/**
+ * options for date formatting (e.g., March 30, 2025).
+ */
+const DATE_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    month: 'long',
+    day: 'numeric',
+    year: 'numeric',
+}
+
+const MONTH_YEAR_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    month: 'long',
+    year: 'numeric',
+}
+
+const YEAR_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    year: 'numeric',
+}
+
+/**
+ * checks if two dates are on the same day, ignoring time.
+ * @param date1 first date.
+ * @param date2 second date.
+ * @returns true if they are the same day, false otherwise.
+ */
+function isSameDay(date1: Date, date2: Date): boolean {
+    return (
+        date1.getFullYear() === date2.getFullYear() &&
+        date1.getMonth() === date2.getMonth() &&
+        date1.getDate() === date2.getDate()
+    )
+}
+
+/**
+ * calculates the difference in days between two dates.
+ * @param date1 first date.
+ * @param date2 second date.
+ * @returns the number of days between the two dates.
+ */
+function daysDifference(date1: Date, date2: Date): number {
+    const oneDay = 24 * 60 * 60 * 1000 // hours*minutes*seconds*milliseconds
+    // reset time to midnight for accurate day difference
+    const date1Midnight = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate())
+    const date2Midnight = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())
+    return Math.round(Math.abs((date1Midnight.getTime() - date2Midnight.getTime()) / oneDay))
+}
path: src/utils/dateGrouping.utils.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`daysDifference` silently treats “future” dates as “past” – can yield wrong groups**

`Math.abs` makes the function symmetrical, so a date **3 days in the future** is classified the same as one 3 days in the past (`Last7Days`, `LastMonth`, …).  
If the UI is only supposed to show *past* transactions this is misleading and will surface the wrong headers once client / server clocks differ or future-dated txns slip in.

```diff
-    return Math.round(Math.abs((date1Midnight.getTime() - date2Midnight.getTime()) / oneDay))
+    // Positive if `date1` is after `date2`
+    return Math.round((date1Midnight.getTime() - date2Midnight.getTime()) / oneDay)
```

and then use the **sign** in `getDateGroup` to bail out (e.g. treat positive diff as `Today` by definition, negative as “future / upcoming”).  
Alternatively, explicitly clamp `daysDiff` at `0` if future dates are impossible.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
function daysDifference(date1: Date, date2: Date): number {
    const oneDay = 24 * 60 * 60 * 1000 // hours*minutes*seconds*milliseconds
    // reset time to midnight for accurate day difference
    const date1Midnight = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate())
    const date2Midnight = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())
    // Positive if `date1` is after `date2`
    return Math.round((date1Midnight.getTime() - date2Midnight.getTime()) / oneDay)
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T18:39:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,144 @@
+/**
+ * @fileoverview utility functions for grouping and formatting dates for transaction history.
+ */
+
+/**
+ * represents the possible grouping categories for history entries.
+ */
+export enum DateGroup {
+    Today = 'today',
+    Yesterday = 'yesterday',
+    Last7Days = 'last7days',
+    LastMonth = 'lastmonth',
+    Older = 'older', // older means different month but same year
+    OlderYear = 'olderyear', // different year
+}
+
+/**
+ * options for date formatting (e.g., March 30, 2025).
+ */
+const DATE_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    month: 'long',
+    day: 'numeric',
+    year: 'numeric',
+}
+
+const MONTH_YEAR_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    month: 'long',
+    year: 'numeric',
+}
+
+const YEAR_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    year: 'numeric',
+}
+
+/**
+ * checks if two dates are on the same day, ignoring time.
+ * @param date1 first date.
+ * @param date2 second date.
+ * @returns true if they are the same day, false otherwise.
+ */
+function isSameDay(date1: Date, date2: Date): boolean {
+    return (
+        date1.getFullYear() === date2.getFullYear() &&
+        date1.getMonth() === date2.getMonth() &&
+        date1.getDate() === date2.getDate()
+    )
+}
+
+/**
+ * calculates the difference in days between two dates.
+ * @param date1 first date.
+ * @param date2 second date.
+ * @returns the number of days between the two dates.
+ */
+function daysDifference(date1: Date, date2: Date): number {
+    const oneDay = 24 * 60 * 60 * 1000 // hours*minutes*seconds*milliseconds
+    // reset time to midnight for accurate day difference
+    const date1Midnight = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate())
+    const date2Midnight = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())
+    return Math.round(Math.abs((date1Midnight.getTime() - date2Midnight.getTime()) / oneDay))
+}
+
+/**
+ * classifies a date into a specific group relative to today.
+ * @param date the date to classify.
+ * @param today the current date (passed for consistency, defaults to new date()).
+ * @returns the date group category.
+ */
+export function getDateGroup(date: Date, today: Date = new Date()): DateGroup {
+    const daysDiff = daysDifference(today, date)
+
+    if (isSameDay(date, today)) {
+        return DateGroup.Today
+    }
+    if (daysDiff === 1) {
+        return DateGroup.Yesterday
+    }
+    if (daysDiff <= 7) {
+        return DateGroup.Last7Days
+    }
+    // check if it's in the previous month but same year
+    if (date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() - 1) {
+        return DateGroup.LastMonth
+    }
+    // check if it's in the same year but different month (and not last month)
+    if (date.getFullYear() === today.getFullYear()) {
+        return DateGroup.Older
+    }
path: src/utils/dateGrouping.utils.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**December-vs-January edge case mis-labels “last month” items as “older year”**

`date.getMonth() === today.getMonth() - 1` only works for Feb–Dec.  
When `today` is in **January (month 0)**, `today.getMonth() - 1` is `-1`, and December of the *same* fiscal year (month 11) falls through to `OlderYear`.

```diff
-    if (date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() - 1) {
+    const prevMonth = (today.getMonth() + 11) % 12
+    const sameCalYear = date.getFullYear() === today.getFullYear()
+    if (sameCalYear && date.getMonth() === prevMonth) {
```

This keeps December-vs-January in the correct bucket.  



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

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T18:39:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,144 @@
+/**
+ * @fileoverview utility functions for grouping and formatting dates for transaction history.
+ */
+
+/**
+ * represents the possible grouping categories for history entries.
+ */
+export enum DateGroup {
+    Today = 'today',
+    Yesterday = 'yesterday',
+    Last7Days = 'last7days',
+    LastMonth = 'lastmonth',
+    Older = 'older', // older means different month but same year
+    OlderYear = 'olderyear', // different year
+}
+
+/**
+ * options for date formatting (e.g., March 30, 2025).
+ */
+const DATE_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    month: 'long',
+    day: 'numeric',
+    year: 'numeric',
+}
+
+const MONTH_YEAR_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    month: 'long',
+    year: 'numeric',
+}
+
+const YEAR_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
+    year: 'numeric',
+}
+
+/**
+ * checks if two dates are on the same day, ignoring time.
+ * @param date1 first date.
+ * @param date2 second date.
+ * @returns true if they are the same day, false otherwise.
+ */
+function isSameDay(date1: Date, date2: Date): boolean {
+    return (
+        date1.getFullYear() === date2.getFullYear() &&
+        date1.getMonth() === date2.getMonth() &&
+        date1.getDate() === date2.getDate()
+    )
+}
+
+/**
+ * calculates the difference in days between two dates.
+ * @param date1 first date.
+ * @param date2 second date.
+ * @returns the number of days between the two dates.
+ */
+function daysDifference(date1: Date, date2: Date): number {
+    const oneDay = 24 * 60 * 60 * 1000 // hours*minutes*seconds*milliseconds
+    // reset time to midnight for accurate day difference
+    const date1Midnight = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate())
+    const date2Midnight = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())
+    return Math.round(Math.abs((date1Midnight.getTime() - date2Midnight.getTime()) / oneDay))
+}
+
+/**
+ * classifies a date into a specific group relative to today.
+ * @param date the date to classify.
+ * @param today the current date (passed for consistency, defaults to new date()).
+ * @returns the date group category.
+ */
+export function getDateGroup(date: Date, today: Date = new Date()): DateGroup {
+    const daysDiff = daysDifference(today, date)
+
+    if (isSameDay(date, today)) {
+        return DateGroup.Today
+    }
+    if (daysDiff === 1) {
+        return DateGroup.Yesterday
+    }
+    if (daysDiff <= 7) {
+        return DateGroup.Last7Days
+    }
+    // check if it's in the previous month but same year
+    if (date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() - 1) {
+        return DateGroup.LastMonth
+    }
+    // check if it's in the same year but different month (and not last month)
+    if (date.getFullYear() === today.getFullYear()) {
+        return DateGroup.Older
+    }
+    // otherwise, it's in a different year
+    return DateGroup.OlderYear
+}
+
+/**
+ * formats a date into a string suitable for a group header.
+ * @param date the date to format.
+ * @param group the date group category it belongs to.
+ * @param today the current date (optional).
+ * @returns a formatted date string for the header.
+ */
+export function formatGroupHeaderDate(date: Date, group: DateGroup, today: Date = new Date()): string {
+    switch (group) {
+        case DateGroup.Today:
+            return 'today'
+        case DateGroup.Yesterday:
+            return 'yesterday'
+        case DateGroup.Last7Days:
+        case DateGroup.LastMonth:
+        case DateGroup.Older:
+            // show full date (month day, year) for all other past dates within the same or previous years
+            return date.toLocaleDateString('en-us', DATE_FORMAT_OPTIONS)
+        case DateGroup.OlderYear:
+            // can still optionally show only year for very old entries, or use full date too
+            return date.toLocaleDateString('en-us', DATE_FORMAT_OPTIONS) // using full date for consistency now
+        default:
+            // fallback to full date format
+            return date.toLocaleDateString('en-us', DATE_FORMAT_OPTIONS)
+    }
+}
+
+/**
+ * generates a unique key for a date group to detect changes.
+ * @param date the date.
+ * @param group the date group category.
+ * @returns a string key representing the group.
+ */
+export function getDateGroupKey(date: Date, group: DateGroup): string {
+    switch (group) {
+        case DateGroup.Today:
+        case DateGroup.Yesterday:
+            return group // these are unique enough
+        case DateGroup.Last7Days:
+            // key by day within last 7 days
+            return date.toISOString().slice(0, 10) // yyyy-mm-dd
+        case DateGroup.LastMonth:
+        case DateGroup.Older:
+            // key by month and year
+            return date.toISOString().slice(0, 7) // yyyy-mm
+        case DateGroup.OlderYear:
+            // key by year
+            return date.getFullYear().toString()
path: src/utils/dateGrouping.utils.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Key generation mixes local time logic with UTC strings – can break grouping**

`toISOString()` is UTC; `getDateGroup` relies on *local-midnight* calculations.  
Users near midnight will occasionally see headers split unexpectedly (e.g. a tx at 23:30 local shows as next-day key).

Prefer locale-based, zero-padded components:

```diff
-            return date.toISOString().slice(0, 10) // yyyy-mm-dd
+            return `${date.getFullYear()}-${(date.getMonth() + 1)
+                .toString()
+                .padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`
```

Do the same for the `yyyy-mm` case.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
export function getDateGroupKey(date: Date, group: DateGroup): string {
    switch (group) {
        case DateGroup.Today:
        case DateGroup.Yesterday:
            return group // these are unique enough
        case DateGroup.Last7Days:
            // key by day within last 7 days
            return `${date.getFullYear()}-${(date.getMonth() + 1)
                .toString()
                .padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`
        case DateGroup.LastMonth:
        case DateGroup.Older:
            // key by month and year
            return date.toISOString().slice(0, 7) // yyyy-mm
        case DateGroup.OlderYear:
            // key by year
            return date.getFullYear().toString()
    }
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T18:39:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,60 +71,72 @@ const HistoryPage = () => {
         return <div className="w-full py-4 text-center">Error loading history: {error?.message}</div>
     }
 
-    if (!historyData || historyData.pages.length === 0) {
+    if (allEntries.length === 0) {
         return (
-            <div className="flex h-[80dvh] items-center justify-center">
-                <NoDataEmptyState animSize="lg" message="You haven't done any transactions" />
+            <div className="flex h-[80dvh] flex-col items-center justify-center">
+                <NavHeader title={getHeaderTitle(pathname)} />
+                <div className="flex flex-grow items-center justify-center">
+                    <NoDataEmptyState animSize="lg" message="You haven't done any transactions" />
+                </div>
             </div>
         )
     }
 
+    let lastGroupHeaderKey: string | null = null
+    const today = new Date()
+
     return (
         <div className="mx-auto w-full space-y-6 md:max-w-2xl md:space-y-3">
-            {!!historyData?.pages.length ? <NavHeader title={getHeaderTitle(pathname)} /> : null}
-            <div className="h-full w-full border-t border-n-1">
-                {!!historyData?.pages.length &&
-                    historyData?.pages.map((page, pageIndex) => (
-                        <div key={pageIndex}>
-                            {page.entries.map((item) => (
-                                <div key={item.uuid}>
-                                    <ListItemView
-                                        id={item.uuid}
-                                        variant="history"
-                                        primaryInfo={{
-                                            title: (
-                                                <div className="flex flex-col items-start gap-2 md:flex-row md:items-center ">
-                                                    <div className="font-bold">
-                                                        {item.type} {item.userRole}
-                                                    </div>
-                                                    <div className="flex flex-col items-end justify-end gap-2 text-end">
-                                                        <TransactionBadge status={item.status as string} />
-                                                    </div>
-                                                </div>
-                                            ),
-                                            subtitle: !!item.recipientAccount.identifier && (
-                                                <div
-                                                    className="text-xs text-gray-1"
-                                                    onClick={(e) => e.stopPropagation()}
-                                                >
-                                                    To: <AddressLink address={item.recipientAccount.identifier} />
-                                                </div>
-                                            ),
-                                        }}
-                                        secondaryInfo={{
-                                            mainText: item.extraData?.usdAmount,
-                                            subText: item.timestamp ? formatDate(new Date(item.timestamp)) : '',
-                                        }}
-                                        metadata={{
-                                            tokenLogo: getTokenLogo(item.tokenSymbol),
-                                            chainLogo: getChainLogo(getChainName(item.chainId) ?? ''),
-                                        }}
-                                        details={item}
-                                    />
+            <NavHeader title={getHeaderTitle(pathname)} />
+            <div className="h-full w-full">
+                {allEntries.map((item, index) => {
+                    const itemDate = new Date(item.timestamp)
+                    const group = getDateGroup(itemDate, today)
+                    const currentGroupHeaderKey = getDateGroupKey(itemDate, group)
+                    const showHeader = currentGroupHeaderKey !== lastGroupHeaderKey
+                    if (showHeader) {
+                        lastGroupHeaderKey = currentGroupHeaderKey
+                    }
+
+                    const { transactionDetails, transactionCardType } = mapTransactionDataForDrawer(
+                        item,
+                        currentUserUsername || ''
+                    )
+
+                    let position: CardPosition = 'middle'
+                    const isFirstOverall = index === 0
+                    const isLastOverall = index === allEntries.length - 1
+                    const isFirstInGroup = showHeader
+
+                    if (allEntries.length === 1) {
+                        position = 'single'
+                    } else if (isFirstInGroup && isLastOverall) {
+                        position = 'single'
+                    } else if (isFirstInGroup || isFirstOverall) {
+                        position = 'first'
+                    } else if (isLastOverall) {
+                        position = 'last'
+                    }
+
path: src/app/(mobile-ui)/history/page.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Card position calculation misses “last-in-group” for middle groups**

Only the *final list element* is marked `last`, so groups that end before the array’s tail render their last item as `middle`, affecting rounded-corner styling.

Compute position by inspecting neighbours instead:

```ts
const prevGroup = index > 0 ? getDateGroupKey(new Date(allEntries[index - 1].timestamp), group) : null
const nextGroup = index < allEntries.length - 1
    ? getDateGroupKey(new Date(allEntries[index + 1].timestamp), group)
    : null

let position: CardPosition
if (!prevGroup && !nextGroup) position = 'single'
else if (!prevGroup) position = 'first'
else if (!nextGroup) position = 'last'
else position = 'middle'
```

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-07T18:39:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,60 +71,72 @@
         return <div className="w-full py-4 text-center">Error loading history: {error?.message}</div>
     }
 
-    if (!historyData || historyData.pages.length === 0) {
+    if (allEntries.length === 0) {
         return (
-            <div className="flex h-[80dvh] items-center justify-center">
-                <NoDataEmptyState animSize="lg" message="You haven't done any transactions" />
+            <div className="flex h-[80dvh] flex-col items-center justify-center">
+                <NavHeader title={getHeaderTitle(pathname)} />
+                <div className="flex flex-grow items-center justify-center">
+                    <NoDataEmptyState animSize="lg" message="You haven't done any transactions" />
+                </div>
             </div>
         )
     }
 
+    let lastGroupHeaderKey: string | null = null
+    const today = new Date()
+
     return (
         <div className="mx-auto w-full space-y-6 md:max-w-2xl md:space-y-3">
-            {!!historyData?.pages.length ? <NavHeader title={getHeaderTitle(pathname)} /> : null}
-            <div className="h-full w-full border-t border-n-1">
-                {!!historyData?.pages.length &&
-                    historyData?.pages.map((page, pageIndex) => (
-                        <div key={pageIndex}>
-                            {page.entries.map((item) => (
-                                <div key={item.uuid}>
-                                    <ListItemView
-                                        id={item.uuid}
-                                        variant="history"
-                                        primaryInfo={{
-                                            title: (
-                                                <div className="flex flex-col items-start gap-2 md:flex-row md:items-center ">
-                                                    <div className="font-bold">
-                                                        {item.type} {item.userRole}
-                                                    </div>
-                                                    <div className="flex flex-col items-end justify-end gap-2 text-end">
-                                                        <TransactionBadge status={item.status as string} />
-                                                    </div>
-                                                </div>
-                                            ),
-                                            subtitle: !!item.recipientAccount.identifier && (
-                                                <div
-                                                    className="text-xs text-gray-1"
-                                                    onClick={(e) => e.stopPropagation()}
-                                                >
-                                                    To: <AddressLink address={item.recipientAccount.identifier} />
-                                                </div>
-                                            ),
-                                        }}
-                                        secondaryInfo={{
-                                            mainText: item.extraData?.usdAmount,
-                                            subText: item.timestamp ? formatDate(new Date(item.timestamp)) : '',
-                                        }}
-                                        metadata={{
-                                            tokenLogo: getTokenLogo(item.tokenSymbol),
-                                            chainLogo: getChainLogo(getChainName(item.chainId) ?? ''),
-                                        }}
-                                        details={item}
-                                    />
+            <NavHeader title={getHeaderTitle(pathname)} />
+            <div className="h-full w-full">
+                {allEntries.map((item, index) => {
+                    const itemDate = new Date(item.timestamp)
+                    const group = getDateGroup(itemDate, today)
+                    const currentGroupHeaderKey = getDateGroupKey(itemDate, group)
+                    const showHeader = currentGroupHeaderKey !== lastGroupHeaderKey
+                    if (showHeader) {
+                        lastGroupHeaderKey = currentGroupHeaderKey
+                    }
+
+                    const { transactionDetails, transactionCardType } = mapTransactionDataForDrawer(
+                        item,
+                        currentUserUsername || ''
+                    )
+
+                    let position: CardPosition = 'middle'
+                    const isFirstOverall = index === 0
+                    const isLastOverall = index === allEntries.length - 1
+                    const isFirstInGroup = showHeader
+
+                    if (allEntries.length === 1) {
+                        position = 'single'
+                    } else if (isFirstInGroup && isLastOverall) {
+                        position = 'single'
+                    } else if (isFirstInGroup || isFirstOverall) {
+                        position = 'first'
+                    } else if (isLastOverall) {
+                        position = 'last'
+                    }
+
+                    return (
+                        <React.Fragment key={item.uuid}>
+                            {showHeader && (
+                                <div className="mb-2 mt-4 px-1 text-sm font-semibold capitalize">
+                                    {formatGroupHeaderDate(itemDate, group, today)}
                                 </div>
-                            ))}
-                        </div>
-                    ))}
+                            )}
+                            <TransactionCard
+                                type={transactionCardType}
+                                name={transactionDetails.userName}
+                                amount={Number(transactionDetails.amount)}
+                                status={transactionDetails.status}
+                                initials={transactionDetails.initials}
+                                transaction={transactionDetails}
+                                position={position}
+                            />
+                        </React.Fragment>
+                    )
path: src/app/(mobile-ui)/history/page.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Possible NaN when `transactionDetails.amount` is `undefined`**

`Number(undefined)` is `NaN`, which may break amount formatting in `TransactionCard`.

Guard before casting:

```diff
-amount={Number(transactionDetails.amount)}
+amount={transactionDetails.amount ? Number(transactionDetails.amount) : 0}
```

(Or make `amount` prop accept `undefined`).  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
                    return (
                        <React.Fragment key={item.uuid}>
                            {showHeader && (
                                <div className="mb-2 mt-4 px-1 text-sm font-semibold capitalize">
                                    {formatGroupHeaderDate(itemDate, group, today)}
                                </div>
                            )}
                            <TransactionCard
                                type={transactionCardType}
                                name={transactionDetails.userName}
                                amount={transactionDetails.amount ? Number(transactionDetails.amount) : 0}
                                status={transactionDetails.status}
                                initials={transactionDetails.initials}
                                transaction={transactionDetails}
                                position={position}
                            />
                        </React.Fragment>
                    )
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T18:39:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,128 @@
+import StatusBadge, { StatusType } from '@/components/Global/Badges/StatusBadge'
+import Card, { CardPosition } from '@/components/Global/Card'
+import { Icon, IconName } from '@/components/Global/Icons/Icon'
+import TransactionAvatarBadge from '@/components/TransactionDetails/TransactionAvatarBadge'
+import { TransactionDetailsDrawer } from '@/components/TransactionDetails/TransactionDetailsDrawer'
+import { TransactionDetails } from '@/components/TransactionDetails/transactionTransformer'
+import { useTransactionDetailsDrawer } from '@/hooks/useTransactionDetailsDrawer'
+import { formatAmount, printableAddress } from '@/utils'
+import React from 'react'
+import { isAddress } from 'viem'
+
+export type TransactionType = 'send' | 'withdraw' | 'add' | 'request' | 'cashout'
+
+interface TransactionCardProps {
+    type: TransactionType
+    name: string
+    amount: number
+    status?: StatusType
+    initials?: string
+    position?: CardPosition
+    transaction: TransactionDetails
+}
+
+/**
+ * component to display a single transaction entry in a list format.
+ * it handles displaying the avatar/icon, name, amount, status,
+ * and opens a transaction details drawer when clicked.
+ */
+const TransactionCard: React.FC<TransactionCardProps> = ({
+    type,
+    name,
+    amount,
+    status,
+    initials = '',
+    position = 'middle',
+    transaction,
+}) => {
+    // hook to manage the state of the details drawer (open/closed, selected transaction)
+    const { isDrawerOpen, selectedTransaction, openTransactionDetails, closeTransactionDetails } =
+        useTransactionDetailsDrawer()
+
+    // determine if amount should be positive or negative based on card type
+    const isNegative = type === 'send' || type === 'withdraw'
+    const displayAmount = isNegative ? `-$${formatAmount(amount)}` : `+$${formatAmount(amount)}`
+
+    // for request/send types, always show positive amount with dollar sign
+    const finalAmount = type === 'request' || type === 'send' ? `$${formatAmount(amount)}` : displayAmount
+
+    const handleClick = () => {
+        openTransactionDetails(transaction)
+    }
+
+    const isLinkTx = transaction.extraDataForDrawer?.isLinkTransaction ?? false
+    const userNameForAvatar = transaction.userName // used by avatar for color hashing or type checking
+
+    return (
+        <>
+            {/* the clickable card */}
+            <Card position={position} onClick={handleClick}>
+                <div className="flex items-center justify-between">
+                    <div className="flex items-center gap-3">
+                        {/* txn avatar component handles icon/initials/colors */}
+                        <TransactionAvatarBadge
+                            initials={initials}
+                            userName={userNameForAvatar}
+                            isLinkTransaction={isLinkTx}
+                            transactionType={type}
+                            context="card"
+                            size="extra-small"
+                        />
+                        <div className="flex flex-col">
+                            {/* display formatted name (address or username) */}
+                            <div className="max-w-40 truncate font-roboto text-sm font-medium">
+                                {isAddress(name) ? printableAddress(name) : name}
+                            </div>
+                            {/* display the action icon and type text */}
+                            <div className="flex items-center gap-1 text-gray-500">
+                                {getActionIcon(type)}
+                                <span className="text-[10px] capitalize">{type}</span>
+                            </div>
+                        </div>
+                    </div>
+
+                    {/* amount and status on the right side */}
+                    <div className="flex flex-col items-end space-y-0.5">
+                        <span className="font-roboto text-xs font-medium">{finalAmount}</span>
+                        {status && <StatusBadge status={status} />}
+                    </div>
+                </div>
+            </Card>
+
+            {/* Transaction Details Drawer */}
+            <TransactionDetailsDrawer
+                isOpen={isDrawerOpen && selectedTransaction?.id === transaction.id}
+                onClose={closeTransactionDetails}
+                transaction={selectedTransaction}
+            />
+        </>
+    )
+}
+
+// helper functions
+function getActionIcon(type: TransactionType): React.ReactNode {
+    let iconName: IconName | null = null
+    let iconSize = 6
+
+    switch (type) {
+        case 'send':
+            iconName = 'arrow-up-right'
+            break
+        case 'request':
+            iconName = 'arrow-down-left'
+            break
+        case 'withdraw':
+            iconName = 'arrow-up'
+            iconSize = 8
+            break
+        case 'add':
+            iconName = 'arrow-down'
+            iconSize = 8
+            break
+        default:
+            return null
+    }
+    return <Icon name={iconName} size={iconSize} fill="currentColor" />
+}
path: src/components/TransactionDetails/TransactionCard.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add cashout icon in getActionIcon function**

The `getActionIcon` function doesn't handle the 'cashout' transaction type even though it's defined in the `TransactionType`.

Add a case for 'cashout' in the switch statement:

```diff
switch (type) {
    case 'send':
        iconName = 'arrow-up-right'
        break
    case 'request':
        iconName = 'arrow-down-left'
        break
    case 'withdraw':
        iconName = 'arrow-up'
        iconSize = 8
        break
    case 'add':
        iconName = 'arrow-down'
        iconSize = 8
        break
+   case 'cashout':
+       iconName = 'arrow-up'
+       iconSize = 8
+       break
    default:
        return null
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
// helper functions
function getActionIcon(type: TransactionType): React.ReactNode {
    let iconName: IconName | null = null
    let iconSize = 6

    switch (type) {
        case 'send':
            iconName = 'arrow-up-right'
            break
        case 'request':
            iconName = 'arrow-down-left'
            break
        case 'withdraw':
            iconName = 'arrow-up'
            iconSize = 8
            break
        case 'add':
            iconName = 'arrow-down'
            iconSize = 8
            break
        case 'cashout':
            iconName = 'arrow-up'
            iconSize = 8
            break
        default:
            return null
    }
    return <Icon name={iconName} size={iconSize} fill="currentColor" />
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T18:39:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,216 @@
+import { StatusType } from '@/components/Global/Badges/StatusBadge'
+import { TransactionType as TransactionCardType } from '@/components/TransactionDetails/TransactionCard'
+import { TransactionDirection } from '@/components/TransactionDetails/TransactionDetailsHeaderCard'
+import { EHistoryEntryType, EHistoryUserRole, HistoryEntry } from '@/hooks/useTransactionHistory'
+import { getExplorerUrl, getInitialsFromName } from '@/utils/general.utils'
+
+/**
+ * @fileoverview maps raw transaction history data from the api/hook to the format needed by ui components.
+ */
+
+/**
+ * defines the structure of the data expected by the transaction details drawer component.
+ * includes ui-specific fields derived from the original history entry.
+ */
+export interface TransactionDetails {
+    id: string
+    direction: TransactionDirection
+    userName: string
+    amount: number | bigint
+    currencySymbol?: string
+    tokenSymbol?: string
+    initials: string
+    status?: StatusType
+    isVerified?: boolean
+    date: string | Date
+    fee?: number | string
+    memo?: string
+    txHash?: string
+    explorerUrl?: string
+    extraDataForDrawer?: {
+        originalType: EHistoryEntryType
+        originalUserRole: EHistoryUserRole
+        link?: string
+        isLinkTransaction?: boolean
+        transactionCardType?: TransactionCardType
+    }
+}
+
+/**
+ * defines the output structure of the mapping function.
+ */
+interface MappedTransactionData {
+    /** data structured for the transactiondetailsdrawer. */
+    transactionDetails: TransactionDetails
+    /** the visual type for the transactioncard component. */
+    transactionCardType: TransactionCardType
+}
+
+/**
+ * maps a raw historyentry from the api/hook to the structured data needed by ui components.
+ *
+ * @param entry the raw history entry object.
+ * @param currentuserusername the username of the currently logged-in user.
+ * @returns an object containing structured transactiondetails and the transactioncardtype.
+ */
+export function mapTransactionDataForDrawer(entry: HistoryEntry, currentUserUsername?: string): MappedTransactionData {
+    // initialize variables
+    let direction: TransactionDirection
+    let transactionCardType: TransactionCardType
+    let nameForDetails = ''
+    let uiStatus: StatusType = 'pending'
+    let isLinkTx = false
+    let isPeerActuallyUser = false
+
+    // determine if the current user was the sender or recipient in the original entry
+    const isCurrentUserSender = entry.senderAccount?.isUser && entry.senderAccount?.username === currentUserUsername
+    const isCurrentUserRecipient =
+        entry.recipientAccount?.isUser && entry.recipientAccount?.username === currentUserUsername
+
+    // determine direction, card type, peer name, and flags based on original type and user role
+    switch (entry.type) {
+        case EHistoryEntryType.SEND_LINK:
+            isLinkTx = true
+            if (isCurrentUserSender) {
+                direction = 'send'
+                transactionCardType = 'send'
+                nameForDetails =
+                    entry.recipientAccount?.username || entry.recipientAccount?.identifier || 'Sent via Link'
+                isPeerActuallyUser = !!entry.recipientAccount?.isUser
+            } else {
+                direction = 'receive'
+                transactionCardType = 'add'
+                nameForDetails = entry.senderAccount?.username || entry.senderAccount?.identifier || 'Received via Link'
+                isPeerActuallyUser = !!entry.senderAccount?.isUser
+            }
+            break
+        case EHistoryEntryType.REQUEST:
+            isLinkTx = !entry.txHash
+            if (isCurrentUserSender) {
+                direction = 'request_sent'
+                transactionCardType = 'request'
+                nameForDetails =
+                    entry.recipientAccount?.username || entry.recipientAccount?.identifier || 'Requested To'
+                isPeerActuallyUser = !!entry.recipientAccount?.isUser
+            } else {
+                if (entry.status?.toUpperCase() === 'NEW' || entry.status?.toUpperCase() === 'PENDING') {
+                    direction = 'request_received'
+                    transactionCardType = 'request'
+                    nameForDetails =
+                        entry.senderAccount?.username ||
+                        entry.senderAccount?.identifier ||
+                        `Request From ${entry.recipientAccount?.username || entry.recipientAccount?.identifier}`
+                    isPeerActuallyUser = !!entry.senderAccount?.isUser
+                } else {
+                    direction = 'send'
+                    transactionCardType = 'send'
+                    nameForDetails =
+                        entry.senderAccount?.username || entry.senderAccount?.identifier || 'Paid Request To'
+                    isPeerActuallyUser = !!entry.senderAccount?.isUser
+                }
+            }
+            break
+        case EHistoryEntryType.CASHOUT:
+            direction = 'withdraw'
+            transactionCardType = 'withdraw'
+            nameForDetails = entry.recipientAccount?.identifier || 'Bank Account'
+            isPeerActuallyUser = false
+            break
+        case EHistoryEntryType.DEPOSIT:
+            direction = 'add'
+            transactionCardType = 'add'
+            nameForDetails = entry.senderAccount?.identifier || 'Deposit Source'
+            isPeerActuallyUser = false
+            break
+        default:
+            direction = 'send'
+            transactionCardType = 'send'
+            nameForDetails = entry.recipientAccount?.identifier || 'Unknown'
+            isPeerActuallyUser = !!entry.recipientAccount?.isUser
+            break
+    }
+
+    if (!isPeerActuallyUser) {
+        isPeerActuallyUser = false
+    } else if (entry.recipientAccount?.isUser === false && entry.senderAccount?.isUser === false) {
+        isPeerActuallyUser = false
+    }
+
+    // map the raw status string to the defined ui status types
+    switch (entry.status?.toUpperCase()) {
+        case 'NEW':
+        case 'PENDING':
+            uiStatus = 'pending'
+            break
+        case 'COMPLETED':
+        case 'SUCCESSFUL':
+        case 'CLAIMED':
+        case 'PAID':
+            uiStatus = 'completed'
+            break
+        case 'FAILED':
+        case 'ERROR':
+            uiStatus = 'failed'
+            break
+        case 'CANCELLED':
+        case 'EXPIRED':
+            uiStatus = 'cancelled'
+            break
+        default:
+            const knownStatuses: StatusType[] = ['completed', 'pending', 'failed', 'cancelled', 'soon', 'processing']
+            if (entry.status && knownStatuses.includes(entry.status.toLowerCase() as StatusType)) {
+                uiStatus = entry.status.toLowerCase() as StatusType
+            } else {
+                uiStatus = 'pending'
+            }
+            break
path: src/components/TransactionDetails/transactionTransformer.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix switch declaration scope issue**

The `knownStatuses` array is declared within a switch case, which can cause issues with variable scope.

Wrap the declaration in a block to restrict its access to this switch clause:

```diff
    default:
-       const knownStatuses: StatusType[] = ['completed', 'pending', 'failed', 'cancelled', 'soon', 'processing']
-       if (entry.status && knownStatuses.includes(entry.status.toLowerCase() as StatusType)) {
-           uiStatus = entry.status.toLowerCase() as StatusType
-       } else {
-           uiStatus = 'pending'
-       }
+       {
+           const knownStatuses: StatusType[] = ['completed', 'pending', 'failed', 'cancelled', 'soon', 'processing']
+           if (entry.status && knownStatuses.includes(entry.status.toLowerCase() as StatusType)) {
+               uiStatus = entry.status.toLowerCase() as StatusType
+           } else {
+               uiStatus = 'pending'
+           }
+       }
        break
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
    default:
        {
            const knownStatuses: StatusType[] = ['completed', 'pending', 'failed', 'cancelled', 'soon', 'processing']
            if (entry.status && knownStatuses.includes(entry.status.toLowerCase() as StatusType)) {
                uiStatus = entry.status.toLowerCase() as StatusType
            } else {
                uiStatus = 'pending'
            }
        }
        break
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 160-160: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.


(lint/correctness/noSwitchDeclarations)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit dcdca3b
created_at: 2025-05-07T18:39:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,60 +71,72 @@ const HistoryPage = () => {
         return <div className="w-full py-4 text-center">Error loading history: {error?.message}</div>
     }
 
-    if (!historyData || historyData.pages.length === 0) {
+    if (allEntries.length === 0) {
         return (
-            <div className="flex h-[80dvh] items-center justify-center">
-                <NoDataEmptyState animSize="lg" message="You haven't done any transactions" />
+            <div className="flex h-[80dvh] flex-col items-center justify-center">
+                <NavHeader title={getHeaderTitle(pathname)} />
+                <div className="flex flex-grow items-center justify-center">
+                    <NoDataEmptyState animSize="lg" message="You haven't done any transactions" />
+                </div>
             </div>
         )
     }
 
+    let lastGroupHeaderKey: string | null = null
+    const today = new Date()
+
     return (
         <div className="mx-auto w-full space-y-6 md:max-w-2xl md:space-y-3">
-            {!!historyData?.pages.length ? <NavHeader title={getHeaderTitle(pathname)} /> : null}
-            <div className="h-full w-full border-t border-n-1">
-                {!!historyData?.pages.length &&
-                    historyData?.pages.map((page, pageIndex) => (
-                        <div key={pageIndex}>
-                            {page.entries.map((item) => (
-                                <div key={item.uuid}>
-                                    <ListItemView
-                                        id={item.uuid}
-                                        variant="history"
-                                        primaryInfo={{
-                                            title: (
-                                                <div className="flex flex-col items-start gap-2 md:flex-row md:items-center ">
-                                                    <div className="font-bold">
-                                                        {item.type} {item.userRole}
-                                                    </div>
-                                                    <div className="flex flex-col items-end justify-end gap-2 text-end">
-                                                        <TransactionBadge status={item.status as string} />
-                                                    </div>
-                                                </div>
-                                            ),
-                                            subtitle: !!item.recipientAccount.identifier && (
-                                                <div
-                                                    className="text-xs text-gray-1"
-                                                    onClick={(e) => e.stopPropagation()}
-                                                >
-                                                    To: <AddressLink address={item.recipientAccount.identifier} />
-                                                </div>
-                                            ),
-                                        }}
-                                        secondaryInfo={{
-                                            mainText: item.extraData?.usdAmount,
-                                            subText: item.timestamp ? formatDate(new Date(item.timestamp)) : '',
-                                        }}
-                                        metadata={{
-                                            tokenLogo: getTokenLogo(item.tokenSymbol),
-                                            chainLogo: getChainLogo(getChainName(item.chainId) ?? ''),
-                                        }}
-                                        details={item}
-                                    />
+            <NavHeader title={getHeaderTitle(pathname)} />
+            <div className="h-full w-full">
+                {allEntries.map((item, index) => {
+                    const itemDate = new Date(item.timestamp)
+                    const group = getDateGroup(itemDate, today)
+                    const currentGroupHeaderKey = getDateGroupKey(itemDate, group)
+                    const showHeader = currentGroupHeaderKey !== lastGroupHeaderKey
+                    if (showHeader) {
+                        lastGroupHeaderKey = currentGroupHeaderKey
+                    }
+
+                    const { transactionDetails, transactionCardType } = mapTransactionDataForDrawer(
+                        item,
+                        currentUserUsername || ''
+                    )
+
+                    let position: CardPosition = 'middle'
+                    const isFirstOverall = index === 0
+                    const isLastOverall = index === allEntries.length - 1
+                    const isFirstInGroup = showHeader
+
+                    if (allEntries.length === 1) {
+                        position = 'single'
+                    } else if (isFirstInGroup && isLastOverall) {
+                        position = 'single'
+                    } else if (isFirstInGroup || isFirstOverall) {
+                        position = 'first'
+                    } else if (isLastOverall) {
+                        position = 'last'
+                    }
+
path: src/app/(mobile-ui)/history/page.tsx
user: login: kushagrasarathe
body: nope its good

created_at: 2025-05-08T14:51:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,60 +71,72 @@ const HistoryPage = () => {
         return <div className="w-full py-4 text-center">Error loading history: {error?.message}</div>
     }
 
-    if (!historyData || historyData.pages.length === 0) {
+    if (allEntries.length === 0) {
         return (
-            <div className="flex h-[80dvh] items-center justify-center">
-                <NoDataEmptyState animSize="lg" message="You haven't done any transactions" />
+            <div className="flex h-[80dvh] flex-col items-center justify-center">
+                <NavHeader title={getHeaderTitle(pathname)} />
+                <div className="flex flex-grow items-center justify-center">
+                    <NoDataEmptyState animSize="lg" message="You haven't done any transactions" />
+                </div>
             </div>
         )
     }
 
+    let lastGroupHeaderKey: string | null = null
+    const today = new Date()
+
     return (
         <div className="mx-auto w-full space-y-6 md:max-w-2xl md:space-y-3">
-            {!!historyData?.pages.length ? <NavHeader title={getHeaderTitle(pathname)} /> : null}
-            <div className="h-full w-full border-t border-n-1">
-                {!!historyData?.pages.length &&
-                    historyData?.pages.map((page, pageIndex) => (
-                        <div key={pageIndex}>
-                            {page.entries.map((item) => (
-                                <div key={item.uuid}>
-                                    <ListItemView
-                                        id={item.uuid}
-                                        variant="history"
-                                        primaryInfo={{
-                                            title: (
-                                                <div className="flex flex-col items-start gap-2 md:flex-row md:items-center ">
-                                                    <div className="font-bold">
-                                                        {item.type} {item.userRole}
-                                                    </div>
-                                                    <div className="flex flex-col items-end justify-end gap-2 text-end">
-                                                        <TransactionBadge status={item.status as string} />
-                                                    </div>
-                                                </div>
-                                            ),
-                                            subtitle: !!item.recipientAccount.identifier && (
-                                                <div
-                                                    className="text-xs text-gray-1"
-                                                    onClick={(e) => e.stopPropagation()}
-                                                >
-                                                    To: <AddressLink address={item.recipientAccount.identifier} />
-                                                </div>
-                                            ),
-                                        }}
-                                        secondaryInfo={{
-                                            mainText: item.extraData?.usdAmount,
-                                            subText: item.timestamp ? formatDate(new Date(item.timestamp)) : '',
-                                        }}
-                                        metadata={{
-                                            tokenLogo: getTokenLogo(item.tokenSymbol),
-                                            chainLogo: getChainLogo(getChainName(item.chainId) ?? ''),
-                                        }}
-                                        details={item}
-                                    />
+            <NavHeader title={getHeaderTitle(pathname)} />
+            <div className="h-full w-full">
+                {allEntries.map((item, index) => {
+                    const itemDate = new Date(item.timestamp)
+                    const group = getDateGroup(itemDate, today)
+                    const currentGroupHeaderKey = getDateGroupKey(itemDate, group)
+                    const showHeader = currentGroupHeaderKey !== lastGroupHeaderKey
+                    if (showHeader) {
+                        lastGroupHeaderKey = currentGroupHeaderKey
+                    }
+
+                    const { transactionDetails, transactionCardType } = mapTransactionDataForDrawer(
+                        item,
+                        currentUserUsername || ''
+                    )
+
+                    let position: CardPosition = 'middle'
+                    const isFirstOverall = index === 0
+                    const isLastOverall = index === allEntries.length - 1
+                    const isFirstInGroup = showHeader
+
+                    if (allEntries.length === 1) {
+                        position = 'single'
+                    } else if (isFirstInGroup && isLastOverall) {
+                        position = 'single'
+                    } else if (isFirstInGroup || isFirstOverall) {
+                        position = 'first'
+                    } else if (isLastOverall) {
+                        position = 'last'
+                    }
+
path: src/app/(mobile-ui)/history/page.tsx
user: login: coderabbitai[bot]
body: @kushagrasarathe Got it! I'll respect your decision to keep the current implementation as is. Let me know if you need any other assistance with the transaction history drawer feature.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@kushagrasarathe Got it! I'll respect your decision to keep the current implementation as is. Let me know if you need any other assistance with the transaction history drawer feature.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-08T14:52:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-06T10:13:44Z
body: [vc]: #dsSKThk7+YkHfZvmSVeSaZniiD1o6TSAimVII89iFvM=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJwZWFudXQtdWkiLCJyb290RGlyZWN0b3J5IjpudWxsLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc3F1aXJyZWxsYWJzL3BlYW51dC11aS9BOU1BSkdWOG1uTWpDMjZMQ25WNlJ5TDZ4Y2pUIiwicHJldmlld1VybCI6InBlYW51dC11aS1naXQtZmVhdC1oaXN0b3J5LWRldGFpbHMtc3F1aXJyZWxsYWJzLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoicGVhbnV0LXVpLWdpdC1mZWF0LWhpc3RvcnktZGV0YWlscy1zcXVpcnJlbGxhYnMudmVyY2VsLmFwcCJ9fV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **peanut-ui** | ✅ Ready ([Inspect](https://vercel.com/squirrellabs/peanut-ui/A9MAJGV8mnMjC26LCnV6RyL6xcjT)) | [Visit Preview](https://peanut-ui-git-feat-history-details-squirrellabs.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/peanut-ui-git-feat-history-details-squirrellabs.vercel.app?via=pr-comment-feedback-link) | May 11, 2025 1:47pm |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-06T10:13:48Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update introduces a comprehensive refactor and enhancement of the transaction history and details UI. It replaces the legacy `ListItemView` with new, modular components for transaction cards, avatars, and details drawers. Utilities for color and date grouping are added. Several components are removed or replaced, and the transaction rendering logic is streamlined for maintainability and clarity.

## Changes

| File(s)                                                                                  | Change Summary |
|------------------------------------------------------------------------------------------|---------------|
| src/components/Global/ListItemView/index.tsx<br>src/components/Payment/History/index.tsx | Removed the `ListItemView` component and the `PaymentHistory` component, eliminating legacy transaction list rendering. |
| src/components/TransactionDetails/TransactionCard.tsx<br>src/components/TransactionDetails/TransactionDetailsDrawer.tsx<br>src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx<br>src/components/TransactionDetails/TransactionAvatarBadge.tsx<br>src/components/TransactionDetails/transactionTransformer.ts<br>src/hooks/useTransactionDetailsDrawer.ts<br>src/utils/color.utils.ts<br>src/utils/dateGrouping.utils.ts | Introduced new components and utilities for transaction cards, details drawers, avatar badges, transaction data transformation, color calculation, and date grouping. |
| src/components/Home/TransactionCard.tsx                                                  | Refactored to require a `transaction` prop, use new formatting and drawer logic, and render the new details drawer. |
| src/components/Global/Badges/StatusBadge.tsx                                             | Extended status types to include `'processing'`, updated style logic, and adjusted size/type mappings. |
| src/components/Profile/AvatarWithBadge.tsx                                               | Enhanced to support icons, dynamic background/text colors, and improved sizing logic for avatars. |
| src/components/Home/HomeHistory.tsx                                                      | Updated to fetch 5 transactions, use current username, and render new `TransactionCard` components. |
| src/app/(mobile-ui)/history/page.tsx                                                     | Refactored to flatten transaction entries, group by date, and render using `TransactionCard` with new grouping logic. |
| src/app/(mobile-ui)/home/page.tsx                                                        | Removed an extraneous blank line after a state variable declaration. |
| src/components/Global/PeanutLoading/index.tsx                                            | Added `left-0` to full-screen cover CSS classes for correct positioning. |
| src/components/Profile/components/PublicProfile.tsx                                      | Commented out example transaction cards and import; added TODO for real data. |
| src/utils/general.utils.ts                                                               | Removed unused `TransactionType` import and deleted `getHistoryTransactionStatus` utility function. |
| tailwind.config.js                                                                       | Extended Tailwind color palette with new shades and color groups. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant HistoryPage
    participant TransactionCard
    participant useTransactionDetailsDrawer
    participant TransactionDetailsDrawer

    User->>HistoryPage: Loads transaction history
    HistoryPage->>TransactionCard: Renders each transaction
    User->>TransactionCard: Clicks on a transaction
    TransactionCard->>useTransactionDetailsDrawer: openTransactionDetails(transaction)
    useTransactionDetailsDrawer-->>TransactionCard: Updates drawer state
    TransactionCard->>TransactionDetailsDrawer: Renders drawer with transaction details
    User->>TransactionDetailsDrawer: Views details, may close drawer
    TransactionDetailsDrawer->>useTransactionDetailsDrawer: closeTransactionDetails()
```

## Suggested reviewers

- jjramirezn

## Poem

> In fields of code where carrots grow,  
> The old list view had to go!  
> New cards and drawers, colors bright,  
> Grouped by date, a tidy sight.  
> Avatars now shine with flair—  
> Transaction tales, everywhere!  
> 🥕✨

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> - CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> 
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d6c4c455c2cbac0388da9c8a07bdb18f598f7ff6 and dcdca3bc2e45aac3c8d462fc0ac364c1afca3ae3.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `src/app/(mobile-ui)/history/page.tsx` (3 hunks)
* `src/components/TransactionDetails/TransactionCard.tsx` (1 hunks)
* `src/components/TransactionDetails/transactionTransformer.ts` (1 hunks)
* `src/utils/dateGrouping.utils.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary>

* src/components/TransactionDetails/TransactionCard.tsx
* src/app/(mobile-ui)/history/page.tsx
* src/utils/dateGrouping.utils.ts
* src/components/TransactionDetails/transactionTransformer.ts

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=peanutprotocol/peanut-ui&utm_content=831):

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

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

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

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

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

### CodeRabbit Configuration File (`.coderabbit.yaml`)

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

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: notion-workspace[bot]
created_at: 2025-05-08T18:54:47Z
body: <a href="https://www.notion.so/Implement-detail-screens-1c983811757980e7a81fe838b1c6f40b?pvs=8&n=github_linkback">Implement detail screens</a>
author_association: NONE
user: login: notion-workspace[bot]
created_at: 2025-05-08T18:54:48Z
body: <a href="https://www.notion.so/Implement-history-component-1c9838117579803d8465eaac13a959fc?pvs=8&n=github_linkback">Implement history component</a>
author_association: NONE