Skip to content

feat: show list pending offer in Transfers page #441

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ abstract class BaseWalletTransfersFrontendIntegrationTest
offerCard.childElement(className("transfer-offer-sender"))
) should matchText(expectedAns(aliceUserParty, aliceAnsName))

seleniumText(
offerCard.childElement(className("transfer-offer-received"))
Copy link
Contributor

Choose a reason for hiding this comment

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

transfer-offer-receiver

) should matchText(expectedAns(bobUserParty, bobAnsName))

Copy link
Contributor

Choose a reason for hiding this comment

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

Does this pass? You shouldn't see both the sender and receiver at the same time according to the rendering logic below

offerCard
.childElement(className("transfer-offer-amulet-amount"))
.text should matchText(
Expand All @@ -143,6 +147,7 @@ abstract class BaseWalletTransfersFrontendIntegrationTest
val aliceDamlUser = aliceWalletClient.config.ledgerApiUser
val aliceUserParty = onboardWalletUser(aliceWalletClient, aliceValidatorBackend)
val aliceAnsName = perTestCaseName("alice")
val aliceAnsDisplay = expectedAns(aliceUserParty, aliceAnsName)

val bobUserParty = onboardWalletUser(bobWalletClient, bobValidatorBackend)
val bobAnsName = perTestCaseName("bob")
Expand Down Expand Up @@ -190,6 +195,10 @@ abstract class BaseWalletTransfersFrontendIntegrationTest
offerCard.childElement(className("transfer-offer-sender"))
) should matchText(bobAnsDisplay)

seleniumText(
offerCard.childElement(className("transfer-offer-received"))
) should matchText(aliceAnsDisplay)

offerCard.childElement(className("transfer-offer-expiry")).text should matchText(
s"Expires $expectedExpiry"
)
Expand Down
2 changes: 2 additions & 0 deletions apps/wallet/frontend/src/components/SendTransfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import useLookupTransferPreapproval from '../hooks/scan-proxy/useLookupTransferP
import BftAnsField from './BftAnsField';
import { useFeatureSupport } from '../hooks/useFeatureSupport';
import AmountInput from './AmountInput';
import { TransferOffers } from './TransferOffers';

