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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"siwe": "^2.3.2",
"tailwind-merge": "^1.14.0",
"tailwind-scrollbar": "^3.1.0",
"use-haptic": "^1.1.11",
"uuid": "^10.0.0",
"validator": "^13.12.0",
"vaul": "^1.1.2",
Expand Down
45 changes: 16 additions & 29 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/app/(mobile-ui)/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import NoMoreJailModal from '@/components/Global/NoMoreJailModal'
import EarlyUserModal from '@/components/Global/EarlyUserModal'
import InvitesIcon from '@/components/Home/InvitesIcon'
import NavigationArrow from '@/components/Global/NavigationArrow'
import { useHaptic } from 'use-haptic'

const BALANCE_WARNING_THRESHOLD = parseInt(process.env.NEXT_PUBLIC_BALANCE_WARNING_THRESHOLD ?? '500')
const BALANCE_WARNING_EXPIRY = parseInt(process.env.NEXT_PUBLIC_BALANCE_WARNING_EXPIRY ?? '1814400') // 21 days in seconds
Expand All @@ -55,6 +56,7 @@ export default function Home() {
})
const { isConnected: isWagmiConnected } = useAccount()
const { disconnect: disconnectWagmi } = useDisconnect()
const { triggerHaptic } = useHaptic()

const { isFetchingUser, addAccount } = useAuth()
const { isUserKycApproved } = useKycStatus()
Expand Down Expand Up @@ -234,7 +236,7 @@ export default function Home() {
<div className="h-full w-full space-y-6 p-5">
<div className="flex items-center justify-between gap-2">
<UserHeader username={username!} fullName={userFullName} isVerified={isUserKycApproved} />
<Link href="/points" className="flex items-center gap-0">
<Link onClick={() => triggerHaptic()} href="/points" className="flex items-center gap-0">
<InvitesIcon />
<span className="whitespace-nowrap pl-1 text-sm font-semibold md:text-base">Points</span>
<NavigationArrow size={16} className="fill-black" />
Expand Down
3 changes: 3 additions & 0 deletions src/app/(mobile-ui)/withdraw/crypto/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { captureMessage } from '@sentry/nextjs'
import type { Address } from 'viem'
import { Slider } from '@/components/Slider'
import { tokenSelectorContext } from '@/context'
import { useHaptic } from 'use-haptic'

export default function WithdrawCryptoPage() {
const router = useRouter()
Expand Down Expand Up @@ -64,6 +65,7 @@ export default function WithdrawCryptoPage() {
isPreparingTx,
reset: resetPaymentInitiator,
} = usePaymentInitiator()
const { triggerHaptic } = useHaptic()

// Helper to manage errors consistently
const setError = useCallback(
Expand Down Expand Up @@ -223,6 +225,7 @@ export default function WithdrawCryptoPage() {
const result = await initiatePayment(paymentPayload)

if (result.success && result.txHash) {
triggerHaptic()
setCurrentView('STATUS')
} else {
console.error('Withdrawal execution failed:', result.error)
Expand Down
4 changes: 4 additions & 0 deletions src/app/[...recipient]/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import SupportCTA from '@/components/Global/SupportCTA'
import { BankRequestType, useDetermineBankRequestType } from '@/hooks/useDetermineBankRequestType'
import { PointsAction } from '@/services/services.types'
import { usePointsCalculation } from '@/hooks/usePointsCalculation'
import { useHaptic } from 'use-haptic'

export type PaymentFlow = 'request_pay' | 'external_wallet' | 'direct_pay' | 'withdraw'
interface Props {
Expand Down Expand Up @@ -72,6 +73,7 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props)
fulfillUsingManteca,
} = useRequestFulfillmentFlow()
const { requestType } = useDetermineBankRequestType(chargeDetails?.requestLink.recipientAccount.userId ?? '')
const { triggerHaptic } = useHaptic()

// Calculate points API call
// Points are ALWAYS calculated based on USD value (per PR.md: "1c in cost = 10 pts")
Expand Down Expand Up @@ -134,6 +136,7 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props)

// show STATUS view for any payment attempt (including failed ones)
if (latestPayment.status !== 'NEW') {
triggerHaptic()
dispatch(paymentActions.setView('STATUS'))
}
}
Expand Down Expand Up @@ -404,6 +407,7 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props)

// show status view only if fulfillment payment is successful
if (chargeDetails?.fulfillmentPayment?.status === 'SUCCESSFUL') {
triggerHaptic()
dispatch(paymentActions.setView('STATUS'))
}

Expand Down
10 changes: 10 additions & 0 deletions src/components/0_Bruddle/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { forwardRef, useEffect, useRef, useState, useCallback } from 'rea
import { twMerge } from 'tailwind-merge'
import { Icon, type IconName } from '../Global/Icons/Icon'
import Loading from '../Global/Loading'
import { useHaptic } from 'use-haptic'

export type ButtonVariant =
| 'purple'
Expand Down Expand Up @@ -37,6 +38,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
onLongPressStart?: () => void
onLongPressEnd?: () => void
}
disableHaptics?: boolean
}

