@0xsquid/deposit-widget 0.0.2-beta.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/DepositWidget.d.ts +0 -1
- package/dist/types/components/ViewTransition.d.ts +0 -1
- package/dist/types/components/shared/buttons/button.d.ts +0 -1
- package/dist/types/components/shared/icons/types.d.ts +0 -1
- package/dist/types/components/shared/icons/user-round.d.ts +0 -1
- package/dist/types/components/shared/navigation/base-navbar.d.ts +0 -1
- package/dist/types/components/shared/navigation/sub-navbar.d.ts +0 -1
- package/dist/types/components/token-badge-icon.d.ts +0 -1
- package/dist/types/components/token-list-item.d.ts +0 -1
- package/dist/types/components/view-container.d.ts +0 -1
- package/dist/types/constants.d.ts +0 -1
- package/dist/types/hooks/ui/useMainCTAButtonState.d.ts +0 -1
- package/dist/types/hooks/use-auto-select-token.d.ts +0 -1
- package/dist/types/hooks/use-deposit-route.d.ts +0 -1
- package/dist/types/hooks/use-token-selection.d.ts +0 -1
- package/dist/types/hooks/use-transaction-history.d.ts +0 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/services/assets-service.d.ts +0 -1
- package/dist/types/services/wallet-history/format.d.ts +0 -1
- package/dist/types/services/wallet-history/format.test.d.ts +0 -1
- package/dist/types/services/wallet-history/get-main-explorer-url.d.ts +0 -1
- package/dist/types/services/wallet-history/get-wallet-history.d.ts +0 -1
- package/dist/types/services/wallet-history/types.d.ts +0 -1
- package/dist/types/services/wallet-history/validation.d.ts +0 -1
- package/dist/types/store/use-deposit-store.d.ts +0 -1
- package/dist/types/store/use-input-mode.d.ts +0 -1
- package/dist/types/store/useRouter.d.ts +0 -1
- package/dist/types/types.d.ts +0 -1
- package/dist/types/utils/format-date.d.ts +0 -1
- package/dist/types/utils/format-date.test.d.ts +0 -1
- package/dist/types/utils/transaction.d.ts +0 -1
- package/dist/types/views/connect-wallet/connect-wallet-view.d.ts +0 -1
- package/dist/types/views/connect-wallet/wallet-list-item.d.ts +0 -1
- package/dist/types/views/main/amount-input.d.ts +0 -1
- package/dist/types/views/main/connect-prompt.d.ts +0 -1
- package/dist/types/views/main/deposit-amount-input.d.ts +0 -1
- package/dist/types/views/main/deposit-form.d.ts +0 -1
- package/dist/types/views/main/main-cta-button.d.ts +0 -1
- package/dist/types/views/main/main-view.d.ts +0 -1
- package/dist/types/views/main/navbar/actions.d.ts +0 -1
- package/dist/types/views/main/navbar/icon.d.ts +0 -1
- package/dist/types/views/main/navbar/navbar.d.ts +0 -1
- package/dist/types/views/main/navbar/title.d.ts +0 -1
- package/dist/types/views/main/recipient/account.d.ts +0 -1
- package/dist/types/views/main/recipient/recipient.d.ts +0 -1
- package/dist/types/views/main/token-selector.d.ts +0 -1
- package/dist/types/views/qr-code.d.ts +0 -1
- package/dist/types/views/render-view.d.ts +0 -1
- package/dist/types/views/select-chain/chain-type-meta.d.ts +0 -1
- package/dist/types/views/select-chain/select-chain-view.d.ts +0 -1
- package/dist/types/views/select-token.d.ts +0 -1
- package/dist/types/views/transaction-history/activity-list-item.d.ts +0 -1
- package/dist/types/views/transaction-history/transaction-history-view.d.ts +0 -1
- package/dist/types/views/transaction-progress/helpers.d.ts +0 -1
- package/dist/types/views/transaction-progress/transaction-progress-view.d.ts +0 -1
- package/dist/types/views/transaction-progress/use-transaction-progress.d.ts +0 -1
- package/package.json +7 -7
- package/src/DepositWidget.tsx +158 -0
- package/src/compiled-tailwind.css +6100 -0
- package/src/components/ViewTransition.tsx +81 -0
- package/src/components/shared/buttons/button.tsx +17 -0
- package/src/components/shared/icons/types.ts +3 -0
- package/src/components/shared/icons/user-round.tsx +21 -0
- package/src/components/shared/navigation/base-navbar.tsx +15 -0
- package/src/components/shared/navigation/sub-navbar.tsx +46 -0
- package/src/components/token-badge-icon.tsx +31 -0
- package/src/components/token-list-item.tsx +84 -0
- package/src/components/view-container.tsx +16 -0
- package/src/constants.ts +1 -0
- package/src/css.d.ts +4 -0
- package/src/fonts/DMSans-Variable.woff2 +0 -0
- package/src/hooks/ui/useMainCTAButtonState.ts +143 -0
- package/src/hooks/use-auto-select-token.ts +65 -0
- package/src/hooks/use-deposit-route.ts +58 -0
- package/src/hooks/use-token-selection.ts +17 -0
- package/src/hooks/use-transaction-history.ts +198 -0
- package/src/index.ts +3 -0
- package/src/services/assets-service.ts +21 -0
- package/src/services/wallet-history/format.test.ts +63 -0
- package/src/services/wallet-history/format.ts +128 -0
- package/src/services/wallet-history/get-main-explorer-url.ts +74 -0
- package/src/services/wallet-history/get-wallet-history.ts +24 -0
- package/src/services/wallet-history/types.ts +66 -0
- package/src/services/wallet-history/validation.ts +60 -0
- package/src/store/use-deposit-store.ts +20 -0
- package/src/store/use-input-mode.ts +10 -0
- package/src/store/useRouter.ts +49 -0
- package/src/tailwind.css +16 -0
- package/src/types.ts +39 -0
- package/src/utils/format-date.test.ts +32 -0
- package/src/utils/format-date.ts +25 -0
- package/src/utils/transaction.ts +39 -0
- package/src/views/connect-wallet/connect-wallet-view.tsx +147 -0
- package/src/views/connect-wallet/wallet-list-item.tsx +69 -0
- package/src/views/main/amount-input.tsx +272 -0
- package/src/views/main/connect-prompt.tsx +47 -0
- package/src/views/main/deposit-amount-input.tsx +42 -0
- package/src/views/main/deposit-form.tsx +13 -0
- package/src/views/main/main-cta-button.tsx +14 -0
- package/src/views/main/main-view.tsx +24 -0
- package/src/views/main/navbar/actions.tsx +25 -0
- package/src/views/main/navbar/icon.tsx +11 -0
- package/src/views/main/navbar/navbar.tsx +16 -0
- package/src/views/main/navbar/title.tsx +64 -0
- package/src/views/main/recipient/account.tsx +81 -0
- package/src/views/main/recipient/recipient.tsx +64 -0
- package/src/views/main/token-selector.tsx +77 -0
- package/src/views/qr-code.tsx +14 -0
- package/src/views/render-view.tsx +28 -0
- package/src/views/select-chain/chain-type-meta.ts +37 -0
- package/src/views/select-chain/select-chain-view.tsx +97 -0
- package/src/views/select-token.tsx +227 -0
- package/src/views/transaction-history/activity-list-item.tsx +87 -0
- package/src/views/transaction-history/transaction-history-view.tsx +58 -0
- package/src/views/transaction-progress/helpers.tsx +93 -0
- package/src/views/transaction-progress/transaction-progress-view.tsx +217 -0
- package/src/views/transaction-progress/use-transaction-progress.ts +112 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { useEffect, useRef, useMemo, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
filterViewableTokens,
|
|
4
|
+
formatTokenAmount,
|
|
5
|
+
getTokenImage,
|
|
6
|
+
searchTokens,
|
|
7
|
+
useAllConnectedWalletBalances,
|
|
8
|
+
useSquidChains,
|
|
9
|
+
} from "@0xsquid/react-hooks";
|
|
10
|
+
import { SubNavBar } from "../components/shared/navigation/sub-navbar";
|
|
11
|
+
import {
|
|
12
|
+
filterTokensByChain,
|
|
13
|
+
sortTokensByBalance,
|
|
14
|
+
type TokenWithBalance,
|
|
15
|
+
} from "../services/assets-service";
|
|
16
|
+
import { ViewContainer } from "../components/view-container";
|
|
17
|
+
import { Button, cn, Image } from "@0xsquid/ui";
|
|
18
|
+
import { TokenListItem } from "../components/token-list-item";
|
|
19
|
+
import { VIEW_TRANSITION_DURATION_MS } from "../constants";
|
|
20
|
+
import { useSwap } from "@0xsquid/react-hooks";
|
|
21
|
+
import { useRouter } from "../store/useRouter";
|
|
22
|
+
import { useDepositStore, usePaymentAmount } from "../store/use-deposit-store";
|
|
23
|
+
import { List, type RowComponentProps } from "react-window";
|
|
24
|
+
import { isSameAddress } from "../utils/transaction";
|
|
25
|
+
|
|
26
|
+
const selectedOutline =
|
|
27
|
+
"tw-outline tw-outline-2 tw-outline-material-light-thick tw-outline-offset-2";
|
|
28
|
+
|
|
29
|
+
type TokenRowProps = {
|
|
30
|
+
tokens: TokenWithBalance[];
|
|
31
|
+
chainIconMap: Record<string, string>;
|
|
32
|
+
onSelect: (token: TokenWithBalance) => void;
|
|
33
|
+
paymentAmountUsd: number | null;
|
|
34
|
+
isLoadingBalances: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function TokenRow({
|
|
38
|
+
index,
|
|
39
|
+
style,
|
|
40
|
+
tokens,
|
|
41
|
+
chainIconMap,
|
|
42
|
+
onSelect,
|
|
43
|
+
paymentAmountUsd,
|
|
44
|
+
isLoadingBalances,
|
|
45
|
+
}: RowComponentProps<TokenRowProps>) {
|
|
46
|
+
const token = tokens[index];
|
|
47
|
+
if (!token) return null;
|
|
48
|
+
|
|
49
|
+
const balanceNum = +token.balance;
|
|
50
|
+
const balanceUsd = balanceNum * (token.usdPrice ?? 0);
|
|
51
|
+
const insufficientBalance =
|
|
52
|
+
!isLoadingBalances &&
|
|
53
|
+
paymentAmountUsd !== null &&
|
|
54
|
+
balanceUsd < paymentAmountUsd;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div style={style}>
|
|
58
|
+
<TokenListItem
|
|
59
|
+
balance={
|
|
60
|
+
balanceNum > 0
|
|
61
|
+
? formatTokenAmount(token.balance, { exact: false })
|
|
62
|
+
: ""
|
|
63
|
+
}
|
|
64
|
+
icon={getTokenImage(token)}
|
|
65
|
+
chainIcon={chainIconMap[token.chainId]}
|
|
66
|
+
name={token.name}
|
|
67
|
+
price={token.usdPrice ?? 0}
|
|
68
|
+
usdValue={balanceUsd}
|
|
69
|
+
symbol={token.symbol}
|
|
70
|
+
disabled={insufficientBalance}
|
|
71
|
+
isLoadingBalance={isLoadingBalances}
|
|
72
|
+
onClick={() => !insufficientBalance && onSelect(token)}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function SelectTokenView() {
|
|
79
|
+
const { tokens, isInitialLoading: isLoadingBalances } =
|
|
80
|
+
useAllConnectedWalletBalances();
|
|
81
|
+
|
|
82
|
+
const { chains } = useSquidChains();
|
|
83
|
+
const { onSwapChange, toToken } = useSwap();
|
|
84
|
+
const pop = useRouter((s) => s.pop);
|
|
85
|
+
const destinationToken = useDepositStore((s) => s.config?.destinationToken);
|
|
86
|
+
const paymentAmount = usePaymentAmount();
|
|
87
|
+
const paymentAmountUsd = paymentAmount
|
|
88
|
+
? Number(paymentAmount) * (toToken?.usdPrice ?? 0)
|
|
89
|
+
: null;
|
|
90
|
+
|
|
91
|
+
const onSelect = (token: TokenWithBalance) => {
|
|
92
|
+
onSwapChange({
|
|
93
|
+
fromChainId: token.chainId,
|
|
94
|
+
fromTokenAddress: token.address,
|
|
95
|
+
});
|
|
96
|
+
pop();
|
|
97
|
+
};
|
|
98
|
+
const searchRef = useRef<HTMLInputElement>(null);
|
|
99
|
+
const [selectedChainId, setSelectedChainId] = useState<string | null>(null);
|
|
100
|
+
const [search, setSearch] = useState("");
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
const timer = setTimeout(
|
|
104
|
+
() => searchRef.current?.focus({ preventScroll: true }),
|
|
105
|
+
VIEW_TRANSITION_DURATION_MS,
|
|
106
|
+
);
|
|
107
|
+
return () => clearTimeout(timer);
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
const chainIconMap = useMemo(
|
|
111
|
+
() => Object.fromEntries(chains.map((c) => [c.chainId, c.chainIconURI])),
|
|
112
|
+
[chains],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const filteredTokens = useMemo(() => {
|
|
116
|
+
const viewableTokens = filterViewableTokens(tokens, {});
|
|
117
|
+
const chainFiltered = filterTokensByChain(viewableTokens, selectedChainId);
|
|
118
|
+
const withoutDestToken = destinationToken
|
|
119
|
+
? chainFiltered.filter(
|
|
120
|
+
(t) =>
|
|
121
|
+
!(
|
|
122
|
+
t.chainId === destinationToken.chainId &&
|
|
123
|
+
isSameAddress(t.address, destinationToken.address)
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
: chainFiltered;
|
|
127
|
+
|
|
128
|
+
const matched = search
|
|
129
|
+
? (searchTokens(search, withoutDestToken, chains) as TokenWithBalance[])
|
|
130
|
+
: withoutDestToken;
|
|
131
|
+
|
|
132
|
+
return sortTokensByBalance(matched);
|
|
133
|
+
}, [tokens, selectedChainId, search, chains, destinationToken]);
|
|
134
|
+
|
|
135
|
+
const selectChain = (chainId: string | null) => {
|
|
136
|
+
setSelectedChainId(chainId);
|
|
137
|
+
searchRef.current?.focus();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<>
|
|
142
|
+
<SubNavBar
|
|
143
|
+
input={{
|
|
144
|
+
ref: searchRef,
|
|
145
|
+
placeholder: "Select coin",
|
|
146
|
+
value: search,
|
|
147
|
+
onChange: (e) => setSearch(e.target.value),
|
|
148
|
+
}}
|
|
149
|
+
/>
|
|
150
|
+
|
|
151
|
+
<ViewContainer>
|
|
152
|
+
<div
|
|
153
|
+
role="radiogroup"
|
|
154
|
+
aria-label="Filter by chain"
|
|
155
|
+
className="tw-flex tw-h-14 tw-px-4 tw-items-center tw-justify-stretch tw-gap-3 tw-self-stretch tw-w-full tw-overflow-auto tw-min-h-14 tw-scrollbar-hidden"
|
|
156
|
+
>
|
|
157
|
+
<Button
|
|
158
|
+
role="radio"
|
|
159
|
+
aria-checked={selectedChainId === null}
|
|
160
|
+
size="sm"
|
|
161
|
+
variant="tertiary"
|
|
162
|
+
className={cn(
|
|
163
|
+
"!tw-p-3 !tw-h-8",
|
|
164
|
+
selectedChainId === null && selectedOutline,
|
|
165
|
+
)}
|
|
166
|
+
onClick={() => selectChain(null)}
|
|
167
|
+
>
|
|
168
|
+
<span className="tw-text-xs">ALL</span>
|
|
169
|
+
</Button>
|
|
170
|
+
|
|
171
|
+
{chains.map((chain) => (
|
|
172
|
+
<button
|
|
173
|
+
key={chain.chainId}
|
|
174
|
+
role="radio"
|
|
175
|
+
aria-checked={selectedChainId === chain.chainId}
|
|
176
|
+
className={cn(
|
|
177
|
+
"tw-shrink-0 tw-rounded-squid-xs",
|
|
178
|
+
selectedChainId === chain.chainId && selectedOutline,
|
|
179
|
+
)}
|
|
180
|
+
onClick={() => selectChain(chain.chainId)}
|
|
181
|
+
>
|
|
182
|
+
<Image
|
|
183
|
+
size="large"
|
|
184
|
+
rounded="xs"
|
|
185
|
+
shadow
|
|
186
|
+
style={{ width: "2rem", height: "2rem" }}
|
|
187
|
+
src={chain.chainIconURI}
|
|
188
|
+
/>
|
|
189
|
+
</button>
|
|
190
|
+
))}
|
|
191
|
+
</div>
|
|
192
|
+
{filteredTokens.length > 0 ? (
|
|
193
|
+
<List
|
|
194
|
+
tagName="ul"
|
|
195
|
+
className="tw-scrollbar-hidden tw-flex-1 tw-self-stretch"
|
|
196
|
+
style={{ flex: "1 1 0" }}
|
|
197
|
+
rowCount={filteredTokens.length}
|
|
198
|
+
rowHeight={56} // tw-h-14
|
|
199
|
+
rowComponent={TokenRow}
|
|
200
|
+
rowProps={{
|
|
201
|
+
tokens: filteredTokens,
|
|
202
|
+
chainIconMap,
|
|
203
|
+
onSelect,
|
|
204
|
+
paymentAmountUsd,
|
|
205
|
+
isLoadingBalances,
|
|
206
|
+
}}
|
|
207
|
+
overscanCount={5}
|
|
208
|
+
/>
|
|
209
|
+
) : (
|
|
210
|
+
<div className="tw-flex tw-flex-1 tw-items-center tw-justify-center tw-self-stretch tw-px-4">
|
|
211
|
+
<span className="tw-text-material-light-thick tw-text-lg tw-font-medium tw-text-center tw-truncate tw-max-w-full">
|
|
212
|
+
{search ? (
|
|
213
|
+
<>
|
|
214
|
+
No tokens matching "
|
|
215
|
+
<span className="tw-text-grey-100">{search}</span>
|
|
216
|
+
"
|
|
217
|
+
</>
|
|
218
|
+
) : (
|
|
219
|
+
"No tokens found"
|
|
220
|
+
)}
|
|
221
|
+
</span>
|
|
222
|
+
</div>
|
|
223
|
+
)}
|
|
224
|
+
</ViewContainer>
|
|
225
|
+
</>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BodyText,
|
|
3
|
+
CaptionText,
|
|
4
|
+
cn,
|
|
5
|
+
Loader,
|
|
6
|
+
SquareArrowTopRightIcon,
|
|
7
|
+
} from "@0xsquid/ui";
|
|
8
|
+
import { TokenBadgeIcon } from "../../components/token-badge-icon";
|
|
9
|
+
import type { FormattedTx } from "../../services/wallet-history/types";
|
|
10
|
+
import { useIsPaymentMode } from "../../store/use-deposit-store";
|
|
11
|
+
import { formatDateToHhMm } from "../../utils/format-date";
|
|
12
|
+
|
|
13
|
+
type Props = FormattedTx;
|
|
14
|
+
|
|
15
|
+
export function ActivityListItem({
|
|
16
|
+
fromAmount,
|
|
17
|
+
fromToken,
|
|
18
|
+
fromChain,
|
|
19
|
+
toAmount,
|
|
20
|
+
toToken,
|
|
21
|
+
timestamp,
|
|
22
|
+
status,
|
|
23
|
+
explorer,
|
|
24
|
+
}: Props) {
|
|
25
|
+
const isPaymentMode = useIsPaymentMode();
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<li>
|
|
29
|
+
<a
|
|
30
|
+
href={explorer?.url}
|
|
31
|
+
target={explorer ? "_blank" : undefined}
|
|
32
|
+
rel={explorer ? "noopener noreferrer" : undefined}
|
|
33
|
+
title={explorer ? `View on ${explorer.name}` : undefined}
|
|
34
|
+
className={cn(
|
|
35
|
+
"tw-flex tw-h-[4.5rem] tw-px-4 tw-items-center tw-gap-3 tw-shrink-0",
|
|
36
|
+
explorer && "hover:tw-bg-material-light-thin",
|
|
37
|
+
)}
|
|
38
|
+
>
|
|
39
|
+
<TokenBadgeIcon tokenSrc={fromToken.logo} chainSrc={fromChain.logo} />
|
|
40
|
+
|
|
41
|
+
<div className="tw-flex tw-flex-col tw-items-start tw-gap-0.5 tw-flex-1 tw-min-w-0">
|
|
42
|
+
<div className="tw-flex tw-items-center tw-gap-1 tw-self-stretch">
|
|
43
|
+
<BodyText size="small">
|
|
44
|
+
{isPaymentMode ? "Payment" : "Deposit"}
|
|
45
|
+
</BodyText>
|
|
46
|
+
{explorer && (
|
|
47
|
+
<SquareArrowTopRightIcon
|
|
48
|
+
size="0.875rem"
|
|
49
|
+
className="tw-text-grey-500"
|
|
50
|
+
/>
|
|
51
|
+
)}
|
|
52
|
+
<BodyText size="small" className="tw-ml-auto">
|
|
53
|
+
{toAmount}
|
|
54
|
+
</BodyText>
|
|
55
|
+
<BodyText size="small" className="tw-text-grey-400">
|
|
56
|
+
{toToken.symbol}
|
|
57
|
+
</BodyText>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div className="tw-flex tw-justify-between tw-items-center tw-self-stretch">
|
|
61
|
+
<div className="tw-flex tw-items-center tw-gap-1">
|
|
62
|
+
<CaptionText className="tw-text-grey-500">
|
|
63
|
+
Paid {fromAmount} {fromToken.symbol}
|
|
64
|
+
</CaptionText>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{status === "ongoing" ? (
|
|
68
|
+
<Loader
|
|
69
|
+
size="1rem"
|
|
70
|
+
strokeWidth="4"
|
|
71
|
+
className="tw-text-royal-500"
|
|
72
|
+
/>
|
|
73
|
+
) : status === "refunded" ? (
|
|
74
|
+
<CaptionText className="tw-text-status-negative">
|
|
75
|
+
Refunded
|
|
76
|
+
</CaptionText>
|
|
77
|
+
) : (
|
|
78
|
+
<CaptionText className="tw-text-grey-500">
|
|
79
|
+
{formatDateToHhMm(new Date(timestamp))}
|
|
80
|
+
</CaptionText>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</a>
|
|
85
|
+
</li>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BodyText, Loader, SectionTitle } from "@0xsquid/ui";
|
|
2
|
+
import { SubNavBar } from "../../components/shared/navigation/sub-navbar";
|
|
3
|
+
import { ViewContainer } from "../../components/view-container";
|
|
4
|
+
import { useTransactionHistory } from "../../hooks/use-transaction-history";
|
|
5
|
+
import { useIsPaymentMode } from "../../store/use-deposit-store";
|
|
6
|
+
import { formatDayHeader } from "../../utils/format-date";
|
|
7
|
+
import { ActivityListItem } from "./activity-list-item";
|
|
8
|
+
|
|
9
|
+
export function TransactionHistoryView() {
|
|
10
|
+
const { isLoading, isError, groups } = useTransactionHistory();
|
|
11
|
+
const isPaymentMode = useIsPaymentMode();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<SubNavBar title="Activity" />
|
|
16
|
+
|
|
17
|
+
<ViewContainer>
|
|
18
|
+
{isLoading ? (
|
|
19
|
+
<div className="tw-flex tw-flex-1 tw-items-center tw-justify-center">
|
|
20
|
+
<Loader size="2rem" className="tw-text-royal-500" />
|
|
21
|
+
</div>
|
|
22
|
+
) : isError ? (
|
|
23
|
+
<Message>Error loading transactions</Message>
|
|
24
|
+
) : groups.length === 0 ? (
|
|
25
|
+
<Message>
|
|
26
|
+
Your {isPaymentMode ? "payments" : "deposits"} will appear here
|
|
27
|
+
</Message>
|
|
28
|
+
) : (
|
|
29
|
+
<div className="tw-flex tw-flex-col tw-w-full">
|
|
30
|
+
{groups.map((group) => (
|
|
31
|
+
<section key={group.day} className="tw-flex tw-flex-col">
|
|
32
|
+
<SectionTitle
|
|
33
|
+
title={formatDayHeader(group.day)}
|
|
34
|
+
className="tw-bg-transparent"
|
|
35
|
+
/>
|
|
36
|
+
<ul className="tw-flex tw-flex-col">
|
|
37
|
+
{group.transactions.map((tx) => (
|
|
38
|
+
<ActivityListItem key={tx.id} {...tx} />
|
|
39
|
+
))}
|
|
40
|
+
</ul>
|
|
41
|
+
</section>
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
</ViewContainer>
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function Message({ children }: { children: React.ReactNode }) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="tw-flex tw-flex-1 tw-items-center tw-justify-center tw-px-4 tw-text-center">
|
|
53
|
+
<BodyText size="small" className="tw-text-material-light-thick">
|
|
54
|
+
{children}
|
|
55
|
+
</BodyText>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { CaptionText, Transfer } from "@0xsquid/ui";
|
|
2
|
+
import { DepositStatus } from "./use-transaction-progress";
|
|
3
|
+
|
|
4
|
+
export function getStatusTitle(
|
|
5
|
+
status: DepositStatus,
|
|
6
|
+
isPaymentMode: boolean,
|
|
7
|
+
): string {
|
|
8
|
+
const action = isPaymentMode ? "Payment" : "Deposit";
|
|
9
|
+
switch (status) {
|
|
10
|
+
case DepositStatus.CONFIRMING:
|
|
11
|
+
return "Confirm transaction";
|
|
12
|
+
case DepositStatus.PROCESSING:
|
|
13
|
+
return `${action} in progress`;
|
|
14
|
+
case DepositStatus.COMPLETED:
|
|
15
|
+
return `${action} complete`;
|
|
16
|
+
case DepositStatus.PARTIAL_SUCCESS:
|
|
17
|
+
return `${action} failed to complete`;
|
|
18
|
+
case DepositStatus.REJECTED:
|
|
19
|
+
return `${action} rejected`;
|
|
20
|
+
case DepositStatus.FAILED:
|
|
21
|
+
return `${action} failed`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getStatusDescription(
|
|
26
|
+
status: DepositStatus,
|
|
27
|
+
{
|
|
28
|
+
amount,
|
|
29
|
+
symbol,
|
|
30
|
+
recipientName,
|
|
31
|
+
isPaymentMode,
|
|
32
|
+
}: {
|
|
33
|
+
amount: string;
|
|
34
|
+
symbol: string;
|
|
35
|
+
recipientName: string;
|
|
36
|
+
isPaymentMode: boolean;
|
|
37
|
+
},
|
|
38
|
+
): string {
|
|
39
|
+
const verb = isPaymentMode ? "paid" : "deposited";
|
|
40
|
+
switch (status) {
|
|
41
|
+
case DepositStatus.CONFIRMING:
|
|
42
|
+
return "Authenticate the transaction in your wallet.";
|
|
43
|
+
case DepositStatus.PROCESSING:
|
|
44
|
+
return "Feel free to dismiss this dialog.";
|
|
45
|
+
case DepositStatus.COMPLETED:
|
|
46
|
+
return `You ${verb} ${amount} ${symbol} to ${recipientName} successfully.`;
|
|
47
|
+
case DepositStatus.PARTIAL_SUCCESS:
|
|
48
|
+
return "You received a refund to your wallet.";
|
|
49
|
+
case DepositStatus.REJECTED:
|
|
50
|
+
return "You declined the transaction in your wallet.";
|
|
51
|
+
case DepositStatus.FAILED:
|
|
52
|
+
return "Your tokens were safely returned to your wallet.";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getButtonLabel(status: DepositStatus): string {
|
|
57
|
+
switch (status) {
|
|
58
|
+
case DepositStatus.CONFIRMING:
|
|
59
|
+
return "Cancel";
|
|
60
|
+
case DepositStatus.PROCESSING:
|
|
61
|
+
case DepositStatus.COMPLETED:
|
|
62
|
+
return "Ok, done";
|
|
63
|
+
case DepositStatus.PARTIAL_SUCCESS:
|
|
64
|
+
return "Ok, go back";
|
|
65
|
+
case DepositStatus.REJECTED:
|
|
66
|
+
case DepositStatus.FAILED:
|
|
67
|
+
return "Ok, go back";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function TimeDetail({
|
|
72
|
+
status,
|
|
73
|
+
timer,
|
|
74
|
+
estimatedTimeDisplay,
|
|
75
|
+
}: {
|
|
76
|
+
status: DepositStatus;
|
|
77
|
+
timer: string;
|
|
78
|
+
estimatedTimeDisplay: string | undefined;
|
|
79
|
+
}) {
|
|
80
|
+
switch (status) {
|
|
81
|
+
case DepositStatus.PROCESSING:
|
|
82
|
+
return estimatedTimeDisplay ? (
|
|
83
|
+
<Transfer from={timer} to={estimatedTimeDisplay} />
|
|
84
|
+
) : (
|
|
85
|
+
<CaptionText>{timer}</CaptionText>
|
|
86
|
+
);
|
|
87
|
+
case DepositStatus.COMPLETED:
|
|
88
|
+
case DepositStatus.PARTIAL_SUCCESS:
|
|
89
|
+
return <CaptionText>{timer}</CaptionText>;
|
|
90
|
+
default:
|
|
91
|
+
return <CaptionText>{estimatedTimeDisplay ?? "—"}</CaptionText>;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatHash,
|
|
3
|
+
formatTokenAmount,
|
|
4
|
+
getTokenImage,
|
|
5
|
+
useEstimate,
|
|
6
|
+
useExecuteTransaction,
|
|
7
|
+
useSwap,
|
|
8
|
+
} from "@0xsquid/react-hooks";
|
|
9
|
+
import {
|
|
10
|
+
AnimationWrapper,
|
|
11
|
+
ArrowRightDownCircleIcon,
|
|
12
|
+
ArrowRightUpCircleIcon,
|
|
13
|
+
BodyText,
|
|
14
|
+
CaptionText,
|
|
15
|
+
IconLabel,
|
|
16
|
+
Image,
|
|
17
|
+
PropertyListItem,
|
|
18
|
+
SquareArrowTopRightIcon,
|
|
19
|
+
TimeFliesIcon,
|
|
20
|
+
transactionFailureAnimation,
|
|
21
|
+
transactionHalfSuccessAnimation,
|
|
22
|
+
transactionPendingAnimation,
|
|
23
|
+
transactionProcessingAnimation,
|
|
24
|
+
transactionRejectedAnimation,
|
|
25
|
+
transactionSuccessAnimation,
|
|
26
|
+
useTimer,
|
|
27
|
+
WalletFilledIcon,
|
|
28
|
+
} from "@0xsquid/ui";
|
|
29
|
+
import { useEffect } from "react";
|
|
30
|
+
import { Button } from "../../components/shared/buttons/button";
|
|
31
|
+
import { SubNavBar } from "../../components/shared/navigation/sub-navbar";
|
|
32
|
+
import { ViewContainer } from "../../components/view-container";
|
|
33
|
+
import { useDepositRoute } from "../../hooks/use-deposit-route";
|
|
34
|
+
import {
|
|
35
|
+
useDepositStore,
|
|
36
|
+
useIsPaymentMode,
|
|
37
|
+
} from "../../store/use-deposit-store";
|
|
38
|
+
import { useRouter } from "../../store/useRouter";
|
|
39
|
+
import {
|
|
40
|
+
getButtonLabel,
|
|
41
|
+
getStatusDescription,
|
|
42
|
+
getStatusTitle,
|
|
43
|
+
TimeDetail,
|
|
44
|
+
} from "./helpers";
|
|
45
|
+
import {
|
|
46
|
+
DepositStatus,
|
|
47
|
+
useTransactionProgress,
|
|
48
|
+
} from "./use-transaction-progress";
|
|
49
|
+
|
|
50
|
+
const progressAnimations: Record<DepositStatus, object> = {
|
|
51
|
+
[DepositStatus.CONFIRMING]: transactionPendingAnimation,
|
|
52
|
+
[DepositStatus.PROCESSING]: transactionProcessingAnimation,
|
|
53
|
+
[DepositStatus.COMPLETED]: transactionSuccessAnimation,
|
|
54
|
+
[DepositStatus.PARTIAL_SUCCESS]: transactionHalfSuccessAnimation,
|
|
55
|
+
[DepositStatus.REJECTED]: transactionRejectedAnimation,
|
|
56
|
+
[DepositStatus.FAILED]: transactionFailureAnimation,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function TransactionProgressView() {
|
|
60
|
+
const popToRoot = useRouter((s) => s.popToRoot);
|
|
61
|
+
const isPaymentMode = useIsPaymentMode();
|
|
62
|
+
const config = useDepositStore((s) => s.config);
|
|
63
|
+
const { fromToken, toToken } = useSwap();
|
|
64
|
+
|
|
65
|
+
const { routeData } = useDepositRoute();
|
|
66
|
+
const { fromAmountFormatted: rawFromAmount, toAmount } =
|
|
67
|
+
useEstimate(routeData);
|
|
68
|
+
const { cancelSwap } = useExecuteTransaction(routeData);
|
|
69
|
+
|
|
70
|
+
const { status, estimatedTime, recipient, explorerLink } =
|
|
71
|
+
useTransactionProgress();
|
|
72
|
+
|
|
73
|
+
const { timer, startTimer, stopTimer } = useTimer({ immediateStart: false });
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (status === DepositStatus.PROCESSING) {
|
|
77
|
+
startTimer();
|
|
78
|
+
} else if (
|
|
79
|
+
status === DepositStatus.COMPLETED ||
|
|
80
|
+
status === DepositStatus.PARTIAL_SUCCESS ||
|
|
81
|
+
status === DepositStatus.REJECTED ||
|
|
82
|
+
status === DepositStatus.FAILED
|
|
83
|
+
) {
|
|
84
|
+
stopTimer();
|
|
85
|
+
}
|
|
86
|
+
}, [status, startTimer, stopTimer]);
|
|
87
|
+
|
|
88
|
+
const actionLabel = isPaymentMode ? "Payment" : "Deposit";
|
|
89
|
+
const fromAmountFormatted = formatTokenAmount(rawFromAmount ?? "0", {
|
|
90
|
+
exact: false,
|
|
91
|
+
});
|
|
92
|
+
const toAmountFormatted = formatTokenAmount(toAmount ?? "0", {
|
|
93
|
+
exact: false,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const handleClose = () => {
|
|
97
|
+
if (status === DepositStatus.CONFIRMING) {
|
|
98
|
+
cancelSwap();
|
|
99
|
+
}
|
|
100
|
+
popToRoot();
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const title = getStatusTitle(status, isPaymentMode);
|
|
104
|
+
const description = getStatusDescription(status, {
|
|
105
|
+
amount: toAmountFormatted,
|
|
106
|
+
symbol: toToken?.symbol ?? "",
|
|
107
|
+
recipientName: recipient.name,
|
|
108
|
+
isPaymentMode,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<>
|
|
113
|
+
<SubNavBar title={actionLabel} />
|
|
114
|
+
|
|
115
|
+
<ViewContainer className="!tw-py-0">
|
|
116
|
+
<div className="tw-flex tw-h-20 tw-w-full tw-items-center tw-justify-center tw-bg-animation-bg [&_svg_path]:tw-stroke-[6]">
|
|
117
|
+
<AnimationWrapper
|
|
118
|
+
style={{ height: "100%", maxHeight: "100%", width: "auto" }}
|
|
119
|
+
autoplay
|
|
120
|
+
loop
|
|
121
|
+
src={progressAnimations[status]}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<header className="tw-flex tw-w-full tw-flex-col tw-gap-squid-xs tw-px-squid-m tw-py-squid-s">
|
|
126
|
+
<div className="tw-flex tw-items-center tw-gap-2">
|
|
127
|
+
<BodyText size="large" className="tw-text-grey-300">
|
|
128
|
+
{title}
|
|
129
|
+
</BodyText>
|
|
130
|
+
</div>
|
|
131
|
+
<CaptionText className="tw-text-grey-500">{description}</CaptionText>
|
|
132
|
+
</header>
|
|
133
|
+
|
|
134
|
+
<ul className="tw-flex tw-w-full tw-flex-col tw-items-center tw-justify-center tw-gap-squid-xxs tw-rounded-squid-l tw-pb-squid-s tw-pt-squid-xs tw-squid-property-gradient-bg-odd-container">
|
|
135
|
+
<PropertyListItem
|
|
136
|
+
icon={<ArrowRightUpCircleIcon />}
|
|
137
|
+
label="You send"
|
|
138
|
+
detail={
|
|
139
|
+
<IconLabel src={getTokenImage(fromToken) ?? ""} rounded="full">
|
|
140
|
+
{fromAmountFormatted} {fromToken?.symbol}
|
|
141
|
+
</IconLabel>
|
|
142
|
+
}
|
|
143
|
+
/>
|
|
144
|
+
|
|
145
|
+
<PropertyListItem
|
|
146
|
+
icon={<ArrowRightDownCircleIcon />}
|
|
147
|
+
label={`${actionLabel} amount`}
|
|
148
|
+
detail={
|
|
149
|
+
<IconLabel src={getTokenImage(toToken) ?? ""} rounded="full">
|
|
150
|
+
{toAmountFormatted} {toToken?.symbol}
|
|
151
|
+
</IconLabel>
|
|
152
|
+
}
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
<PropertyListItem
|
|
156
|
+
icon={<WalletFilledIcon size="24" />}
|
|
157
|
+
label="Recipient"
|
|
158
|
+
detail={
|
|
159
|
+
<span className="tw-flex tw-items-center tw-gap-squid-xxs">
|
|
160
|
+
{config?.integrator.logoUrl && (
|
|
161
|
+
<Image
|
|
162
|
+
src={config.integrator.logoUrl}
|
|
163
|
+
size="medium"
|
|
164
|
+
rounded="full"
|
|
165
|
+
/>
|
|
166
|
+
)}
|
|
167
|
+
{recipient.name}
|
|
168
|
+
|
|
169
|
+
<span className="tw-text-material-light-thick">
|
|
170
|
+
(
|
|
171
|
+
{formatHash({
|
|
172
|
+
hash: recipient.address,
|
|
173
|
+
chainType: toToken?.type,
|
|
174
|
+
})}
|
|
175
|
+
)
|
|
176
|
+
</span>
|
|
177
|
+
</span>
|
|
178
|
+
}
|
|
179
|
+
/>
|
|
180
|
+
|
|
181
|
+
<PropertyListItem
|
|
182
|
+
icon={<TimeFliesIcon />}
|
|
183
|
+
label="Time"
|
|
184
|
+
detail={
|
|
185
|
+
<TimeDetail
|
|
186
|
+
status={status}
|
|
187
|
+
timer={timer}
|
|
188
|
+
estimatedTimeDisplay={estimatedTime}
|
|
189
|
+
/>
|
|
190
|
+
}
|
|
191
|
+
/>
|
|
192
|
+
</ul>
|
|
193
|
+
|
|
194
|
+
<footer className="tw-relative tw-flex tw-flex-col tw-p-2 tw-shrink-0 tw-self-stretch tw-mt-auto">
|
|
195
|
+
{explorerLink && (
|
|
196
|
+
<a
|
|
197
|
+
href={explorerLink.url}
|
|
198
|
+
target="_blank"
|
|
199
|
+
rel="noopener noreferrer"
|
|
200
|
+
className="tw-group/explorer tw-w-fit tw-mx-auto tw-absolute tw-inset-x-0 tw-bottom-full tw-flex tw-justify-center tw-items-center tw-gap-1 tw-py-2 tw-text-sm tw-text-grey-300 hover:tw-text-grey-100 tw-transition-colors"
|
|
201
|
+
>
|
|
202
|
+
View on {explorerLink.name}
|
|
203
|
+
<SquareArrowTopRightIcon
|
|
204
|
+
size="0.75rem"
|
|
205
|
+
className="tw-transition-all tw-duration-200 tw-w-0 tw-opacity-0 group-hover/explorer:tw-w-4 group-hover/explorer:tw-opacity-100"
|
|
206
|
+
/>
|
|
207
|
+
</a>
|
|
208
|
+
)}
|
|
209
|
+
|
|
210
|
+
<Button className="tw-min-h-12" onClick={handleClose}>
|
|
211
|
+
{getButtonLabel(status)}
|
|
212
|
+
</Button>
|
|
213
|
+
</footer>
|
|
214
|
+
</ViewContainer>
|
|
215
|
+
</>
|
|
216
|
+
);
|
|
217
|
+
}
|