-
Notifications
You must be signed in to change notification settings - Fork 13
Card history #1909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Card history #1909
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
66bf914
card: render merchant-enriched card activity in feed + drawer
jjramirezn 37324c3
card: thread enriched merchant logo + id through drawer extraData
jjramirezn 5d65963
card: refactor receipt drawer — extract provider components, predicates
jjramirezn a744a9a
card: collapse cardPayment row slot when no sub-rows would render
jjramirezn c7a4b27
card: treat whitespace-only / placeholder merchant fields as absent
jjramirezn 2d6fa1e
fix: format
jjramirezn 0e3c88c
receipt: lift derived view-model state into useReceiptViewModel hook
jjramirezn abbab89
card: address review feedback across receipt + transformer
jjramirezn b24d076
fix: format
jjramirezn 04f769e
card: avoid breaking jest module-mock on transaction-predicates
jjramirezn 9ca8e6c
card: guard Bridge US deposit-instructions on actual data presence
jjramirezn 9e741cc
card: break circular import in transaction-predicates (jest)
jjramirezn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
810 changes: 49 additions & 761 deletions
810
src/components/TransactionDetails/TransactionDetailsReceipt.tsx
Large diffs are not rendered by default.
Oops, something went wrong.
156 changes: 156 additions & 0 deletions
156
src/components/TransactionDetails/provider-actions/CancelDepositActions.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| 'use client' | ||
|
|
||
| import { Button } from '@/components/0_Bruddle/Button' | ||
| import { Icon } from '@/components/Global/Icons/Icon' | ||
| import { type TransactionDetails } from '@/components/TransactionDetails/transactionTransformer' | ||
| import { EHistoryEntryType, EHistoryUserRole } from '@/hooks/useTransactionHistory' | ||
| import { TRANSACTIONS } from '@/constants/query.consts' | ||
| import { cancelOnramp } from '@/app/actions/onramp' | ||
| import { chargesApi } from '@/services/charges' | ||
| import { mantecaApi } from '@/services/manteca' | ||
| import { captureException } from '@sentry/nextjs' | ||
| import { useQueryClient } from '@tanstack/react-query' | ||
|
|
||
| /** | ||
| * Cancel-deposit buttons for pending bank-deposit-shaped flows. | ||
| * | ||
| * Replaces three near-identical inline buttons in the receipt: | ||
| * - Bridge onramp pending → cancelOnramp(transaction.id) | ||
| * - Manteca onramp pending → mantecaApi.cancelDeposit(transaction.id) | ||
| * - REQUEST pending + bridge fulfillment + sender role → cancelOnramp(bridgeTransferId) + chargesApi.cancel(transaction.id) | ||
| * | ||
| * Renders at most one button — conditions are mutually exclusive by | ||
| * construction (different originalType / direction / role combos). | ||
| */ | ||
| export function CancelDepositActions({ | ||
| transaction, | ||
| isPendingBankRequest, | ||
| isLoading, | ||
| setIsLoading, | ||
| onClose, | ||
| }: { | ||
| transaction: TransactionDetails | ||
| isPendingBankRequest: boolean | ||
| isLoading: boolean | undefined | ||
| setIsLoading: ((loading: boolean) => void) | undefined | ||
| onClose: (() => void) | undefined | ||
| }) { | ||
| const queryClient = useQueryClient() | ||
| if (!setIsLoading || !onClose) return null | ||
|
|
||
| const refetchAndClose = () => | ||
| queryClient.invalidateQueries({ queryKey: [TRANSACTIONS] }).then(() => { | ||
| setIsLoading(false) | ||
| onClose() | ||
| }) | ||
|
|
||
| const wrapAction = async (run: () => Promise<void>) => { | ||
| setIsLoading(true) | ||
| try { | ||
| await run() | ||
| await refetchAndClose() | ||
| } catch (error) { | ||
| captureException(error) | ||
| console.error('Error canceling deposit:', error) | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
|
|
||
| // 1. Bridge onramp pending — generic bank deposit cancel. | ||
| const showBridgeOnrampCancel = | ||
| transaction.direction === 'bank_deposit' && | ||
| transaction.extraDataForDrawer?.originalType !== EHistoryEntryType.REQUEST && | ||
| transaction.status === 'pending' && | ||
| !!transaction.extraDataForDrawer?.depositInstructions | ||
|
|
||
| if (showBridgeOnrampCancel) { | ||
| return ( | ||
| <CancelButton | ||
| disabled={!!isLoading} | ||
| onClick={() => | ||
| wrapAction(async () => { | ||
| const result = await cancelOnramp(transaction.id) | ||
| if (result.error) throw new Error(result.error) | ||
| }) | ||
| } | ||
| /> | ||
| ) | ||
| } | ||
|
|
||
| // 2. Manteca onramp pending. | ||
| const showMantecaCancel = | ||
| transaction.extraDataForDrawer?.originalType === EHistoryEntryType.MANTECA_ONRAMP && | ||
| transaction.status === 'pending' | ||
|
|
||
| if (showMantecaCancel) { | ||
| return ( | ||
| <CancelButton | ||
| disabled={!!isLoading} | ||
| onClick={() => | ||
| wrapAction(async () => { | ||
| const result = await mantecaApi.cancelDeposit(transaction.id) | ||
| if (result.error) throw new Error(result.error) | ||
| }) | ||
| } | ||
| /> | ||
| ) | ||
| } | ||
|
|
||
| // 3. REQUEST pending + bridge fulfillment + sender role — cancels the | ||
| // bridge-side onramp first, then the charge so the recipient stops seeing | ||
| // the request as outstanding. | ||
| const showPendingBankRequestCancel = | ||
| isPendingBankRequest && transaction.extraDataForDrawer?.originalUserRole === EHistoryUserRole.SENDER | ||
|
|
||
| if (showPendingBankRequestCancel) { | ||
| return ( | ||
| <div className="pr-1"> | ||
| <CancelButton | ||
| label="Cancel Request" | ||
| disabled={!!isLoading} | ||
| onClick={() => | ||
| wrapAction(async () => { | ||
| const bridgeTransferId = transaction.extraDataForDrawer?.bridgeTransferId | ||
| if (!bridgeTransferId) { | ||
| throw new Error('Cannot cancel REQUEST: missing bridgeTransferId on transaction') | ||
| } | ||
| // Bridge cancel must succeed before we cancel the | ||
| // charge — otherwise the onramp orphans on Bridge's | ||
| // side while the user sees the request as cancelled. | ||
| const bridgeResult = await cancelOnramp(bridgeTransferId) | ||
| if (bridgeResult.error) throw new Error(bridgeResult.error) | ||
| await chargesApi.cancel(transaction.id) | ||
| }) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| /> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| return null | ||
| } | ||
|
|
||
| function CancelButton({ | ||
| label = 'Cancel deposit', | ||
| disabled, | ||
| onClick, | ||
| }: { | ||
| label?: string | ||
| disabled: boolean | ||
| onClick: () => void | ||
| }) { | ||
| return ( | ||
| <Button | ||
| disabled={disabled} | ||
| onClick={onClick} | ||
| variant={'primary-soft'} | ||
| className="flex w-full items-center gap-1" | ||
| shadowSize="4" | ||
| > | ||
| <div className="flex items-center"> | ||
| <Icon name="cancel" className="mr-0.5 min-w-3 rounded-full border border-black p-0.5" /> | ||
| </div> | ||
| <span>{label}</span> | ||
| </Button> | ||
| ) | ||
| } | ||
93 changes: 93 additions & 0 deletions
93
src/components/TransactionDetails/provider-receipts/PerkRewardReceipt.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| 'use client' | ||
|
|
||
| import { type RefObject } from 'react' | ||
| import { twMerge } from 'tailwind-merge' | ||
| import Card from '@/components/Global/Card' | ||
| import { PaymentInfoRow } from '@/components/Payment/PaymentInfoRow' | ||
| import { Icon } from '@/components/Global/Icons/Icon' | ||
| import { PerkIcon } from '@/components/TransactionDetails/PerkIcon' | ||
| import { type TransactionDetails } from '@/components/TransactionDetails/transactionTransformer' | ||
| import { type HistoryEntryPerkReward } from '@/services/services.types' | ||
| import { formatDate } from '@/utils/general.utils' | ||
| import { useModalsContext } from '@/context/ModalsContext' | ||
|
|
||
| /** | ||
| * Self-contained receipt for PERK_REWARD entries. Replaces the early-return | ||
| * branch in TransactionDetailsReceipt — Perk has its own header (PerkIcon + | ||
| * "Peanut Reward" copy), its own status pills, and a tiny detail card with | ||
| * date + reason. None of it composes with the generic transaction details | ||
| * card, hence a separate top-level layout instead of slotting into rows. | ||
| */ | ||
| export function PerkRewardReceipt({ | ||
| transaction, | ||
| perkRewardData, | ||
| amountDisplay, | ||
| contentRef, | ||
| className, | ||
| }: { | ||
| transaction: TransactionDetails | ||
| perkRewardData: HistoryEntryPerkReward | ||
| amountDisplay: string | ||
| contentRef?: RefObject<HTMLDivElement> | ||
| className?: string | ||
| }) { | ||
| const { setIsSupportModalOpen } = useModalsContext() | ||
|
|
||
| return ( | ||
| <div ref={contentRef} className={twMerge('space-y-4', className)}> | ||
| {/* Perk Reward Header — top section with logo, amount, and status */} | ||
| <Card position="single" className="px-4 py-6"> | ||
| <div className="flex items-start justify-between"> | ||
| <div className="flex items-center gap-3"> | ||
| <PerkIcon size="medium" /> | ||
| <div className="flex flex-col"> | ||
| <h2 className="text-lg font-semibold text-gray-900">Peanut Reward</h2> | ||
| <p className="text-2xl font-bold text-gray-900">{amountDisplay}</p> | ||
| </div> | ||
| </div> | ||
| <div className="flex-shrink-0"> | ||
| {transaction.status === 'completed' ? ( | ||
| <span className="rounded-full bg-green-100 px-3 py-1 text-xs font-medium text-green-700"> | ||
| Completed | ||
| </span> | ||
| ) : transaction.status === 'pending' || transaction.status === 'processing' ? ( | ||
| <span className="rounded-full bg-yellow-100 px-3 py-1 text-xs font-medium text-yellow-700"> | ||
| Processing | ||
| </span> | ||
| ) : ( | ||
| <span className="rounded-full bg-gray-100 px-3 py-1 text-xs font-medium text-gray-700"> | ||
| {transaction.status} | ||
| </span> | ||
| )} | ||
| </div> | ||
| </div> | ||
| <p className="mt-3 text-sm text-gray-600">Earn rewards every time your friends use Peanut.</p> | ||
| </Card> | ||
|
|
||
| {/* Perk details — date + reason. Reason has a payment-UUID suffix | ||
| stripped because PerkUsage uses it for idempotency (purchase- | ||
| listener.ts) and shouldn't surface to users. Backend follow-up: | ||
| add requestPaymentUuid column so reason can be clean. */} | ||
| <Card position="single" className="px-4 py-0"> | ||
| <PaymentInfoRow | ||
| label="Received" | ||
| value={formatDate(new Date(transaction.date))} | ||
| hideBottomBorder={false} | ||
| /> | ||
| <PaymentInfoRow | ||
| label="Reason" | ||
| value={perkRewardData.reason.replace(/\s*\(payment:\s*[a-f0-9-]+\)/i, '')} | ||
| hideBottomBorder={true} | ||
| /> | ||
| </Card> | ||
|
|
||
| <button | ||
| onClick={() => setIsSupportModalOpen(true)} | ||
| className="flex w-full 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? | ||
| </button> | ||
| </div> | ||
| ) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.