Skip to content

Commit 67df3fa

Browse files
authored
Transaction Dashboard: Events (#1677)
* Transaction Dashboard: Events in progress * Fix type * Updated UI * Fix tests * Added tests
1 parent 3675575 commit 67df3fa

File tree

12 files changed

+1088
-44
lines changed

12 files changed

+1088
-44
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
"use client";
2+
3+
import React, { useState } from "react";
4+
import { Badge, Button, Card, Icon, Text } from "@stellar/design-system";
5+
import { stringify } from "lossless-json";
6+
7+
import { Box } from "@/components/layout/Box";
8+
import { ScValPrettyJson } from "@/components/ScValPrettyJson";
9+
import { ExpandBox } from "@/components/ExpandBox";
10+
import { CopyJsonPayloadButton } from "@/components/CopyJsonPayloadButton";
11+
12+
import { shortenStellarAddress } from "@/helpers/shortenStellarAddress";
13+
import { FormattedTxEvent, formatTxEvents } from "@/helpers/formatTxEvents";
14+
import { buildContractExplorerHref } from "@/helpers/buildContractExplorerHref";
15+
import { useIsXdrInit } from "@/hooks/useIsXdrInit";
16+
import {
17+
AnyObject,
18+
RpcTxJsonResponseContractEventsJson,
19+
RpcTxJsonResponseTransactionEventsJson,
20+
} from "@/types/types";
21+
22+
export const Events = ({
23+
txEvents,
24+
}: {
25+
txEvents:
26+
| {
27+
contractEventsJson: RpcTxJsonResponseContractEventsJson;
28+
transactionEventsJson: RpcTxJsonResponseTransactionEventsJson;
29+
}
30+
| undefined;
31+
}) => {
32+
const isXdrInit = useIsXdrInit();
33+
34+
const events = txEvents
35+
? formatTxEvents({
36+
contractEvents: txEvents.contractEventsJson[0],
37+
transactionEvents: txEvents.transactionEventsJson,
38+
})
39+
: null;
40+
41+
if (
42+
!(
43+
events?.formattedTransactionEvents?.length ||
44+
events?.formattedContractEvents?.length
45+
)
46+
) {
47+
return (
48+
<Text as="div" size="sm" weight="regular">
49+
There are no events in this transaction.
50+
</Text>
51+
);
52+
}
53+
54+
const EventItem = ({ label, value }: { label: string; value: AnyObject }) => {
55+
return (
56+
<div className="TransactionEvents__item">
57+
<div className="TransactionEvents__item__label">{label}</div>
58+
<div className="TransactionEvents__item__value">
59+
<ScValPrettyJson
60+
json={value}
61+
isReady={isXdrInit}
62+
formatAsset={true}
63+
/>
64+
</div>
65+
</div>
66+
);
67+
};
68+
69+
const ContractIdBadge = ({ children }: { children: string }) => {
70+
return (
71+
<div
72+
className="TransactionEvents__badgeLink"
73+
onClick={(e) => {
74+
e.preventDefault();
75+
// No need to sanitize this URL because we built it
76+
window.open(
77+
`https://lab.stellar.org${buildContractExplorerHref(children)}`,
78+
"_blank",
79+
"noopener,noreferrer",
80+
);
81+
}}
82+
role="link"
83+
>
84+
<Badge
85+
variant="secondary"
86+
size="sm"
87+
icon={<Icon.LinkExternal01 />}
88+
>{`Contract ID: ${shortenStellarAddress(children)}`}</Badge>
89+
</div>
90+
);
91+
};
92+
93+
const TransactionCard = ({
94+
id,
95+
title,
96+
events,
97+
}: {
98+
id: string;
99+
title: string;
100+
events: FormattedTxEvent[] | undefined;
101+
}) => {
102+
if (!events?.length) {
103+
return null;
104+
}
105+
106+
return (
107+
<Box gap="lg">
108+
<ExpandSection title={title}>
109+
{events.map((t, cIndex) => (
110+
<div
111+
className="TransactionEvents__card"
112+
key={`${id}-${cIndex}-${t.contractId}`}
113+
data-testid={`${id}-item`}
114+
>
115+
<ContractIdBadge>{t.contractId}</ContractIdBadge>
116+
117+
<div className="TransactionEvents__card TransactionEvents__card--inset TransactionEvents__card--data">
118+
{t.topics.map((to, tIndex) => (
119+
<EventItem
120+
key={`${id}-${cIndex}-${t.contractId}-topic-${tIndex}`}
121+
label={to.label}
122+
value={to.value}
123+
/>
124+
))}
125+
126+
<EventItem label="Data" value={t.data} />
127+
</div>
128+
129+
<EventJson json={t.rawJson} />
130+
</div>
131+
))}
132+
</ExpandSection>
133+
</Box>
134+
);
135+
};
136+
137+
return (
138+
<Box gap="lg" addlClassName="TransactionEvents">
139+
<TransactionCard
140+
id="ev-c"
141+
title="Contract Events"
142+
events={events.formattedContractEvents}
143+
/>
144+
145+
<TransactionCard
146+
id="ev-t"
147+
title="Transaction Events"
148+
events={events.formattedTransactionEvents}
149+
/>
150+
</Box>
151+
);
152+
};
153+
154+
// =============================================================================
155+
// Local Components
156+
// =============================================================================
157+
const ExpandSection = ({
158+
title,
159+
children,
160+
}: {
161+
title: string;
162+
children: React.ReactNode;
163+
}) => {
164+
const [isExpanded, setIsExpanded] = useState(true);
165+
166+
return (
167+
<Card>
168+
<div
169+
className="ExpandToggle"
170+
onClick={() => {
171+
setIsExpanded(!isExpanded);
172+
}}
173+
data-is-expanded={isExpanded}
174+
>
175+
<Text as="h3" size="sm" weight="medium">
176+
{title}
177+
</Text>
178+
<Icon.ChevronRight />
179+
</div>
180+
181+
<ExpandBox offsetTop="lg" isExpanded={isExpanded}>
182+
<Box gap="lg">{children}</Box>
183+
</ExpandBox>
184+
</Card>
185+
);
186+
};
187+
188+
const EventJson = ({ json }: { json: AnyObject }) => {
189+
const [isExpanded, setIsExpanded] = useState(false);
190+
191+
const formatJson = () => {
192+
try {
193+
return stringify(json, null, 2);
194+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
195+
} catch (e) {
196+
return "";
197+
}
198+
};
199+
200+
const formattedJson = formatJson();
201+
202+
if (!formattedJson) {
203+
return null;
204+
}
205+
206+
return (
207+
<>
208+
<Button
209+
variant={isExpanded ? "secondary" : "tertiary"}
210+
size="sm"
211+
icon={<Icon.ChevronDown />}
212+
onClick={() => setIsExpanded(!isExpanded)}
213+
data-is-expanded={isExpanded}
214+
>
215+
View JSON
216+
</Button>
217+
218+
<ExpandBox offsetTop="md" isExpanded={isExpanded}>
219+
<Box gap="md">
220+
<div className="TransactionEvents__card TransactionEvents__card--inset">
221+
<pre>{formattedJson}</pre>
222+
</div>
223+
<Box gap="sm" direction="row" justify="end">
224+
<CopyJsonPayloadButton size="sm" jsonString={formattedJson} />
225+
</Box>
226+
</Box>
227+
</ExpandBox>
228+
</>
229+
);
230+
};

src/app/(sidebar)/transaction-dashboard/components/FeeBreakdown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export const FeeBreakdown = ({
138138
<GridTableRow>
139139
<GridTableCell>
140140
<div
141-
className="FeeBreakdown__resourceFeeToggle"
141+
className="ExpandToggle"
142142
onClick={() => {
143143
setIsExpanded(!isExpanded);
144144
}}

src/app/(sidebar)/transaction-dashboard/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { TransactionInfo } from "./components/TransactionInfo";
2727
import { StateChange } from "./components/StateChange";
2828
import { FeeBreakdown } from "./components/FeeBreakdown";
2929
import { Signatures } from "./components/Signatures";
30+
import { Events } from "./components/Events";
3031

3132
import "./styles.scss";
3233

@@ -324,8 +325,8 @@ export default function TransactionDashboard() {
324325
tab3={{
325326
id: "tx-events",
326327
label: "Events",
327-
content: <ComingSoonText />,
328-
isDisabled: true,
328+
content: <Events txEvents={txDetails?.events} />,
329+
isDisabled: !isDataLoaded,
329330
}}
330331
tab4={{
331332
id: "tx-state-change",

0 commit comments

Comments
 (0)