@coin-voyage/paykit 2.4.3-beta.0 → 2.4.4-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.
- package/README.md +11 -17
- package/dist/components/Pages/CardPayment/index.js +5 -3
- package/dist/components/Pages/Confirmation/index.js +16 -6
- package/dist/components/Pages/PayToAddress/index.js +3 -3
- package/dist/components/Pages/PreparingPayment/index.d.ts +1 -0
- package/dist/components/Pages/PreparingPayment/index.js +50 -0
- package/dist/components/pay-button/index.js +173 -93
- package/dist/components/pay-modal/index.d.ts +3 -1
- package/dist/components/pay-modal/index.js +2 -2
- package/dist/components/ui/Modal/index.d.ts +2 -1
- package/dist/components/ui/Modal/index.js +8 -1
- package/dist/config/route-config.js +7 -0
- package/dist/hooks/usePayToAddressChainOptions.js +6 -13
- package/dist/hooks/usePayToAddressTokens.js +4 -15
- package/dist/hooks/usePayWithToken.js +7 -6
- package/dist/hooks/usePaymentLifecycle.d.ts +5 -1
- package/dist/hooks/usePaymentLifecycle.js +52 -46
- package/dist/hooks/usePaymentState.d.ts +4 -3
- package/dist/hooks/usePaymentState.js +34 -7
- package/dist/hooks/useTokenList.d.ts +182 -0
- package/dist/hooks/useTokenList.js +24 -0
- package/dist/hooks/useTokenOptions.js +1 -1
- package/dist/providers/paykit-provider.js +14 -7
- package/dist/types/routes.d.ts +15 -14
- package/dist/types/routes.js +15 -14
- package/package.json +3 -6
- package/dist/utils/item.d.ts +0 -5
- package/dist/utils/item.js +0 -6
package/README.md
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
# CoinVoyage Payments
|
|
2
2
|
|
|
3
|
-
CoinVoyage enables seamless crypto payments
|
|
3
|
+
CoinVoyage enables seamless crypto payments in your app.
|
|
4
4
|
|
|
5
|
-
Onboard users from any chain
|
|
5
|
+
Onboard users from any chain and any coin with one click.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
and much more...
|
|
9
|
+
- Cross-chain payments across many tokens and chains.
|
|
10
|
+
- Single-transaction payment flows for end users.
|
|
11
|
+
- Permissionless payment execution without custodial fund handling.
|
|
12
|
+
- Support for major browser and mobile wallets.
|
|
13
|
+
- Fast integration for embedded crypto payment UX.
|
|
16
14
|
|
|
17
15
|
## Documentation
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
Read the docs at [docs.coinvoyage.io](https://docs.coinvoyage.io).
|
|
20
18
|
|
|
21
19
|
## Examples
|
|
22
20
|
|
|
23
|
-
[
|
|
24
|
-
|
|
25
|
-
### Try in Playground
|
|
26
|
-
|
|
27
|
-
Coming soon.
|
|
21
|
+
See [coin-voyage/examples](https://github.com/coin-voyage/examples).
|
|
28
22
|
|
|
29
23
|
## Support
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
Contact [help@coinvoyage.io](mailto:help@coinvoyage.io) for integration support.
|
|
32
26
|
|
|
33
27
|
## Credits
|
|
34
28
|
|
|
35
|
-
CoinVoyage PayKit is a fork of [
|
|
29
|
+
CoinVoyage PayKit is a fork of [ConnectKit](https://github.com/family/connectkit) by [Family](https://family.co).
|
|
@@ -102,6 +102,8 @@ function StripeOnrampCheckout({ paymentData }) {
|
|
|
102
102
|
}
|
|
103
103
|
function OnrampSession({ stripeOnramp, clientSecret, theme, onUiLoaded, onSessionUpdate, }) {
|
|
104
104
|
const mountNodeRef = useRef(null);
|
|
105
|
+
const onUiLoadedRef = useLatestRef(onUiLoaded);
|
|
106
|
+
const onSessionUpdateRef = useLatestRef(onSessionUpdate);
|
|
105
107
|
useEffect(() => {
|
|
106
108
|
const mountNode = mountNodeRef.current;
|
|
107
109
|
if (!mountNode) {
|
|
@@ -113,10 +115,10 @@ function OnrampSession({ stripeOnramp, clientSecret, theme, onUiLoaded, onSessio
|
|
|
113
115
|
appearance: theme ? { theme } : undefined,
|
|
114
116
|
});
|
|
115
117
|
const handleUiLoaded = () => {
|
|
116
|
-
|
|
118
|
+
onUiLoadedRef.current?.();
|
|
117
119
|
};
|
|
118
120
|
const handleSessionUpdated = (event) => {
|
|
119
|
-
|
|
121
|
+
onSessionUpdateRef.current?.(event.payload.session.status);
|
|
120
122
|
};
|
|
121
123
|
session.addEventListener("onramp_ui_loaded", handleUiLoaded);
|
|
122
124
|
session.addEventListener("onramp_session_updated", handleSessionUpdated);
|
|
@@ -126,7 +128,7 @@ function OnrampSession({ stripeOnramp, clientSecret, theme, onUiLoaded, onSessio
|
|
|
126
128
|
session.removeEventListener("onramp_session_updated", handleSessionUpdated);
|
|
127
129
|
mountNode.replaceChildren();
|
|
128
130
|
};
|
|
129
|
-
}, [clientSecret,
|
|
131
|
+
}, [clientSecret, onSessionUpdateRef, onUiLoadedRef, stripeOnramp, theme]);
|
|
130
132
|
return _jsx(OnrampMount, { ref: mountNodeRef });
|
|
131
133
|
}
|
|
132
134
|
function useCardPaymentData() {
|
|
@@ -29,39 +29,49 @@ function getConfirmationState(payOrder, isDeposit, locales, optimisticConfirmati
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
const payment = payOrder.payment;
|
|
32
|
+
const status = payOrder.status;
|
|
32
33
|
const confirmOptimistically = optimisticConfirmation && !isDeposit;
|
|
33
34
|
const sourceTxURL = payment?.src.chain_id && payment?.source_tx_hash
|
|
34
35
|
? getChainExplorerTxUrl(payment.src.chain_id, payment.source_tx_hash)
|
|
35
36
|
: undefined;
|
|
36
|
-
if (
|
|
37
|
+
if (status === PayOrderStatus.FAILED) {
|
|
37
38
|
return {
|
|
38
39
|
title: locales.confirmationScreen_failed ?? "Transaction failed",
|
|
39
40
|
uiState: "error",
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
|
-
if (
|
|
43
|
+
if (status === PayOrderStatus.PARTIAL_PAYMENT) {
|
|
43
44
|
return {
|
|
44
45
|
title: "Partial payment received",
|
|
45
46
|
uiState: "warning",
|
|
46
47
|
txURL: sourceTxURL,
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
|
-
if (
|
|
50
|
-
|
|
50
|
+
if (status === PayOrderStatus.REFUNDED) {
|
|
51
|
+
const refundTxURL = payment?.dst.chain_id && payment?.refund_tx_hash
|
|
52
|
+
? getChainExplorerTxUrl(payment.dst.chain_id, payment.refund_tx_hash)
|
|
53
|
+
: undefined;
|
|
54
|
+
return {
|
|
55
|
+
title: "Payment Refunded",
|
|
56
|
+
uiState: "warning",
|
|
57
|
+
txURL: refundTxURL ?? sourceTxURL,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (status === PayOrderStatus.AWAITING_CONFIRMATION || status === PayOrderStatus.OPTIMISTIC_CONFIRMED) {
|
|
51
61
|
return {
|
|
52
62
|
title: confirmOptimistically ? locales.confirmationScreen_completed : _jsxs(_Fragment, { children: [locales.awaitingConfirmation, "..."] }),
|
|
53
63
|
uiState: confirmOptimistically ? "success" : "loading",
|
|
54
64
|
txURL: sourceTxURL,
|
|
55
65
|
};
|
|
56
66
|
}
|
|
57
|
-
if (
|
|
67
|
+
if (status === PayOrderStatus.EXECUTING_ORDER) {
|
|
58
68
|
return {
|
|
59
69
|
title: confirmOptimistically ? (locales.confirmationScreen_completed) : (_jsxs(_Fragment, { children: [locales.confirmationScreen_executing, "..."] })),
|
|
60
70
|
uiState: confirmOptimistically ? "success" : "loading",
|
|
61
71
|
txURL: sourceTxURL,
|
|
62
72
|
};
|
|
63
73
|
}
|
|
64
|
-
if (
|
|
74
|
+
if (status === PayOrderStatus.COMPLETED) {
|
|
65
75
|
const receivingTxHash = payment?.destination_tx_hash;
|
|
66
76
|
const destChainId = payment?.dst.chain_id;
|
|
67
77
|
const txURL = isDeposit && destChainId && receivingTxHash ? getChainExplorerTxUrl(destChainId, receivingTxHash) : sourceTxURL;
|
|
@@ -22,16 +22,16 @@ export default function PayToAddress() {
|
|
|
22
22
|
function PayToAddressView() {
|
|
23
23
|
const { paymentState, triggerResize } = usePayContext();
|
|
24
24
|
const { payToAddressChainId: payToAddressChain, payToAddressCurrency } = paymentState;
|
|
25
|
-
const { data
|
|
25
|
+
const { data, isLoading, isError } = useDepositAddressQuery({
|
|
26
26
|
enabled: payToAddressCurrency != undefined,
|
|
27
27
|
});
|
|
28
28
|
if (isError) {
|
|
29
29
|
return payToAddressChain ? _jsx(DepositFailed, {}) : null;
|
|
30
30
|
}
|
|
31
|
-
if (isLoading || !
|
|
31
|
+
if (isLoading || !data) {
|
|
32
32
|
return _jsx(DepositAddressLoading, {});
|
|
33
33
|
}
|
|
34
|
-
return (_jsx(DepositAddressInfo, { details:
|
|
34
|
+
return (_jsx(DepositAddressInfo, { details: data, triggerResize: triggerResize, isDeposit: paymentState.payOrder?.mode === PayOrderMode.DEPOSIT }));
|
|
35
35
|
}
|
|
36
36
|
function DepositAddressLoading() {
|
|
37
37
|
const locales = useLocales();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function PreparingPayment(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import styled from "../../../styles/styled";
|
|
4
|
+
import usePayContext from "../../contexts/pay";
|
|
5
|
+
import { PageContent } from "../../ui/Modal/styles";
|
|
6
|
+
import OptionsList from "../../ui/OptionsList";
|
|
7
|
+
import PoweredByFooter from "../../ui/PoweredByFooter";
|
|
8
|
+
import { Skeleton } from "../../ui/Skeleton";
|
|
9
|
+
export default function PreparingPayment() {
|
|
10
|
+
const { triggerResize } = usePayContext();
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
triggerResize();
|
|
13
|
+
}, [triggerResize]);
|
|
14
|
+
return (_jsxs(PageContent, { children: [_jsxs(HeaderSkeleton, { children: [_jsx(AmountSkeleton, {}), _jsxs(SubtitleSkeletonRow, { children: [_jsxs(LogoSkeletonStack, { children: [_jsx(LogoSkeleton, {}), _jsx(LogoSkeleton, { "$overlap": true }), _jsx(LogoSkeleton, { "$overlap": true }), _jsx(LogoSkeleton, { "$overlap": true })] }), _jsx(SubtitleSkeleton, {})] })] }), _jsx(OptionsList, { options: [], isLoading: true, requiredSkeletons: 3 }), _jsx(PoweredByFooter, {})] }));
|
|
15
|
+
}
|
|
16
|
+
const HeaderSkeleton = styled.div `
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
align-items: center;
|
|
20
|
+
`;
|
|
21
|
+
const BaseSkeleton = styled(Skeleton) `
|
|
22
|
+
background-color: rgba(0, 0, 0, 0.1);
|
|
23
|
+
`;
|
|
24
|
+
const AmountSkeleton = styled(BaseSkeleton) `
|
|
25
|
+
width: 184px;
|
|
26
|
+
height: 50px;
|
|
27
|
+
border-radius: 16px;
|
|
28
|
+
`;
|
|
29
|
+
const SubtitleSkeletonRow = styled.div `
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
gap: 8px;
|
|
34
|
+
margin: 24px 0;
|
|
35
|
+
`;
|
|
36
|
+
const LogoSkeletonStack = styled.div `
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
`;
|
|
40
|
+
const LogoSkeleton = styled(BaseSkeleton) `
|
|
41
|
+
width: 24px;
|
|
42
|
+
height: 24px;
|
|
43
|
+
border-radius: 9999px;
|
|
44
|
+
margin-left: ${(props) => (props.$overlap ? -8 : 0)}px;
|
|
45
|
+
`;
|
|
46
|
+
const SubtitleSkeleton = styled(BaseSkeleton) `
|
|
47
|
+
width: 160px;
|
|
48
|
+
height: 18px;
|
|
49
|
+
border-radius: 9999px;
|
|
50
|
+
`;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useIsClient } from "@coin-voyage/shared/hooks";
|
|
3
|
-
import { useQuery } from "@tanstack/react-query";
|
|
4
3
|
import { AnimatePresence } from "framer-motion";
|
|
5
4
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
6
5
|
import { usePaymentLifecycle } from "../../hooks/usePaymentLifecycle";
|
|
7
6
|
import { ResetContainer } from "../../styles";
|
|
7
|
+
import { ROUTE } from "../../types/routes";
|
|
8
8
|
import usePayContext from "../contexts/pay";
|
|
9
9
|
import ThemedButton, { ThemeContainer } from "../ui/ThemedButton";
|
|
10
10
|
import { SkeletonContainer, TextContainer } from "./styles";
|
|
@@ -19,97 +19,18 @@ export function PayButton(props) {
|
|
|
19
19
|
}
|
|
20
20
|
/** Like PayButton, but with custom styling. */
|
|
21
21
|
function PayButtonCustom(props) {
|
|
22
|
-
const {
|
|
23
|
-
|
|
24
|
-
const {
|
|
25
|
-
const { children, closeOnSuccess, resetOnSuccess } = props;
|
|
26
|
-
const hasDepositParams = "toAddress" in props;
|
|
27
|
-
const hasPayId = "payId" in props;
|
|
28
|
-
const depositParams = useMemo(() => {
|
|
29
|
-
if (!hasDepositParams)
|
|
30
|
-
return null;
|
|
31
|
-
return {
|
|
32
|
-
intent: {
|
|
33
|
-
asset: {
|
|
34
|
-
address: props.toToken,
|
|
35
|
-
chain_id: props.toChain,
|
|
36
|
-
},
|
|
37
|
-
receiving_address: props.toAddress,
|
|
38
|
-
amount: {
|
|
39
|
-
token_amount: props.toAmount,
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
metadata: props.metadata,
|
|
43
|
-
};
|
|
44
|
-
}, [hasDepositParams, props]);
|
|
45
|
-
const payId = hasPayId ? props.payId : null;
|
|
22
|
+
const { options } = usePayContext();
|
|
23
|
+
usePayModalCallbacks(props.onOpen, props.onClose);
|
|
24
|
+
const { order, show, hide } = usePayButtonController(props);
|
|
46
25
|
usePaymentLifecycle(order, {
|
|
47
|
-
onPaymentStarted,
|
|
48
|
-
onPaymentCompleted,
|
|
49
|
-
onPaymentBounced,
|
|
26
|
+
onPaymentStarted: props.onPaymentStarted,
|
|
27
|
+
onPaymentCompleted: props.onPaymentCompleted,
|
|
28
|
+
onPaymentBounced: props.onPaymentBounced,
|
|
29
|
+
}, {
|
|
30
|
+
optimisticConfirmation: options?.optimisticConfirmation,
|
|
50
31
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
useQuery({
|
|
54
|
-
queryKey: [
|
|
55
|
-
"payOrder",
|
|
56
|
-
depositParams?.intent.asset?.address,
|
|
57
|
-
depositParams?.intent.asset?.chain_id,
|
|
58
|
-
depositParams?.intent.receiving_address,
|
|
59
|
-
depositParams?.intent.amount.token_amount,
|
|
60
|
-
metadataKey,
|
|
61
|
-
],
|
|
62
|
-
enabled: hasDepositParams,
|
|
63
|
-
staleTime: Infinity,
|
|
64
|
-
queryFn: async () => {
|
|
65
|
-
if (!depositParams)
|
|
66
|
-
return null;
|
|
67
|
-
await paymentState.createDepositPayOrder(depositParams, (msg) => props.onPaymentCreationError?.({
|
|
68
|
-
type: "payorder_creation_error",
|
|
69
|
-
errorMessage: msg,
|
|
70
|
-
}));
|
|
71
|
-
return null;
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
// Load payment by ID when using payId
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (!payId)
|
|
77
|
-
return;
|
|
78
|
-
setPayId(payId);
|
|
79
|
-
}, [payId, setPayId]);
|
|
80
|
-
// Register open/close handlers
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
setOnOpen(props.onOpen);
|
|
83
|
-
setOnClose(props.onClose);
|
|
84
|
-
return () => {
|
|
85
|
-
setOnOpen(undefined);
|
|
86
|
-
setOnClose(undefined);
|
|
87
|
-
};
|
|
88
|
-
}, [props.onOpen, props.onClose, setOnOpen, setOnClose]);
|
|
89
|
-
const show = useCallback(() => {
|
|
90
|
-
if (!order)
|
|
91
|
-
return;
|
|
92
|
-
showModal({
|
|
93
|
-
closeOnSuccess,
|
|
94
|
-
resetOnSuccess,
|
|
95
|
-
});
|
|
96
|
-
}, [order, closeOnSuccess, resetOnSuccess, showModal]);
|
|
97
|
-
const hide = useCallback(() => {
|
|
98
|
-
setOpen(false);
|
|
99
|
-
}, [setOpen]);
|
|
100
|
-
// Auto-open modal
|
|
101
|
-
const hasAutoOpened = useRef(false);
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (!props.defaultOpen)
|
|
104
|
-
return;
|
|
105
|
-
if (hasAutoOpened.current)
|
|
106
|
-
return;
|
|
107
|
-
if (!order)
|
|
108
|
-
return;
|
|
109
|
-
show();
|
|
110
|
-
hasAutoOpened.current = true;
|
|
111
|
-
}, [order, props.defaultOpen, show]);
|
|
112
|
-
return children({ show, hide });
|
|
32
|
+
useDefaultOpen(props.defaultOpen, show);
|
|
33
|
+
return props.children({ show, hide });
|
|
113
34
|
}
|
|
114
35
|
PayButtonCustom.displayName = "PayButton.Custom";
|
|
115
36
|
PayButton.Custom = PayButtonCustom;
|
|
@@ -140,7 +61,166 @@ const contentVariants = {
|
|
|
140
61
|
},
|
|
141
62
|
};
|
|
142
63
|
function ButtonInner({ label }) {
|
|
143
|
-
return (_jsx(AnimatePresence, { initial: false, children: _jsx(TextContainer, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants, style: {
|
|
144
|
-
|
|
145
|
-
|
|
64
|
+
return (_jsx(AnimatePresence, { initial: false, children: _jsx(TextContainer, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants, style: { height: 40 }, children: label }) }));
|
|
65
|
+
}
|
|
66
|
+
function isDepositParams(props) {
|
|
67
|
+
return "toAddress" in props;
|
|
68
|
+
}
|
|
69
|
+
function isPayIdProps(props) {
|
|
70
|
+
return "payId" in props;
|
|
71
|
+
}
|
|
72
|
+
function useResolvedPaymentInput(props) {
|
|
73
|
+
const hasDepositParams = isDepositParams(props);
|
|
74
|
+
const hasPayId = isPayIdProps(props);
|
|
75
|
+
const toToken = hasDepositParams ? props.toToken : undefined;
|
|
76
|
+
const toChain = hasDepositParams ? props.toChain : undefined;
|
|
77
|
+
const toAddress = hasDepositParams ? props.toAddress : undefined;
|
|
78
|
+
const toAmount = hasDepositParams ? props.toAmount : undefined;
|
|
79
|
+
const metadata = hasDepositParams ? props.metadata : undefined;
|
|
80
|
+
const payId = hasPayId ? props.payId : null;
|
|
81
|
+
const depositParams = useMemo(() => {
|
|
82
|
+
if (!hasDepositParams)
|
|
83
|
+
return null;
|
|
84
|
+
return {
|
|
85
|
+
intent: {
|
|
86
|
+
asset: {
|
|
87
|
+
address: toToken,
|
|
88
|
+
chain_id: toChain,
|
|
89
|
+
},
|
|
90
|
+
receiving_address: toAddress,
|
|
91
|
+
amount: {
|
|
92
|
+
token_amount: toAmount,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
metadata,
|
|
96
|
+
};
|
|
97
|
+
}, [hasDepositParams, toToken, toChain, toAddress, toAmount, metadata]);
|
|
98
|
+
if (payId) {
|
|
99
|
+
return {
|
|
100
|
+
kind: "payId",
|
|
101
|
+
payId,
|
|
102
|
+
depositParams: null,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
kind: "deposit",
|
|
107
|
+
payId: null,
|
|
108
|
+
depositParams: depositParams,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function usePayModalCallbacks(onOpen, onClose) {
|
|
112
|
+
const { setOnOpen, setOnClose } = usePayContext();
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
setOnOpen(onOpen);
|
|
115
|
+
setOnClose(onClose);
|
|
116
|
+
return () => {
|
|
117
|
+
setOnOpen(undefined);
|
|
118
|
+
setOnClose(undefined);
|
|
119
|
+
};
|
|
120
|
+
}, [onClose, onOpen, setOnClose, setOnOpen]);
|
|
121
|
+
}
|
|
122
|
+
function useDefaultOpen(enabled, show) {
|
|
123
|
+
const hasAutoOpened = useRef(false);
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!enabled)
|
|
126
|
+
return;
|
|
127
|
+
if (hasAutoOpened.current)
|
|
128
|
+
return;
|
|
129
|
+
hasAutoOpened.current = true;
|
|
130
|
+
show();
|
|
131
|
+
}, [enabled, show]);
|
|
132
|
+
}
|
|
133
|
+
function useDepositPayOrderLoader(params) {
|
|
134
|
+
const { paymentState } = usePayContext();
|
|
135
|
+
const { depositParams, onPaymentCreationError } = params;
|
|
136
|
+
const handlePaymentCreationError = useCallback((msg) => {
|
|
137
|
+
onPaymentCreationError?.({
|
|
138
|
+
type: "payorder_creation_error",
|
|
139
|
+
errorMessage: msg,
|
|
140
|
+
});
|
|
141
|
+
}, [onPaymentCreationError]);
|
|
142
|
+
return useCallback(async () => {
|
|
143
|
+
if (!depositParams)
|
|
144
|
+
return undefined;
|
|
145
|
+
return paymentState.createDepositPayOrder(depositParams, handlePaymentCreationError);
|
|
146
|
+
}, [depositParams, handlePaymentCreationError, paymentState]);
|
|
147
|
+
}
|
|
148
|
+
function usePayButtonController(props) {
|
|
149
|
+
const { paymentState, showModal, setOpen, setRoute, open, route, displayError } = usePayContext();
|
|
150
|
+
const { payOrder: order, setPayId } = paymentState;
|
|
151
|
+
const { closeOnSuccess, resetOnSuccess, onPaymentCreationError } = props;
|
|
152
|
+
const paymentInput = useResolvedPaymentInput(props);
|
|
153
|
+
const queuedModalOptionsRef = useRef(null);
|
|
154
|
+
const paymentKind = paymentInput.kind;
|
|
155
|
+
const handlePaymentCreationError = useCallback((event) => {
|
|
156
|
+
queuedModalOptionsRef.current = null;
|
|
157
|
+
displayError(event.errorMessage);
|
|
158
|
+
onPaymentCreationError?.(event);
|
|
159
|
+
}, [displayError, onPaymentCreationError]);
|
|
160
|
+
const loadDepositPayOrder = useDepositPayOrderLoader({
|
|
161
|
+
depositParams: paymentInput.depositParams,
|
|
162
|
+
onPaymentCreationError: handlePaymentCreationError,
|
|
163
|
+
});
|
|
164
|
+
const payId = paymentKind === "payId" ? paymentInput.payId : null;
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!payId)
|
|
167
|
+
return;
|
|
168
|
+
setPayId(payId);
|
|
169
|
+
}, [payId, setPayId]);
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
const queuedModalOptions = queuedModalOptionsRef.current;
|
|
172
|
+
if (!queuedModalOptions || !order)
|
|
173
|
+
return;
|
|
174
|
+
queuedModalOptionsRef.current = null;
|
|
175
|
+
showModal(queuedModalOptions);
|
|
176
|
+
}, [order, showModal]);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (paymentKind !== "deposit")
|
|
179
|
+
return;
|
|
180
|
+
if (open || route !== ROUTE.PREPARING_PAYMENT)
|
|
181
|
+
return;
|
|
182
|
+
queuedModalOptionsRef.current = null;
|
|
183
|
+
}, [open, paymentKind, route]);
|
|
184
|
+
const show = useCallback(() => {
|
|
185
|
+
const nextModalOptions = {
|
|
186
|
+
closeOnSuccess,
|
|
187
|
+
resetOnSuccess,
|
|
188
|
+
};
|
|
189
|
+
if (order) {
|
|
190
|
+
showModal(nextModalOptions);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
queuedModalOptionsRef.current = nextModalOptions;
|
|
194
|
+
if (paymentKind === "deposit") {
|
|
195
|
+
displayError(null);
|
|
196
|
+
setRoute(ROUTE.PREPARING_PAYMENT);
|
|
197
|
+
setOpen(true);
|
|
198
|
+
void loadDepositPayOrder();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (payId) {
|
|
202
|
+
void setPayId(payId);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}, [
|
|
206
|
+
closeOnSuccess,
|
|
207
|
+
displayError,
|
|
208
|
+
loadDepositPayOrder,
|
|
209
|
+
order,
|
|
210
|
+
payId,
|
|
211
|
+
paymentKind,
|
|
212
|
+
resetOnSuccess,
|
|
213
|
+
setOpen,
|
|
214
|
+
setPayId,
|
|
215
|
+
setRoute,
|
|
216
|
+
showModal,
|
|
217
|
+
]);
|
|
218
|
+
const hide = useCallback(() => {
|
|
219
|
+
setOpen(false);
|
|
220
|
+
}, [setOpen]);
|
|
221
|
+
return {
|
|
222
|
+
order,
|
|
223
|
+
show,
|
|
224
|
+
hide,
|
|
225
|
+
};
|
|
146
226
|
}
|
|
@@ -9,7 +9,7 @@ import { isWalletConnectConnector } from "../../utils";
|
|
|
9
9
|
import usePayContext from "../contexts/pay";
|
|
10
10
|
import Modal from "../ui/Modal";
|
|
11
11
|
const pages = Object.fromEntries(Object.entries(routeConfig).map(([key, cfg]) => [key, cfg.component]));
|
|
12
|
-
export function PayModal() {
|
|
12
|
+
export function PayModal({ onExited }) {
|
|
13
13
|
const { route, setOpen, setRoute, paymentState, mode, theme, customTheme } = usePayContext();
|
|
14
14
|
const { setConnectorChainType, clearUserSelection, setPayToAddressChainId: setPayToAddressChain, setPayToAddressCurrency, setSelectedCurrencyOption, selectedWallet, selectedCurrencyOption, connectorChainType, } = paymentState;
|
|
15
15
|
const locales = useLocales({
|
|
@@ -61,5 +61,5 @@ export function PayModal() {
|
|
|
61
61
|
setRoute(config.onBack);
|
|
62
62
|
}
|
|
63
63
|
}, [config, actions, setRoute, clearUserSelection]);
|
|
64
|
-
return (_jsx(ThemeProvider, { theme: theme, customTheme: customTheme, mode: mode, children: _jsx(Modal, { pages: pages, pageId: route, heading: heading, depth: depth, onClose: () => setOpen(false), onInfo: showInfoButton ? () => setRoute(ROUTE.ABOUT) : undefined, onBack: showBackButton ? onBack : undefined }) }));
|
|
64
|
+
return (_jsx(ThemeProvider, { theme: theme, customTheme: customTheme, mode: mode, children: _jsx(Modal, { pages: pages, pageId: route, heading: heading, depth: depth, onClose: () => setOpen(false), onExited: onExited, onInfo: showInfoButton ? () => setRoute(ROUTE.ABOUT) : undefined, onBack: showBackButton ? onBack : undefined }) }));
|
|
65
65
|
}
|
|
@@ -10,8 +10,9 @@ type ModalProps = {
|
|
|
10
10
|
positionInside?: boolean;
|
|
11
11
|
inline?: boolean;
|
|
12
12
|
onClose?: () => void;
|
|
13
|
+
onExited?: () => void;
|
|
13
14
|
onBack?: () => void;
|
|
14
15
|
onInfo?: () => void;
|
|
15
16
|
};
|
|
16
|
-
export default function Modal({ pages, pageId, heading, depth, positionInside, inline, onClose, onBack, onInfo, }: ModalProps): import("react/jsx-runtime").JSX.Element | null;
|
|
17
|
+
export default function Modal({ pages, pageId, heading, depth, positionInside, inline, onClose, onExited, onBack, onInfo, }: ModalProps): import("react/jsx-runtime").JSX.Element | null;
|
|
17
18
|
export {};
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { getChainTypeName } from "@coin-voyage/shared/chain";
|
|
3
3
|
import { FocusTrap, usePrevious } from "@coin-voyage/shared/hooks";
|
|
4
4
|
import { isMobile } from "@coin-voyage/shared/utils";
|
|
5
|
+
import { useEffect } from "react";
|
|
5
6
|
import useLocales from "../../../hooks/useLocales";
|
|
6
7
|
import { useThemeContext } from "../../../providers/theme/provider";
|
|
7
8
|
import { ResetContainer } from "../../../styles";
|
|
@@ -37,7 +38,7 @@ export const contentVariants = {
|
|
|
37
38
|
transition: { duration: contentTransitionDuration, ease: [0.26, 0.08, 0.25, 1] },
|
|
38
39
|
},
|
|
39
40
|
};
|
|
40
|
-
export default function Modal({ pages, pageId, heading, depth = 1, positionInside, inline, onClose, onBack, onInfo, }) {
|
|
41
|
+
export default function Modal({ pages, pageId, heading, depth = 1, positionInside, inline, onClose, onExited, onBack, onInfo, }) {
|
|
41
42
|
const context = usePayContext();
|
|
42
43
|
const themeContext = useThemeContext();
|
|
43
44
|
const mobile = isMobile();
|
|
@@ -50,6 +51,12 @@ export default function Modal({ pages, pageId, heading, depth = 1, positionInsid
|
|
|
50
51
|
resizeDependency: context.resize,
|
|
51
52
|
});
|
|
52
53
|
const prevDepth = usePrevious(depth, depth);
|
|
54
|
+
const prevMounted = usePrevious(mounted, mounted);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (prevMounted && !mounted && !context.open) {
|
|
57
|
+
onExited?.();
|
|
58
|
+
}
|
|
59
|
+
}, [context.open, mounted, onExited, prevMounted]);
|
|
53
60
|
const Content = (_jsx(ResetContainer, { "$useTheme": themeContext.theme, "$useMode": themeContext.mode, "$customTheme": themeContext.customTheme, children: _jsxs(ModalContainer, { role: "dialog", style: { pointerEvents: rendered ? "auto" : "none", position: positionInside ? "absolute" : undefined }, children: [!inline && _jsx(BackgroundOverlay, { "$active": rendered, onClick: onClose, "$blur": context.options?.overlayBlur }), _jsxs(Container, { style: dimensionsCSS, initial: false, children: [_jsx("div", { style: {
|
|
54
61
|
pointerEvents: inTransition ? "all" : "none",
|
|
55
62
|
position: "absolute",
|
|
@@ -9,6 +9,7 @@ import MobileConnectors from "../components/Pages/MobileConnectors";
|
|
|
9
9
|
import Onboarding from "../components/Pages/Onboarding";
|
|
10
10
|
import PayToAddress from "../components/Pages/PayToAddress";
|
|
11
11
|
import PayWithToken from "../components/Pages/PayWithToken";
|
|
12
|
+
import PreparingPayment from "../components/Pages/PreparingPayment";
|
|
12
13
|
import SelectChain from "../components/Pages/SelectChain";
|
|
13
14
|
import SelectMethod from "../components/Pages/SelectMethod";
|
|
14
15
|
import SelectPayToAddressChain from "../components/Pages/SelectPayToAddressChain";
|
|
@@ -22,6 +23,12 @@ export const routeConfig = {
|
|
|
22
23
|
heading: (ctx) => ctx.locales.selectMethodScreen_heading,
|
|
23
24
|
showBackButton: false,
|
|
24
25
|
},
|
|
26
|
+
[ROUTE.PREPARING_PAYMENT]: {
|
|
27
|
+
component: _jsx(PreparingPayment, {}),
|
|
28
|
+
heading: () => "Preparing payment",
|
|
29
|
+
showBackButton: false,
|
|
30
|
+
showInfoButton: false,
|
|
31
|
+
},
|
|
25
32
|
[ROUTE.CARD_PAYMENT]: {
|
|
26
33
|
component: _jsx(CardPayment, {}),
|
|
27
34
|
heading: () => "Pay with Card",
|
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { fetchTokenList, getChains } from "@coin-voyage/shared/currency";
|
|
3
|
-
import { useQuery } from "@tanstack/react-query";
|
|
4
2
|
import { useMemo } from "react";
|
|
5
3
|
import usePayContext from "../components/contexts/pay";
|
|
6
4
|
import { SquircleIcon } from "../components/ui/Icon";
|
|
5
|
+
import { BTC_MIN_USD_AMOUNT } from "../lib/constants";
|
|
7
6
|
import { ChainId } from "../server";
|
|
8
7
|
import { ROUTE } from "../types/routes";
|
|
9
|
-
import {
|
|
10
|
-
const TOKEN_LIST_QUERY_KEY = ["token-list"];
|
|
8
|
+
import { useTokenList } from "./useTokenList";
|
|
11
9
|
export function usePayToAddressChainOptions() {
|
|
12
10
|
const { setRoute, paymentState } = usePayContext();
|
|
13
|
-
const { setPayToAddressChainId
|
|
11
|
+
const { setPayToAddressChainId, payOrder } = paymentState;
|
|
14
12
|
const fiatAmount = payOrder?.fulfillment.amount.value_usd;
|
|
15
|
-
const {
|
|
16
|
-
queryKey: TOKEN_LIST_QUERY_KEY,
|
|
17
|
-
queryFn: fetchTokenList,
|
|
18
|
-
staleTime: 1000 * 60 * 15,
|
|
19
|
-
});
|
|
20
|
-
const chains = useMemo(() => getChains(tokenList?.chains ?? []), [tokenList]);
|
|
13
|
+
const { chains, isLoading } = useTokenList();
|
|
21
14
|
const options = useMemo(() => chains.map((chain) => {
|
|
22
15
|
const disabled = chain.chainId === ChainId.BTC && typeof fiatAmount === "number" && fiatAmount < BTC_MIN_USD_AMOUNT;
|
|
23
16
|
return {
|
|
@@ -25,12 +18,12 @@ export function usePayToAddressChainOptions() {
|
|
|
25
18
|
title: chain.name,
|
|
26
19
|
icons: [_jsx(SquircleIcon, { icon: chain.logoURI, alt: chain.name }, chain.chainId)],
|
|
27
20
|
onClick: () => {
|
|
28
|
-
|
|
21
|
+
setPayToAddressChainId(chain.chainId);
|
|
29
22
|
setRoute(ROUTE.ADDRESS_TOKEN_SELECT);
|
|
30
23
|
},
|
|
31
24
|
disabled,
|
|
32
25
|
subtitle: disabled ? `Minimum $${BTC_MIN_USD_AMOUNT}` : undefined,
|
|
33
26
|
};
|
|
34
|
-
}), [chains,
|
|
27
|
+
}), [chains, setPayToAddressChainId, setRoute, fiatAmount]);
|
|
35
28
|
return { options, isLoading };
|
|
36
29
|
}
|
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { tokenToCurrency } from "@coin-voyage/shared/currency";
|
|
4
3
|
import { useMemo } from "react";
|
|
5
4
|
import usePayContext from "../components/contexts/pay";
|
|
6
5
|
import { SquircleIcon } from "../components/ui/Icon";
|
|
7
6
|
import { ROUTE } from "../types/routes";
|
|
8
|
-
|
|
7
|
+
import { useTokenList } from "./useTokenList";
|
|
9
8
|
export function usePayToAddressTokens() {
|
|
10
9
|
const { paymentState, setRoute } = usePayContext();
|
|
11
|
-
const { payToAddressChainId
|
|
12
|
-
const
|
|
13
|
-
const { data: tokenList, isLoading } = useQuery({
|
|
14
|
-
queryKey: TOKEN_LIST_QUERY_KEY,
|
|
15
|
-
queryFn: fetchTokenList,
|
|
16
|
-
staleTime: 1000 * 60 * 5,
|
|
17
|
-
});
|
|
18
|
-
const tokens = useMemo(() => {
|
|
19
|
-
if (!tokenList || !chainId)
|
|
20
|
-
return [];
|
|
21
|
-
return tokensByChainId(tokenList.chains, chainId);
|
|
22
|
-
}, [tokenList, chainId]);
|
|
10
|
+
const { payToAddressChainId, setPayToAddressCurrency } = paymentState;
|
|
11
|
+
const { tokens, isLoading } = useTokenList(payToAddressChainId);
|
|
23
12
|
const options = useMemo(() => tokens.map((token) => ({
|
|
24
13
|
id: token.address ?? token.ticker,
|
|
25
14
|
title: token.name,
|