const buttonVariants: Record<ButtonVariant, string> = {
Expand Down Expand Up @@ -93,13 +95,16 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
iconContainerClassName,
longPress,
onClick,
disableHaptics,
...props
},
ref
) => {
const localRef = useRef<HTMLButtonElement>(null)
const buttonRef = (ref as React.RefObject<HTMLButtonElement>) || localRef

const { triggerHaptic } = useHaptic()

// Long press state
const [isLongPressed, setIsLongPressed] = useState(false)
const [pressTimer, setPressTimer] = useState<NodeJS.Timeout | null>(null)
Expand Down Expand Up @@ -191,6 +196,11 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
// If long press is enabled but not completed, don't trigger onClick
return
}

if (!disableHaptics) {
triggerHaptic()
}

onClick?.(e)
},
[longPress, isLongPressed, onClick]
Expand Down
6 changes: 6 additions & 0 deletions src/components/ActionListCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
'use client'

import Card, { type CardPosition } from '@/components/Global/Card'
import { Icon } from '@/components/Global/Icons/Icon'
import React from 'react'
import { twMerge } from 'tailwind-merge'
import { Button } from '../0_Bruddle'
import { useHaptic } from 'use-haptic'

interface ActionListCardProps {
title: string | React.ReactNode
Expand All @@ -27,7 +30,10 @@ export const ActionListCard = ({
isDisabled = false,
descriptionClassName,
}: ActionListCardProps) => {
const { triggerHaptic } = useHaptic()

const handleCardClick = () => {
triggerHaptic()
onClick()
}

Expand Down
7 changes: 6 additions & 1 deletion src/components/Claim/Claim.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { ClaimedView, ClaimErrorView } from './Generic'
import { twMerge } from 'tailwind-merge'
import { ClaimBankFlowStep, useClaimBankFlow } from '@/context/ClaimBankFlowContext'
import { useSearchParams } from 'next/navigation'
import { useHaptic } from 'use-haptic'

export const Claim = ({}) => {
const [linkUrl, setLinkUrl] = useState<string>('')
Expand Down Expand Up @@ -74,7 +75,7 @@ export const Claim = ({}) => {

const { setFlowStep: setClaimBankFlowStep } = useClaimBankFlow()
const searchParams = useSearchParams()

const { triggerHaptic } = useHaptic()
// TanStack Query for fetching send link with automatic retry
const {
data: sendLink,
Expand Down Expand Up @@ -168,6 +169,10 @@ export const Claim = ({}) => {
screen: _consts.CLAIM_SCREEN_FLOW[newIdx],
idx: newIdx,
}))

if (step.screen === 'SUCCESS') {
triggerHaptic()
}
}
const handleOnPrev = () => {
if (step.idx === 0) return
Expand Down
2 changes: 1 addition & 1 deletion src/components/Claim/Link/Onchain/Confirm.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useTokenChainIcons } from '@/hooks/useTokenChainIcons'
import { useWallet } from '@/hooks/wallet/useWallet'
import { ErrorHandler, formatTokenAmount, printableAddress, isStableCoin } from '@/utils'
import * as Sentry from '@sentry/nextjs'
import { useContext, useState, useMemo } from 'react'
import { useContext, useState, useMemo, useEffect } from 'react'
import { formatUnits } from 'viem'
import * as _consts from '../../Claim.consts'
import useClaimLink from '../../useClaimLink'
Expand Down
24 changes: 19 additions & 5 deletions src/components/Claim/Link/Onchain/Success.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import { useClaimBankFlow } from '@/context/ClaimBankFlowContext'
import { useUserStore } from '@/redux/hooks'
import { ESendLinkStatus, sendLinksApi } from '@/services/sendLinks'
import { formatTokenAmount, getTokenDetails, printableAddress, shortenStringLong } from '@/utils'
import { useQueryClient, useQuery } from '@tanstack/react-query'
import { useQueryClient } from '@tanstack/react-query'
import { useRouter } from 'next/navigation'
import { useEffect, useMemo, useRef } from 'react'
import { useEffect, useMemo } from 'react'
import type { Hash } from 'viem'
import { formatUnits } from 'viem'
import * as _consts from '../../Claim.consts'
import Image from 'next/image'
import { PEANUT_LOGO_BLACK, PEANUTMAN_LOGO } from '@/assets'
import CreateAccountButton from '@/components/Global/CreateAccountButton'
import chillPeanutAnim from '@/animations/GIF_ALPHA_BACKGORUND/512X512_ALPHA_GIF_konradurban_01.gif'
import Image from 'next/image'
import { useHaptic } from 'use-haptic'

export const SuccessClaimLinkView = ({
transactionHash,
Expand All @@ -31,6 +32,7 @@ export const SuccessClaimLinkView = ({
const router = useRouter()
const queryClient = useQueryClient()
const { offrampDetails, claimType, bankDetails } = useClaimBankFlow()
const { triggerHaptic } = useHaptic()

// @dev: Claimers don't earn points (only senders do), so we don't call calculatePoints
// Points will show in activity history once the sender's transaction is processed
Expand Down Expand Up @@ -163,6 +165,11 @@ export const SuccessClaimLinkView = ({
return <CreateAccountButton onClick={() => router.push('/setup')} />
}

useEffect(() => {
// trigger haptic on mount
triggerHaptic()
}, [triggerHaptic])

return (
<div className="flex min-h-[inherit] flex-col justify-between gap-8">
<SoundPlayer sound="success" />
Expand All @@ -175,7 +182,14 @@ export const SuccessClaimLinkView = ({
}}
/>
</div>
<div className="my-auto flex h-full flex-col justify-center space-y-4">
<div className="relative z-10 my-auto flex h-full flex-col justify-center space-y-4">
<Image
src={chillPeanutAnim.src}
alt="Peanut Mascot"
width={240}
height={240}
className="absolute -top-32 left-1/2 -z-10 h-60 w-60 -translate-x-1/2"
/>
Comment thread
Zishan-7 marked this conversation as resolved.
<PeanutActionDetailsCard {...cardProps} />
{renderButtons()}
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/components/Global/DirectSendQR/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { twMerge } from 'tailwind-merge'
import ActionModal from '../ActionModal'
import { Icon, type IconName } from '../Icons/Icon'
import { EQrType, NAME_BY_QR_TYPE, parseEip681, recognizeQr } from './utils'
import { useHaptic } from 'use-haptic'

const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL!

Expand Down Expand Up @@ -196,6 +197,7 @@ export default function DirectSendQr({
if (!user?.user.username) return ''
return `${BASE_URL}/pay/${user.user.username}`
}, [user?.user.username])
const { triggerHaptic } = useHaptic()

const startScanner = () => {
setIsQRScannerOpen(true)
Expand Down Expand Up @@ -233,6 +235,7 @@ export default function DirectSendQr({
}

const processQRCode = async (data: string): Promise<{ success: boolean; error?: string }> => {
triggerHaptic()
// reset payment state before processing new QR
dispatch(paymentActions.resetPaymentState())

Expand Down
Loading
Loading