Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
428 changes: 205 additions & 223 deletions src/app/(mobile-ui)/home/page.tsx

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions src/app/(mobile-ui)/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Profile } from '@/components'

import PageContainer from '@/components/0_Bruddle/PageContainer'
import { Metadata } from 'next'

export const metadata: Metadata = {
title: 'Peanut Protocol',
description: 'Send to Anyone',
title: 'Profile | Peanut Protocol',
description: 'Manage your Peanut profile',
metadataBase: new URL('https://peanut.me'),

icons: {
Expand All @@ -20,5 +20,9 @@ export const metadata: Metadata = {
}

export default function ProfilePage() {
return <Profile />
return (
<PageContainer>
<Profile />
</PageContainer>
)
}
2 changes: 1 addition & 1 deletion src/components/0_Bruddle/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type ButtonVariant =
| 'yellow'
| 'transparent'
| 'primary-soft'
type ButtonSize = 'small' | 'medium' | 'large' | 'xl' | 'xl-fixed'
export type ButtonSize = 'small' | 'medium' | 'large' | 'xl' | 'xl-fixed'
type ButtonShape = 'default' | 'square'
type ShadowSize = '4' | '6' | '8'
type ShadowType = 'primary' | 'secondary'
Expand Down
2 changes: 1 addition & 1 deletion src/components/0_Bruddle/PageContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HTMLAttributes } from 'react'

const PageContainer = (props: HTMLAttributes<HTMLDivElement>) => {
return <div className="flex w-full items-center justify-center *:w-full md:*:w-2/5">{props.children}</div>
return <div className="flex w-full items-center justify-center *:w-full md:pl-24 md:*:w-2/5">{props.children}</div>
}

export default PageContainer
16 changes: 11 additions & 5 deletions src/components/AddFunds/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type FundingMethod = 'exchange' | 'request_link' | null
type Wallet = { name: string; logo: string }

// main component
const AddFunds = () => {
const AddFunds = ({ cta }: { cta?: ReactNode }) => {
const [fundingMethod, setFundingMethod] = useState<FundingMethod>(null)
const [showModal, setShowModal] = useState(false)
const timerRef = useRef<NodeJS.Timeout>()
Expand Down Expand Up @@ -47,10 +47,16 @@ const AddFunds = () => {
return (
<div>
<div onClick={() => setShowModal(true)} className="flex flex-col items-center gap-2.5">
<Button variant="purple" className={twMerge('h-14 w-14 rounded-full p-0')} shadowSize="4">
<Icon name="plus" className="h-5 w-5" />
</Button>
<div className="font-semibold">Add</div>
{cta ? (
cta
) : (
<>
<Button variant="purple" className={twMerge('h-14 w-14 rounded-full p-0')} shadowSize="4">
<Icon name="plus" className="h-5 w-5" />
</Button>
<div className="font-semibold">Add</div>
</>
)}
</div>

<Modal
Expand Down
97 changes: 97 additions & 0 deletions src/components/Global/Badges/AchievementsBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { twMerge } from 'tailwind-merge'
import { Icon, IconName } from '../Icons/Icon'

export type AchievementsBadgeSize = 'extra-small' | 'small' | 'medium' | 'large'

interface AchievementsBadgeProps {
icon?: IconName
size?: AchievementsBadgeSize
color?: string
className?: string
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
}

const AchievementsBadge = ({
icon = 'check',
size = 'medium',
color = 'bg-success-3',
className,
position = 'top-right',
}: AchievementsBadgeProps) => {
const getContainerSize = () => {
switch (size) {
case 'extra-small':
return 'size-2.5'
case 'small':
return 'size-4'
case 'medium':
return 'size-5'
case 'large':
return 'size-6'
default:
return 'size-5'
}
}

const getIconSize = () => {
switch (size) {
case 'extra-small':
return 6
case 'small':
return 8
case 'medium':
return 10
case 'large':
return 14
default:
return 10
}
}

const getPadding = () => {
switch (size) {
case 'extra-small':
return 'p-0'
case 'small':
return 'p-0.5'
case 'medium':
return 'p-1'
case 'large':
return 'p-1.5'
default:
return 'p-1'
}
}

const getPosition = () => {
switch (position) {
case 'top-right':
return 'right-0 top-0'
case 'top-left':
return 'left-0 top-0'
case 'bottom-right':
return 'right-0 bottom-0'
case 'bottom-left':
return 'left-0 bottom-0'
default:
return 'right-0 top-0'
}
}

return (
<div
className={twMerge(
'absolute flex items-center justify-center rounded-full border border-black',
color,
getContainerSize(),
getPadding(),
getPosition(),
className
)}
>
<Icon name={icon} size={getIconSize()} />
</div>
)
}

export default AchievementsBadge
73 changes: 73 additions & 0 deletions src/components/Global/Badges/StatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react'
import { twMerge } from 'tailwind-merge'

export type StatusType = 'completed' | 'pending' | 'failed' | 'cancelled' | 'soon'

interface StatusBadgeProps {
status: StatusType
className?: string
size?: 'small' | 'medium' | 'large'
}

const StatusBadge: React.FC<StatusBadgeProps> = ({ status, className, size = 'small' }) => {
const getStatusStyles = () => {
switch (status) {
case 'completed':
return 'bg-success-2 text-success-1'
case 'pending':
return 'bg-secondary-4 text-secondary-1'
case 'failed':
case 'cancelled':
return 'bg-error-1 text-error'
case 'soon':
return 'bg-primary-3 text-primary-4'
default:
return 'bg-gray-200 text-gray-700'
}
}

const getStatusText = () => {
switch (status) {
case 'completed':
return 'Completed'
case 'pending':
return 'Pending'
case 'failed':
return 'Failed'
case 'cancelled':
return 'Cancelled'
case 'soon':
return 'Soon!'
default:
return status
}
}

const getSizeClasses = () => {
switch (size) {
case 'small':
return 'px-2 py-0.5 text-[10px]'
case 'medium':
return 'px-3 py-1 text-xs'
case 'large':
return 'px-4 py-1.5 text-sm'
default:
return 'px-2 py-0.5 text-[10px]'
}
}

return (
<span
className={twMerge(
'rounded-full font-roboto font-semibold',
getSizeClasses(),
getStatusStyles(),
className
)}
>
{getStatusText()}
</span>
)
}

export default StatusBadge
7 changes: 6 additions & 1 deletion src/components/Global/BottomDrawer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface BottomDrawerProps {
onClose?: () => void
initialPosition?: DrawerPosition
handleTitle?: string
handleSubtitle?: string
collapsedHeight?: number
halfHeight?: number
expandedHeight?: number
Expand All @@ -22,6 +23,7 @@ const BottomDrawer: React.FC<BottomDrawerProps> = ({
onClose,
initialPosition = 'half',
handleTitle = '',
handleSubtitle = '',
collapsedHeight = 15,
halfHeight = 50,
expandedHeight = 90,
Expand Down Expand Up @@ -307,7 +309,10 @@ const BottomDrawer: React.FC<BottomDrawerProps> = ({
style={{ cursor: isDragging ? 'grabbing' : 'grab' }}
>
<div className="mx-auto mb-4 mt-2 h-2 w-8 rounded-full bg-black"></div>
{handleTitle && <h2 className="mb-8 text-2xl font-extrabold">{handleTitle}</h2>}
<div className="mb-8 space-y-1">
{handleTitle && <h2 className="text-lg font-extrabold">{handleTitle}</h2>}
{handleSubtitle && <h2 className="mb-8">{handleSubtitle}</h2>}
</div>
</div>

{/* Content area */}
Expand Down
57 changes: 57 additions & 0 deletions src/components/Global/Card/index.tsx
Copy link
Contributor

Choose a reason for hiding this comment

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

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

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react'
import { twMerge } from 'tailwind-merge'

export type CardPosition = 'single' | 'first' | 'middle' | 'last'

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

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

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

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

return (
<div
className={twMerge('w-full overflow-hidden bg-white px-4 py-2', getBorderRadius(), getBorder(), className)}
onClick={onClick}
>
{children}
</div>
)
}
Comment on lines +14 to +55
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add accessibility attributes when using onClick handlers.

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

return (
    <div
        className={twMerge('w-full overflow-hidden bg-white px-4 py-2', getBorderRadius(), getBorder(), className)}
        onClick={onClick}
+       tabIndex={onClick ? 0 : undefined}
+       role={onClick ? 'button' : undefined}
+       onKeyDown={onClick ? (e) => e.key === 'Enter' && onClick() : undefined}
    >
        {children}
    </div>
)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

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


export default Card
9 changes: 5 additions & 4 deletions src/components/Global/CopyToClipboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React, { useState } from 'react'
import { twMerge } from 'tailwind-merge'
import Icon from '../Icon'
import { Icon } from '../Icons/Icon'

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

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Beware of dynamic Tailwind classes with template literals.

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


🏁 Script executed:

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

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

Length of output: 181


🏁 Script executed:

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

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

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

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

Length of output: 6856


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

Possible fixes:

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

Files to update:

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

}

const CopyToClipboard = ({ textToCopy, fill, className }: Props) => {
const CopyToClipboard = ({ textToCopy, fill, className, iconSize = '6' }: Props) => {
const [copied, setCopied] = useState(false)

const handleCopy = (e: React.MouseEvent<SVGElement>) => {
Expand All @@ -21,8 +22,8 @@ const CopyToClipboard = ({ textToCopy, fill, className }: Props) => {

return (
<Icon
name={copied ? 'check' : 'content-copy'}
className={twMerge('h-6 w-6 hover:opacity-80', className)}
name={copied ? 'check' : 'copy'}
className={twMerge(`h-${iconSize} w-${iconSize} hover:opacity-80`, className)}
fill={fill ? fill : 'white'}
onClick={handleCopy}
/>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Global/DirectSendQR/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getTokenSymbol, validateEnsName } from '@/utils'
import { isAddress } from 'viem'
import { validateEnsName, getTokenSymbol } from '@/utils'

// Constants
const PINTA_MERCHANTS: Record<string, string> = {
Expand Down Expand Up @@ -67,7 +67,7 @@ const REGEXES_BY_TYPE: { [key in QrType]?: RegExp } = {
/^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/,
}

const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL!
export const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL!

export function recognizeQr(data: string): QrType | null {
if (data.startsWith(BASE_URL)) {
Expand Down
Loading
Loading