@0xsquid/deposit-widget 0.1.0-beta.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.
Files changed (122) hide show
  1. package/dist/cjs/index.cjs +4062 -0
  2. package/dist/cjs/index.cjs.map +1 -0
  3. package/dist/index.d.ts +52 -0
  4. package/dist/index.esm.js +4042 -0
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/types/DepositWidget.d.ts +7 -0
  7. package/dist/types/components/ViewTransition.d.ts +7 -0
  8. package/dist/types/components/shared/buttons/button.d.ts +3 -0
  9. package/dist/types/components/shared/icons/types.d.ts +3 -0
  10. package/dist/types/components/shared/icons/user-round.d.ts +2 -0
  11. package/dist/types/components/shared/navigation/base-navbar.d.ts +3 -0
  12. package/dist/types/components/shared/navigation/sub-navbar.d.ts +6 -0
  13. package/dist/types/components/token-badge-icon.d.ts +6 -0
  14. package/dist/types/components/token-list-item.d.ts +13 -0
  15. package/dist/types/components/view-container.d.ts +1 -0
  16. package/dist/types/constants.d.ts +1 -0
  17. package/dist/types/hooks/ui/useMainCTAButtonState.d.ts +8 -0
  18. package/dist/types/hooks/use-auto-select-token.d.ts +1 -0
  19. package/dist/types/hooks/use-deposit-route.d.ts +7 -0
  20. package/dist/types/hooks/use-token-selection.d.ts +5 -0
  21. package/dist/types/hooks/use-transaction-history.d.ts +6 -0
  22. package/dist/types/index.d.ts +3 -0
  23. package/dist/types/services/assets-service.d.ts +6 -0
  24. package/dist/types/services/wallet-history/format.d.ts +10 -0
  25. package/dist/types/services/wallet-history/format.test.d.ts +1 -0
  26. package/dist/types/services/wallet-history/get-main-explorer-url.d.ts +23 -0
  27. package/dist/types/services/wallet-history/get-wallet-history.d.ts +2 -0
  28. package/dist/types/services/wallet-history/types.d.ts +66 -0
  29. package/dist/types/services/wallet-history/validation.d.ts +2 -0
  30. package/dist/types/store/use-deposit-store.d.ts +9 -0
  31. package/dist/types/store/use-input-mode.d.ts +5 -0
  32. package/dist/types/store/useRouter.d.ts +12 -0
  33. package/dist/types/types.d.ts +43 -0
  34. package/dist/types/utils/format-date.d.ts +2 -0
  35. package/dist/types/utils/format-date.test.d.ts +1 -0
  36. package/dist/types/utils/transaction.d.ts +10 -0
  37. package/dist/types/views/connect-wallet/connect-wallet-view.d.ts +6 -0
  38. package/dist/types/views/connect-wallet/wallet-list-item.d.ts +9 -0
  39. package/dist/types/views/main/amount-input.d.ts +10 -0
  40. package/dist/types/views/main/connect-prompt.d.ts +1 -0
  41. package/dist/types/views/main/deposit-amount-input.d.ts +1 -0
  42. package/dist/types/views/main/deposit-form.d.ts +1 -0
  43. package/dist/types/views/main/main-cta-button.d.ts +1 -0
  44. package/dist/types/views/main/main-view.d.ts +1 -0
  45. package/dist/types/views/main/navbar/actions.d.ts +1 -0
  46. package/dist/types/views/main/navbar/icon.d.ts +1 -0
  47. package/dist/types/views/main/navbar/navbar.d.ts +1 -0
  48. package/dist/types/views/main/navbar/title.d.ts +10 -0
  49. package/dist/types/views/main/recipient/account.d.ts +14 -0
  50. package/dist/types/views/main/recipient/recipient.d.ts +1 -0
  51. package/dist/types/views/main/token-selector.d.ts +1 -0
  52. package/dist/types/views/qr-code.d.ts +1 -0
  53. package/dist/types/views/render-view.d.ts +3 -0
  54. package/dist/types/views/select-chain/chain-type-meta.d.ts +7 -0
  55. package/dist/types/views/select-chain/select-chain-view.d.ts +6 -0
  56. package/dist/types/views/select-token.d.ts +1 -0
  57. package/dist/types/views/transaction-history/activity-list-item.d.ts +4 -0
  58. package/dist/types/views/transaction-history/transaction-history-view.d.ts +1 -0
  59. package/dist/types/views/transaction-progress/helpers.d.ts +14 -0
  60. package/dist/types/views/transaction-progress/transaction-progress-view.d.ts +1 -0
  61. package/dist/types/views/transaction-progress/use-transaction-progress.d.ts +17 -0
  62. package/package.json +99 -0
  63. package/src/DepositWidget.tsx +158 -0
  64. package/src/compiled-tailwind.css +6100 -0
  65. package/src/components/ViewTransition.tsx +81 -0
  66. package/src/components/shared/buttons/button.tsx +17 -0
  67. package/src/components/shared/icons/types.ts +3 -0
  68. package/src/components/shared/icons/user-round.tsx +21 -0
  69. package/src/components/shared/navigation/base-navbar.tsx +15 -0
  70. package/src/components/shared/navigation/sub-navbar.tsx +46 -0
  71. package/src/components/token-badge-icon.tsx +31 -0
  72. package/src/components/token-list-item.tsx +84 -0
  73. package/src/components/view-container.tsx +16 -0
  74. package/src/constants.ts +1 -0
  75. package/src/css.d.ts +4 -0
  76. package/src/fonts/DMSans-Variable.woff2 +0 -0
  77. package/src/hooks/ui/useMainCTAButtonState.ts +143 -0
  78. package/src/hooks/use-auto-select-token.ts +65 -0
  79. package/src/hooks/use-deposit-route.ts +58 -0
  80. package/src/hooks/use-token-selection.ts +17 -0
  81. package/src/hooks/use-transaction-history.ts +198 -0
  82. package/src/index.ts +3 -0
  83. package/src/services/assets-service.ts +21 -0
  84. package/src/services/wallet-history/format.test.ts +63 -0
  85. package/src/services/wallet-history/format.ts +128 -0
  86. package/src/services/wallet-history/get-main-explorer-url.ts +74 -0
  87. package/src/services/wallet-history/get-wallet-history.ts +24 -0
  88. package/src/services/wallet-history/types.ts +66 -0
  89. package/src/services/wallet-history/validation.ts +60 -0
  90. package/src/store/use-deposit-store.ts +20 -0
  91. package/src/store/use-input-mode.ts +10 -0
  92. package/src/store/useRouter.ts +49 -0
  93. package/src/tailwind.css +16 -0
  94. package/src/types.ts +39 -0
  95. package/src/utils/format-date.test.ts +32 -0
  96. package/src/utils/format-date.ts +25 -0
  97. package/src/utils/transaction.ts +39 -0
  98. package/src/views/connect-wallet/connect-wallet-view.tsx +147 -0
  99. package/src/views/connect-wallet/wallet-list-item.tsx +69 -0
  100. package/src/views/main/amount-input.tsx +272 -0
  101. package/src/views/main/connect-prompt.tsx +47 -0
  102. package/src/views/main/deposit-amount-input.tsx +42 -0
  103. package/src/views/main/deposit-form.tsx +13 -0
  104. package/src/views/main/main-cta-button.tsx +14 -0
  105. package/src/views/main/main-view.tsx +24 -0
  106. package/src/views/main/navbar/actions.tsx +25 -0
  107. package/src/views/main/navbar/icon.tsx +11 -0
  108. package/src/views/main/navbar/navbar.tsx +16 -0
  109. package/src/views/main/navbar/title.tsx +64 -0
  110. package/src/views/main/recipient/account.tsx +81 -0
  111. package/src/views/main/recipient/recipient.tsx +64 -0
  112. package/src/views/main/token-selector.tsx +77 -0
  113. package/src/views/qr-code.tsx +14 -0
  114. package/src/views/render-view.tsx +28 -0
  115. package/src/views/select-chain/chain-type-meta.ts +37 -0
  116. package/src/views/select-chain/select-chain-view.tsx +97 -0
  117. package/src/views/select-token.tsx +227 -0
  118. package/src/views/transaction-history/activity-list-item.tsx +87 -0
  119. package/src/views/transaction-history/transaction-history-view.tsx +58 -0
  120. package/src/views/transaction-progress/helpers.tsx +93 -0
  121. package/src/views/transaction-progress/transaction-progress-view.tsx +217 -0
  122. 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 &quot;
215
+ <span className="tw-text-grey-100">{search}</span>
216
+ &quot;
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
+ }