Skip to content

Commit 7fc6397

Browse files
authored
Merge pull request #62 from kleros/feat/copiable-addresses-execute-button-and-etherscan-redirection
feat: copiable-addresses, execute-button and etherscan-redirection
2 parents 1d5be3b + 883f8bc commit 7fc6397

File tree

15 files changed

+195
-32
lines changed

15 files changed

+195
-32
lines changed

subgraph/mappings/escrow.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
} from "../generated/EscrowUniversal/EscrowUniversal";
2323
import { ZERO, ONE } from "./utils";
2424

25-
function createEscrow(id: string): Escrow {
25+
function createEscrow(id: string, transactionHash: Bytes): Escrow {
2626
let escrow = new Escrow(id);
2727
escrow.buyer = Bytes.empty();
2828
escrow.seller = Bytes.empty();
@@ -34,6 +34,7 @@ function createEscrow(id: string): Escrow {
3434
escrow.templateData = "";
3535
escrow.templateDataMappings = "";
3636
escrow.status = "NoDispute";
37+
escrow.transactionHash = transactionHash;
3738
return escrow;
3839
}
3940

@@ -95,7 +96,7 @@ export function handleHasToPayFee(event: HasToPayFeeEvent): void {
9596
let escrow = Escrow.load(escrowId);
9697

9798
if (!escrow) {
98-
escrow = createEscrow(escrowId);
99+
return;
99100
}
100101

101102
let seller = getUser(escrow.seller.toHex());
@@ -131,7 +132,7 @@ export function handleHasToPayFee(event: HasToPayFeeEvent): void {
131132

132133
export function handleNativeTransactionCreated(event: NativeTransactionCreatedEvent): void {
133134
let escrowId = event.params._transactionID.toString();
134-
let escrow = Escrow.load(escrowId) || createEscrow(escrowId);
135+
let escrow = Escrow.load(escrowId) || createEscrow(escrowId, event.transaction.hash);
135136

136137
escrow!.buyer = event.params._buyer;
137138
escrow!.seller = event.params._seller;
@@ -163,7 +164,7 @@ export function handleNativeTransactionCreated(event: NativeTransactionCreatedEv
163164

164165
export function handleERC20TransactionCreated(event: ERC20TransactionCreatedEvent): void {
165166
let escrowId = event.params._transactionID.toString();
166-
let escrow = Escrow.load(escrowId) || createEscrow(escrowId);
167+
let escrow = Escrow.load(escrowId) || createEscrow(escrowId, event.transaction.hash);
167168

168169
escrow!.buyer = event.params._buyer;
169170
escrow!.seller = event.params._seller;
@@ -199,7 +200,7 @@ export function handleTransactionResolved(event: TransactionResolvedEvent): void
199200
let escrow = Escrow.load(escrowId);
200201

201202
if (!escrow) {
202-
escrow = createEscrow(escrowId);
203+
return;
203204
}
204205

205206
let transactionResolvedId = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
@@ -248,7 +249,7 @@ export function handleDisputeRequest(event: DisputeRequestEvent): void {
248249

249250
let escrow = Escrow.load(transactionID);
250251
if (!escrow) {
251-
escrow = createEscrow(transactionID);
252+
return;
252253
}
253254

254255
disputeRequest.escrow = escrow.id;
@@ -284,7 +285,7 @@ export function handleSettlementProposed(event: SettlementProposedEvent): void {
284285

285286
let escrow = Escrow.load(transactionID);
286287
if (!escrow) {
287-
escrow = createEscrow(transactionID);
288+
return;
288289
}
289290
escrow.lastFeePaymentTime = event.block.timestamp;
290291

subgraph/schema.graphql

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Escrow @entity {
4141
token: Bytes
4242
status: Status
4343
timestamp: BigInt
44+
transactionHash: Bytes!
4445
payments: [Payment!]! @derivedFrom(field: "escrow")
4546
hasToPayFees: [HasToPayFee!]! @derivedFrom(field: "escrow")
4647
createdEvents: [TransactionCreated!]! @derivedFrom(field: "escrow")
+10
Loading

web/src/components/PreviewCard/Header.tsx

+35-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import React from "react";
2-
import styled from "styled-components";
2+
import styled, { css } from "styled-components";
3+
import { landscapeStyle } from "styles/landscapeStyle";
4+
import { responsiveSize } from "styles/responsiveSize";
5+
import { SUPPORTED_CHAINS, DEFAULT_CHAIN } from "consts/chains";
36
import { isUndefined } from "utils/index";
47
import { mapStatusToEnum } from "utils/mapStatusToEnum";
58
import { StyledSkeleton } from "../StyledSkeleton";
69
import StatusBanner from "../TransactionCard/StatusBanner";
10+
import EtherscanIcon from "svgs/icons/etherscan.svg";
711

812
const Container = styled.div`
913
display: flex;
@@ -30,24 +34,52 @@ const LeftContent = styled.div`
3034
gap: 8px;
3135
`;
3236

37+
const RightContent = styled.div`
38+
display: flex;
39+
align-items: center;
40+
gap: 16px;
41+
42+
${landscapeStyle(
43+
() => css`
44+
flex-shrink: 0;
45+
gap: 0 ${responsiveSize(24, 32, 900)};
46+
`
47+
)}
48+
`;
49+
50+
const StyledEtherscanIcon = styled(EtherscanIcon)`
51+
display: flex;
52+
height: 16px;
53+
width: 16px;
54+
`;
55+
3356
interface IHeader {
3457
escrowType: string;
3558
escrowTitle?: string;
3659
id: string;
3760
status: string;
61+
transactionHash: string;
3862
isCard: boolean;
3963
}
4064

41-
const Header: React.FC<IHeader> = ({ escrowType, escrowTitle, id, status, isCard }) => {
65+
const Header: React.FC<IHeader> = ({ escrowType, escrowTitle, id, status, transactionHash, isCard }) => {
4266
const currentStatusEnum = mapStatusToEnum(status);
67+
const etherscanUrl = `${SUPPORTED_CHAINS[DEFAULT_CHAIN].blockExplorers?.default.url}/tx/${transactionHash}`;
4368

4469
return (
4570
<Container>
4671
<LeftContent>
4772
<StyledLabel>{escrowType === "general" ? "General Escrow" : "Crypto Swap"}</StyledLabel>
4873
{isUndefined(escrowTitle) ? <StyledSkeleton /> : <StyledHeader>{escrowTitle}</StyledHeader>}
4974
</LeftContent>
50-
<StatusBanner status={currentStatusEnum} isPreview={true} />
75+
<RightContent>
76+
{transactionHash ? (
77+
<a href={etherscanUrl} target="_blank" rel="noreferrer">
78+
<StyledEtherscanIcon />
79+
</a>
80+
) : null}
81+
<StatusBanner status={currentStatusEnum} isPreview={true} />
82+
</RightContent>
5183
</Container>
5284
);
5385
};

web/src/components/PreviewCard/Terms/Description.tsx

+35-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import React from "react";
22
import styled from "styled-components";
33
import { StyledSkeleton } from "components/StyledSkeleton";
4+
import { Copiable } from "@kleros/ui-components-library";
5+
import { useEnsName } from "wagmi";
46
import { isUndefined } from "utils/index";
7+
import { shortenAddress } from "utils/shortenAddress";
58

69
const StyledP = styled.p`
710
margin: 0;
@@ -12,6 +15,15 @@ const InlineBlockSpan = styled.span`
1215
display: inline-block;
1316
`;
1417

18+
const StyledCopiable = styled(Copiable)`
19+
margin-right: 2px;
20+
gap: 6px;
21+
`;
22+
23+
const StyledSpan = styled.span`
24+
color: ${({ theme }) => theme.primaryBlue};
25+
`;
26+
1527
interface IDescription {
1628
escrowType: string;
1729
deliverableText: string;
@@ -38,20 +50,38 @@ const Description: React.FC<IDescription> = ({
3850
deadline,
3951
assetSymbol,
4052
}) => {
53+
const buyerEns = useEnsName({ address: buyerAddress, chainId: 1 });
54+
const sellerEns = useEnsName({ address: sellerAddress, chainId: 1 });
55+
56+
const displayBuyerAddress = buyerEns.data || shortenAddress(buyerAddress);
57+
const displaySellerAddress = sellerEns.data || shortenAddress(sellerAddress);
58+
4159
const generalEscrowSummary = (
4260
<>
4361
By Paying {sendingQuantity}{" "}
4462
<InlineBlockSpan>{assetSymbol ? assetSymbol : <StyledSkeleton width={30} />}</InlineBlockSpan>, address{" "}
45-
{buyerAddress} should receive "{deliverableText}" from address {sellerAddress} before the delivery deadline{" "}
46-
{new Date(deadline).toString()}.
63+
<StyledCopiable copiableContent={buyerAddress ?? ""} info="Copy Buyer Address">
64+
<StyledSpan>{displayBuyerAddress}</StyledSpan>
65+
</StyledCopiable>{" "}
66+
should receive "{deliverableText}" from address{" "}
67+
<StyledCopiable copiableContent={sellerAddress ?? ""} info="Copy Seller Address">
68+
<StyledSpan>{displaySellerAddress}</StyledSpan>
69+
</StyledCopiable>{" "}
70+
before the delivery deadline {new Date(deadline).toString()}.
4771
</>
4872
);
4973

5074
const cryptoSwapSummary = (
5175
<>
52-
By Paying {sendingQuantity} {sendingToken}, [Blockchain] address {buyerAddress} should receive {receivingQuantity}{" "}
53-
{receivingToken} at the [Blockchain] address {sellerAddress} from [Blockchain] address TODO before the delivery
54-
deadline {new Date(deadline).toString()}.
76+
By Paying {sendingQuantity} {sendingToken}, [Blockchain] address{" "}
77+
<StyledCopiable copiableContent={buyerAddress ?? ""} info="Copy Buyer Address">
78+
<StyledSpan>{displayBuyerAddress}</StyledSpan>
79+
</StyledCopiable>{" "}
80+
should receive {receivingQuantity} {receivingToken} at the [Blockchain] address{" "}
81+
<StyledCopiable copiableContent={sellerAddress ?? ""} info="Copy Seller Address">
82+
<StyledSpan>{displaySellerAddress}</StyledSpan>
83+
</StyledCopiable>{" "}
84+
from [Blockchain] address TODO before the delivery deadline {new Date(deadline).toString()}.
5585
</>
5686
);
5787

web/src/components/PreviewCard/index.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Header from "./Header";
77
import TransactionInfo from "components/TransactionInfo";
88
import Terms from "./Terms";
99
import EscrowTimeline from "./EscrowTimeline";
10-
import Buttons from "pages/MyTransactions/TransactionDetails/PreviewCardButtons";
10+
import PreviewCardButtons from "pages/MyTransactions/TransactionDetails/PreviewCardButtons";
1111
import { DisputeRequest, HasToPayFee, Payment, SettlementProposal, TransactionResolved } from "src/graphql/graphql";
1212

1313
export const StyledCard = styled(Card)<{ isPreview?: boolean }>`
@@ -54,6 +54,7 @@ interface IPreviewCard {
5454
receivingQuantity: string;
5555
transactionCreationTimestamp: string;
5656
status: string;
57+
transactionHash: string;
5758
buyerAddress: string;
5859
sendingQuantity: string;
5960
sellerAddress: string;
@@ -79,6 +80,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
7980
receivingQuantity,
8081
transactionCreationTimestamp,
8182
status,
83+
transactionHash,
8284
buyerAddress,
8385
sendingQuantity,
8486
sellerAddress,
@@ -97,7 +99,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
9799
arbitrationCost,
98100
}) => (
99101
<StyledCard {...{ isPreview }}>
100-
<Header {...{ escrowType, escrowTitle, status, isCard: false }} />
102+
<Header {...{ escrowType, escrowTitle, status, transactionHash, isCard: false }} />
101103
<TransactionInfoContainer>
102104
<Divider />
103105
<TransactionInfo
@@ -138,7 +140,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
138140
settlementTimeout,
139141
}}
140142
/>
141-
{!isPreview ? <Buttons {...{ feeTimeout, settlementTimeout, arbitrationCost }} /> : null}
143+
{!isPreview ? <PreviewCardButtons {...{ feeTimeout, settlementTimeout, arbitrationCost }} /> : null}
142144
</StyledCard>
143145
);
144146

web/src/components/TransactionInfo/index.tsx

+28-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import React from "react";
22
import styled, { css } from "styled-components";
3-
import Skeleton from "react-loading-skeleton";
43
import { landscapeStyle } from "styles/landscapeStyle";
4+
import { Copiable } from "@kleros/ui-components-library";
5+
import { useEnsName } from "wagmi";
6+
import Skeleton from "react-loading-skeleton";
57
import { Statuses } from "consts/statuses";
68
import { useIsList } from "context/IsListProvider";
9+
import { shortenAddress } from "utils/shortenAddress";
710
import CalendarIcon from "svgs/icons/calendar.svg";
811
import PileCoinsIcon from "svgs/icons/pile-coins.svg";
912
import UserIcon from "svgs/icons/user.svg";
1013
import Field from "./Field";
11-
import { shortenAddress } from "utils/shortenAddress";
1214

1315
const Container = styled.div<{ isList: boolean; isPreview?: boolean }>`
1416
display: flex;
@@ -92,6 +94,12 @@ const TransactionInfo: React.FC<ITransactionInfo> = ({
9294
const { isList } = useIsList();
9395
const displayAsList = isList && !overrideIsList;
9496

97+
const buyerEns = useEnsName({ address: buyerAddress, chainId: 1 });
98+
const sellerEns = useEnsName({ address: sellerAddress, chainId: 1 });
99+
100+
const displayBuyerAddress = buyerEns.data || shortenAddress(buyerAddress);
101+
const displaySellerAddress = sellerEns.data || shortenAddress(sellerAddress);
102+
95103
return (
96104
<Container isList={displayAsList} isPreview={isPreview}>
97105
<RestOfFieldsContainer isPreview={isPreview} isList={displayAsList}>
@@ -121,7 +129,15 @@ const TransactionInfo: React.FC<ITransactionInfo> = ({
121129
<Field
122130
icon={UserIcon}
123131
name="Buyer"
124-
value={shortenAddress(buyerAddress)}
132+
value={
133+
isPreview ? (
134+
<Copiable copiableContent={buyerAddress ?? ""} info="Copy Buyer Address">
135+
{displayBuyerAddress}
136+
</Copiable>
137+
) : (
138+
displayBuyerAddress
139+
)
140+
}
125141
displayAsList={displayAsList}
126142
isPreview={isPreview}
127143
/>
@@ -130,7 +146,15 @@ const TransactionInfo: React.FC<ITransactionInfo> = ({
130146
<Field
131147
icon={UserIcon}
132148
name="Seller"
133-
value={shortenAddress(sellerAddress)}
149+
value={
150+
isPreview ? (
151+
<Copiable copiableContent={sellerAddress ?? ""} info="Copy Seller Address">
152+
{displaySellerAddress}
153+
</Copiable>
154+
) : (
155+
displaySellerAddress
156+
)
157+
}
134158
displayAsList={displayAsList}
135159
isPreview={isPreview}
136160
/>

web/src/hooks/queries/useTransactionsQuery.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const transactionFragment = graphql(`
2020
templateData
2121
templateDataMappings
2222
status
23+
transactionHash
2324
payments {
2425
id
2526
amount

web/src/hooks/queries/useUserSettings.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useQuery } from "@tanstack/react-query";
2+
import { toast } from "react-toastify";
23

34
import { isUndefined } from "utils/index";
5+
import { OPTIONS } from "utils/wrapWithToast";
46

57
export const useUserSettings = () => {
68
const authToken = sessionStorage.getItem("auth-token")?.replace(/"/g, "");
@@ -22,6 +24,7 @@ export const useUserSettings = () => {
2224

2325
return (await res.json())?.data as IUserSettings;
2426
} catch {
27+
toast.error("Error fetching User Settings!", OPTIONS);
2528
return {} as IUserSettings;
2629
}
2730
},

web/src/pages/MyTransactions/TransactionDetails/PreviewCardButtons/AcceptSettlementButton.tsx

+1-5
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ import {
1010
} from "hooks/contracts/generated";
1111
import { useQueryRefetch } from "hooks/useQueryRefetch";
1212

13-
interface IAcceptButton {
14-
toggleModal?: () => void;
15-
}
16-
17-
const AcceptButton: React.FC<IAcceptButton> = ({ toggleModal }) => {
13+
const AcceptButton: React.FC = () => {
1814
const [isSending, setIsSending] = useState<boolean>(false);
1915
const publicClient = usePublicClient();
2016
const { id } = useTransactionDetailsContext();

0 commit comments

Comments
 (0)