Skip to content

Commit

Permalink
Feat: Improve stardust Transaction history UX (#872)
Browse files Browse the repository at this point in the history
* Change view to date, transactionId, value.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* Refactoring.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* Change properties.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* TransactionCard changes.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* Changes after MR comments.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* feat: changes after MR comments.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* feat: changes after MR comments.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* feat: create transactionHistoryUtils.ts

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* feat: Move one more function and interface.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: fixes for MR comments.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: rethinking transaction history view.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: revert prev commit.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: remove last transaction if user don't have all outputs.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: remove dark bg

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* fix: add edge case for spent outputs by the return address (#937)

* chore: Finding of way how to include unclaimed transactions.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: Request outputs and details using async/await.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: Request list of details and set it.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: update groupOutputsByTransactionId

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: calculate transactions.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: Investigate alternative joining.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: Cleaning branch.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: Get transactionId from outputDetails.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: Don't show tx if balance 0.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* chore: fix formatting after merge conflicts.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* fix: calculation for transaction.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* fix: changes after review.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

---------

Signed-off-by: Eugene Panteleymonchuk <[email protected]>
Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
  • Loading branch information
panteleymonchuk and begonaalvarezd authored Jan 19, 2024
1 parent 41f619f commit 0a15fce
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 250 deletions.
52 changes: 20 additions & 32 deletions client/src/app/components/stardust/history/TransactionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,45 @@
import classNames from "classnames";
import moment from "moment";
import React, { useContext } from "react";
import { ITransactionEntryProps } from "./TransactionEntryProps";
import { DateHelper } from "~helpers/dateHelper";
import { TransactionsHelper } from "~helpers/stardust/transactionsHelper";
import { formatAmount } from "~helpers/stardust/valueFormatHelper";
import { CHRYSALIS_MAINNET } from "~models/config/networkType";
import NetworkContext from "../../../context/NetworkContext";
import React from "react";
import TruncatedId from "../TruncatedId";
import Tooltip from "~app/components/Tooltip";
import { ITransactionEntryProps } from "./TransactionEntryProps";

const TransactionCard: React.FC<ITransactionEntryProps> = ({
outputId,
isGenesisByDate,
isTransactionFromStardustGenesis,
transactionLink,
dateFormatted,
balanceChangeFormatted,
transactionId,
date,
milestoneIndex,
value,
isSpent,
isFormattedAmounts,
setIsFormattedAmounts,
}) => {
const { name: network, tokenInfo } = useContext(NetworkContext);
const ago = moment(date * 1000).fromNow();

const valueView = (
<span className="pointer margin-r-5" onClick={() => setIsFormattedAmounts(!isFormattedAmounts)}>
{`${isSpent ? "-" : "+"} ${formatAmount(value, tokenInfo, !isFormattedAmounts)}`}
{balanceChangeFormatted}
</span>
);

const isTransactionFromStardustGenesis =
milestoneIndex && TransactionsHelper.isTransactionFromIotaStardustGenesis(network, milestoneIndex);
const transactionLink = isTransactionFromStardustGenesis
? `/${CHRYSALIS_MAINNET}/search/${transactionId}`
: `/${network}/transaction/${transactionId}`;

return (
<div className="card">
<div className="field">
<div className="card--label">Transaction Id</div>
<div className="row card--value">
<TruncatedId id={transactionId} link={transactionLink} />
</div>
<div className="card--label">Date</div>
{isGenesisByDate ? <div className="card--value">Genesis</div> : <div className="card--value">{dateFormatted}</div>}
</div>
<div className="field">
<div className="card--label">Output Id</div>
<div className="card--label">Transaction Id</div>
<div className="row card--value">
<TruncatedId id={outputId} link={`/${network}/output/${outputId}`} />
<TruncatedId id={transactionId} link={transactionLink} />
{isTransactionFromStardustGenesis && (
<Tooltip tooltipContent="This link opens the transaction on Chrysalis Mainnet" childrenClass="row middle">
<span className="material-icons" style={{ fontSize: "14px" }}>
warning
</span>
</Tooltip>
)}
</div>
</div>
<div className="field">
<div className="card--label">Date</div>
<div className="card--value">{`${DateHelper.formatShort(date * 1000)} (${ago})`}</div>
</div>
<div className="field">
<div className="card--label">Value</div>
<div className={classNames("amount", "card--value", { negative: isSpent })}>{valueView}</div>
Expand Down
37 changes: 18 additions & 19 deletions client/src/app/components/stardust/history/TransactionEntryProps.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,46 @@
export interface ITransactionEntryProps {
/**
* The output id.
* The transaction id.
*/
outputId: string;
transactionId: string;

/**
* The transaction id.
* The formatted date of the transaction.
*/
transactionId: string;
dateFormatted: string;

/**
* The date of the transaction.
* Is the transaction spent.
*/
date: number;
isSpent: boolean;

/**
* The milestone index of the transaction.
* Are the amounts formatted.
*/
milestoneIndex: number;
isFormattedAmounts: boolean;

/**
* The transaction amount.
* The setter for formatted amounts toggle.
*/
value: number;
setIsFormattedAmounts: React.Dispatch<React.SetStateAction<boolean>>;

/**
* Is the transaction spent.
* The formatted transaction amount.
*/
isSpent: boolean;
balanceChangeFormatted: string;

/**
* Are the amounts formatted.
* Check if transaction from stardust by TransactionHelper.
*/
isFormattedAmounts: boolean;
isTransactionFromStardustGenesis: boolean;

/**
* The setter for formatted amounts toggle.
* check some of outputs timestamps zero
*/
setIsFormattedAmounts: React.Dispatch<React.SetStateAction<boolean>>;
isGenesisByDate: boolean;

/**
* To colour the transaction row ligter/darker, alternating on
* unrelated transactions.
* The transaction link.
*/
darkBackgroundRow?: boolean;
transactionLink: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@
}

td {
padding: 16px 0;
padding: 16px;
text-align: center;
width: 33%;
text-wrap: nowrap;

&.transaction-id,
&.output-id {
Expand Down
138 changes: 53 additions & 85 deletions client/src/app/components/stardust/history/TransactionHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,124 +1,92 @@
/* eslint-disable no-void */
import React, { useEffect, useState } from "react";
import TransactionCard from "./TransactionCard";
import TransactionRow from "./TransactionRow";
import React, { useContext, useEffect, useMemo, useState } from "react";
import { useAddressHistory } from "~helpers/hooks/useAddressHistory";
import NetworkContext from "~app/context/NetworkContext";
import DownloadModal from "../DownloadModal";
import { getTransactionHistoryRecords } from "./transactionHistoryUtils";
import TransactionRow from "./TransactionRow";
import TransactionCard from "./TransactionCard";
import "./TransactionHistory.scss";

interface TransactionHistoryProps {
export interface TransactionHistoryProps {
readonly network: string;
readonly address?: string;
readonly setLoading: (isLoadin: boolean) => void;
readonly setDisabled?: (isDisabled: boolean) => void;
}

const TransactionHistory: React.FC<TransactionHistoryProps> = ({ network, address, setLoading, setDisabled }) => {
const [historyView, outputDetailsMap, loadMore, isLoading, hasMore] = useAddressHistory(network, address, setDisabled);
const [transactionIdToOutputs, loadMore, isLoading, hasMore] = useAddressHistory(network, address, setDisabled);

const [isFormattedAmounts, setIsFormattedAmounts] = useState(true);
const { tokenInfo } = useContext(NetworkContext);

useEffect(() => {
setLoading(isLoading);
}, [isLoading]);

let isDarkBackgroundRow = false;
const transactions = useMemo(() => {
const transactionsLocal = getTransactionHistoryRecords(transactionIdToOutputs, network, tokenInfo, isFormattedAmounts);
if (hasMore) {
// remove last transaction, as it's potentially doesn't have all outputs
transactionsLocal.pop();
}
return transactionsLocal;
}, [transactionIdToOutputs, tokenInfo, isFormattedAmounts, hasMore]);

return historyView.length > 0 && address ? (
return transactions.length > 0 && address ? (
<div className="section transaction-history--section">
<div className="section--header row end">
<DownloadModal network={network} address={address} />
</div>
<table className="transaction-history--table">
<thead>
<tr>
<th>Transaction Id</th>
<th>Output Id</th>
<th>Date</th>
<th>Transaction Id</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{historyView.length > 0 &&
historyView.map((historyItem, idx) => {
const outputDetails = outputDetailsMap[historyItem.outputId];
if (!outputDetails) {
return null;
}
const transactionId = historyItem.isSpent
? outputDetails.metadata.transactionIdSpent
: outputDetails.metadata.transactionId;

if (!transactionId) {
return null;
}

// rotate row background colour for different transaction ids
if (idx > 0) {
const previousItemDetails = outputDetailsMap[historyView[idx - 1].outputId];
const previousSpent = historyView[idx - 1].isSpent;
if (previousItemDetails) {
const previousTransactionId = previousSpent
? previousItemDetails.metadata.transactionIdSpent
: previousItemDetails.metadata.transactionId;

if (transactionId !== previousTransactionId) {
isDarkBackgroundRow = !isDarkBackgroundRow;
}
}
}

return (
<React.Fragment key={idx}>
<TransactionRow
outputId={historyItem.outputId}
transactionId={transactionId}
date={historyItem.milestoneTimestamp}
milestoneIndex={historyItem.milestoneIndex}
value={Number(outputDetails.output.amount)}
isSpent={historyItem.isSpent}
isFormattedAmounts={isFormattedAmounts}
setIsFormattedAmounts={setIsFormattedAmounts}
darkBackgroundRow={isDarkBackgroundRow}
/>
</React.Fragment>
);
})}
{transactions?.map((c, idx) => (
<React.Fragment key={idx}>
<TransactionRow
isGenesisByDate={c.isGenesisByDate}
isTransactionFromStardustGenesis={c.isTransactionFromStardustGenesis}
transactionLink={c.transactionLink}
dateFormatted={c.dateFormatted}
balanceChangeFormatted={c.balanceChangeFormatted}
transactionId={c.transactionId}
isSpent={c.isSpent}
isFormattedAmounts={isFormattedAmounts}
setIsFormattedAmounts={setIsFormattedAmounts}
/>
</React.Fragment>
))}
</tbody>
</table>

{/* Only visible in mobile -- Card transactions*/}
{/** Only visible in mobile -- Card transactions */}
<div className="transaction-history--cards">
{historyView.length > 0 &&
historyView.map((historyItem, idx) => {
const outputDetails = outputDetailsMap[historyItem.outputId];
if (!outputDetails) {
return null;
}
const transactionId = historyItem.isSpent
? outputDetails.metadata.transactionIdSpent
: outputDetails.metadata.transactionId;

if (!transactionId) {
return null;
}

return (
<React.Fragment key={idx}>
<TransactionCard
outputId={historyItem.outputId}
transactionId={transactionId}
date={historyItem.milestoneTimestamp}
milestoneIndex={historyItem.milestoneIndex}
value={Number(outputDetails.output.amount)}
isSpent={historyItem.isSpent}
isFormattedAmounts={isFormattedAmounts}
setIsFormattedAmounts={setIsFormattedAmounts}
/>
</React.Fragment>
);
})}
{transactions.map((c, idx) => {
return (
<React.Fragment key={idx}>
<TransactionCard
isGenesisByDate={c.isGenesisByDate}
isTransactionFromStardustGenesis={c.isTransactionFromStardustGenesis}
transactionLink={c.transactionLink}
dateFormatted={c.dateFormatted}
balanceChangeFormatted={c.balanceChangeFormatted}
transactionId={c.transactionId}
isSpent={c.isSpent}
isFormattedAmounts={isFormattedAmounts}
setIsFormattedAmounts={setIsFormattedAmounts}
/>
</React.Fragment>
);
})}
</div>
{hasMore && historyView.length > 0 && (
{hasMore && transactions.length > 0 && (
<div className="card load-more--button" onClick={loadMore}>
<button type="button">Load more...</button>
</div>
Expand Down
Loading

0 comments on commit 0a15fce

Please sign in to comment.