const SendTransfer: React.FC = () => {
const { createTransferOffer, transferPreapprovalSend, createTransferViaTokenStandard } =
Expand Down Expand Up @@ -252,6 +253,7 @@ const SendTransfer: React.FC = () => {
</DisableConditionally>
</CardContent>
</Card>
<TransferOffers mode="received" />
</Stack>
);
};
Expand Down
83 changes: 55 additions & 28 deletions apps/wallet/frontend/src/components/TransferOffers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,14 @@ type PartialWalletTransferOffer = {
sender: string;
expiresAt: string;
isTokenStandard: boolean;
receiver: string;
};
export const TransferOffers: React.FC = () => {

type TransferOffersListProps = {
mode: 'sent' | 'received';
};

export const TransferOffers: React.FC<TransferOffersListProps> = ({ mode }) => {
const [offers, setOffers] = useState<WalletTransferOffer[]>([]);
const amuletPriceQuery = useAmuletPrice();
const primaryPartyId = usePrimaryParty();
Expand All @@ -46,7 +52,9 @@ export const TransferOffers: React.FC = () => {
amuletPrice: BigNumber
): Promise<WalletTransferOffer[]> => {
return items
.filter(item => item.sender !== primaryPartyId)
.filter(item =>
mode === 'sent' ? item.sender === primaryPartyId : item.sender !== primaryPartyId
)
.map(item => {
return {
contractId: item.contractId,
Expand All @@ -59,12 +67,13 @@ export const TransferOffers: React.FC = () => {
amuletPrice
),
senderId: item.sender,
receiverId: item.receiver,
expiry: item.expiresAt,
isTokenStandard: item.isTokenStandard,
};
});
},
[primaryPartyId]
[primaryPartyId, mode]
);

const transferOfferContractsQuery = useTransferOffers(amuletPriceQuery.data);
Expand All @@ -82,6 +91,7 @@ export const TransferOffers: React.FC = () => {
contractId: offer.contractId,
amount: offer.payload.amount.amount,
sender: offer.payload.sender,
receiver: offer.payload.receiver,
expiresAt: offer.payload.expiresAt,
};
return item;
Expand All @@ -93,6 +103,7 @@ export const TransferOffers: React.FC = () => {
contractId: transfer.contractId,
amount: transfer.payload.transfer.amount,
sender: transfer.payload.transfer.sender,
receiver: transfer.payload.transfer.receiver,
expiresAt: transfer.payload.transfer.executeBefore,
};
return item;
Expand All @@ -110,11 +121,12 @@ export const TransferOffers: React.FC = () => {
amuletPriceQuery.isError ||
transferOfferContractsQuery.isError ||
tokenStandardTransfersQuery.isError;
const heading = mode === 'sent' ? 'Pending Offers ' : 'Action Needed ';

return (
<Stack spacing={4} direction="column" justifyContent="center" id="transfer-offers">
<Typography mt={6} variant="h4">
Action Needed{' '}
{heading}
<Chip label={offers.length} color="success" className="transfer-offers-count" />
</Typography>
{isLoading ? (
Expand All @@ -127,7 +139,7 @@ export const TransferOffers: React.FC = () => {
</Box>
) : (
offers.map((offer, index) => (
<TransferOfferDisplay key={'offer-' + index} transferOffer={offer} />
<TransferOfferDisplay key={'offer-' + index} transferOffer={offer} mode={mode} />
))
)}
</Stack>
Expand All @@ -136,11 +148,14 @@ export const TransferOffers: React.FC = () => {

interface TransferOfferProps {
transferOffer: WalletTransferOffer;
mode: 'sent' | 'received';
}

export const TransferOfferDisplay: React.FC<TransferOfferProps> = props => {
const config = useWalletConfig();
const offer = props.transferOffer;
const mode = props.mode;

const {
acceptTransferOffer,
rejectTransferOffer,
Expand All @@ -151,7 +166,7 @@ export const TransferOfferDisplay: React.FC<TransferOfferProps> = props => {
const reject = offer.isTokenStandard ? rejectTokenStandardTransfer : rejectTransferOffer;

return (
<Card className="transfer-offer" variant="outlined">
<Card className={mode === 'sent' ? 'pending-offer' : 'transfer-offer'} variant="outlined">
<CardContent
sx={{
display: 'flex',
Expand All @@ -163,11 +178,19 @@ export const TransferOfferDisplay: React.FC<TransferOfferProps> = props => {
<ArrowCircleLeftOutlined fontSize="large" />
<Stack direction="row" alignItems="center">
<Stack direction="column">
<BftAnsEntry
partyId={offer.senderId}
variant="h5"
className={'transfer-offer-sender'}
/>
{mode === 'received' ? (
<BftAnsEntry
partyId={offer.senderId}
variant="h5"
className={'transfer-offer-sender'}
/>
) : (
<BftAnsEntry
partyId={offer.receiverId}
variant="h5"
className={'transfer-offer-receiver'}
/>
)}
</Stack>
</Stack>
<Stack direction="column" alignItems="flex-end">
Expand All @@ -186,23 +209,27 @@ export const TransferOfferDisplay: React.FC<TransferOfferProps> = props => {
</Typography>
</Stack>
<Stack direction="row" alignItems="center" spacing={2}>
<Button
variant="pill"
size="small"
onClick={() => accept(offer.contractId)}
className="transfer-offer-accept"
>
Accept
</Button>
<Button
variant="pill"
color="warning"
size="small"
onClick={() => reject(offer.contractId)}
className="transfer-offer-reject"
>
Reject
</Button>
{mode !== 'sent' ? (
<>
<Button
variant="pill"
size="small"
onClick={() => accept(offer.contractId)}
className="transfer-offer-accept"
>
Accept
</Button>
<Button
variant="pill"
color="warning"
size="small"
onClick={() => reject(offer.contractId)}
className="transfer-offer-reject"
>
Reject
</Button>
</>
) : null}
</Stack>
<Typography variant="caption" className="transfer-offer-expiry">
Expires <DateDisplay datetime={offer.expiry} />
Expand Down
1 change: 1 addition & 0 deletions apps/wallet/frontend/src/models/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export interface WalletTransferOffer {
conversionRate: string;
convertedCurrency: ConvertedCurrency;
senderId: string;
receiverId: string;
expiry: string;
isTokenStandard: boolean;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/wallet/frontend/src/routes/transactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Transactions: React.FC = () => {
<DevNetOnly>
<Tap />
</DevNetOnly>
<TransferOffers />
<TransferOffers mode="received" />
<TransactionHistory />
</Box>
);
Expand Down