@theoo/react 0.4.1 → 0.4.3
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 +1 -1
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +6 -6
- package/dist/components.js.map +1 -1
- package/dist/connect.d.ts.map +1 -1
- package/dist/connect.js +1 -3
- package/dist/connect.js.map +1 -1
- package/dist/hooks-entry.d.ts +1 -1
- package/dist/hooks-entry.d.ts.map +1 -1
- package/dist/hooks-entry.js +1 -1
- package/dist/hooks-entry.js.map +1 -1
- package/dist/hooks.d.ts +27 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +112 -0
- package/dist/hooks.js.map +1 -1
- package/dist/money.d.ts +4 -4
- package/dist/money.d.ts.map +1 -1
- package/dist/money.js +195 -97
- package/dist/money.js.map +1 -1
- package/package.json +2 -2
package/dist/money.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { formatUnits, parseUnits } from "@theoo/sdk";
|
|
4
4
|
import { useTheoo } from "./context.js";
|
|
5
5
|
import { styles } from "./components.js";
|
|
6
|
-
import { useBalances, useEspees, useMoneyOrders, useSwap } from "./hooks.js";
|
|
6
|
+
import { useBalances, useEspees, useEspeesSell, useMoneyOrders, useSwap } from "./hooks.js";
|
|
7
7
|
const s = styles;
|
|
8
8
|
const t = styles.token;
|
|
9
9
|
const card = {
|
|
@@ -27,13 +27,13 @@ function find(balances, wanted) {
|
|
|
27
27
|
return balances.find((b) => b.token.symbol.toLowerCase() === w || b.token.address.toLowerCase() === w);
|
|
28
28
|
}
|
|
29
29
|
const STATE_WORDS = {
|
|
30
|
-
awaiting_signature: "
|
|
31
|
-
processing: "
|
|
30
|
+
awaiting_signature: "Awaiting confirmation",
|
|
31
|
+
processing: "Processing",
|
|
32
32
|
completed: "Done",
|
|
33
33
|
failed: "Failed",
|
|
34
34
|
expired: "Expired",
|
|
35
35
|
open: "Open",
|
|
36
|
-
awaiting_delivery: "
|
|
36
|
+
awaiting_delivery: "Pending",
|
|
37
37
|
delivered: "Delivered",
|
|
38
38
|
disputed: "Disputed",
|
|
39
39
|
refunded: "Refunded",
|
|
@@ -47,10 +47,10 @@ const STATE_WORDS = {
|
|
|
47
47
|
* the passkey signs is one batch that approves the exact amount, swaps, and
|
|
48
48
|
* resets the approval to zero. Nothing is left behind for anyone to spend.
|
|
49
49
|
*/
|
|
50
|
-
export function TheooMoney({ chain, flows = ["swap"], defaultSell, defaultBuy, onDone, explorerTx, className
|
|
50
|
+
export function TheooMoney({ chain, flows = ["swap"], defaultSell, defaultBuy, onDone, explorerTx, className }) {
|
|
51
51
|
const [tab, setTab] = useState(flows[0] ?? "swap");
|
|
52
|
-
const names = { swap: "Swap", espees: "Buy ESPEES", buy: "Add money", cashout: "Cash out" };
|
|
53
|
-
return (_jsxs("div", { className: className, style: card, "data-theoo-money": "", children: [flows.length > 1 ? (_jsx("div", { role: "tablist", style: { display: "flex", gap: 6, overflowX: "auto", scrollbarWidth: "none", margin: "0 -4px", padding: "0 4px" }, children: flows.map((f) => (_jsx("button", { type: "button", role: "tab", "aria-selected": tab === f, onClick: () => setTab(f), style: { ...(tab === f ? s.button : s.secondary), padding: "6px 12px", fontSize: 13.5, whiteSpace: "nowrap", flex: "none", width: "auto" }, children: names[f] }, f))) })) : null, tab === "espees" ? (_jsx(EspeesPanel, { chain: chain, onDone: onDone, explorerTx: explorerTx })) : tab === "
|
|
52
|
+
const names = { swap: "Swap", espees: "Buy ESPEES", sell: "Sell ESPEES", buy: "Add money", cashout: "Cash out" };
|
|
53
|
+
return (_jsxs("div", { className: className, style: card, "data-theoo-money": "", children: [flows.length > 1 ? (_jsx("div", { role: "tablist", style: { display: "flex", gap: 6, overflowX: "auto", scrollbarWidth: "none", margin: "0 -4px", padding: "0 4px" }, children: flows.map((f) => (_jsx("button", { type: "button", role: "tab", "aria-selected": tab === f, onClick: () => setTab(f), style: { ...(tab === f ? s.button : s.secondary), padding: "6px 12px", fontSize: 13.5, whiteSpace: "nowrap", flex: "none", width: "auto" }, children: names[f] }, f))) })) : null, tab === "espees" ? (_jsx(EspeesPanel, { chain: chain, onDone: onDone, explorerTx: explorerTx })) : tab === "sell" ? (_jsx(SellPanel, { chain: chain })) : tab === "buy" ? (_jsx(BachsPanel, { mode: "buy" })) : tab === "cashout" ? (_jsx(BachsPanel, { mode: "cashout" })) : (_jsx(SwapPanel, { chain: chain, defaultSell: defaultSell, defaultBuy: defaultBuy, onDone: onDone, explorerTx: explorerTx }))] }));
|
|
54
54
|
}
|
|
55
55
|
function SwapPanel({ chain, defaultSell, defaultBuy, onDone, explorerTx }) {
|
|
56
56
|
const { balances, error: balanceError, reload } = useBalances(chain);
|
|
@@ -82,17 +82,15 @@ function SwapPanel({ chain, defaultSell, defaultBuy, onDone, explorerTx }) {
|
|
|
82
82
|
try {
|
|
83
83
|
const units = parseUnits(amount, sell.token.decimals);
|
|
84
84
|
if (BigInt(units) === 0n)
|
|
85
|
-
return { problem: "Enter an amount
|
|
85
|
+
return { problem: "Enter an amount." };
|
|
86
86
|
if (BigInt(units) > BigInt(sell.amount))
|
|
87
|
-
return { problem:
|
|
87
|
+
return { problem: "Not enough balance." };
|
|
88
88
|
return { units };
|
|
89
89
|
}
|
|
90
90
|
catch (e) {
|
|
91
91
|
const tooPrecise = e instanceof Error && /decimal places/.test(e.message);
|
|
92
92
|
return {
|
|
93
|
-
problem: tooPrecise
|
|
94
|
-
? `${sell.token.symbol} has at most ${sell.token.decimals} decimal places.`
|
|
95
|
-
: "Enter an amount like 12.5.",
|
|
93
|
+
problem: tooPrecise ? "Too many decimals." : "Invalid amount.",
|
|
96
94
|
};
|
|
97
95
|
}
|
|
98
96
|
}, [amount, sell]);
|
|
@@ -123,17 +121,23 @@ function SwapPanel({ chain, defaultSell, defaultBuy, onDone, explorerTx }) {
|
|
|
123
121
|
const q = swap.quote;
|
|
124
122
|
const done = swap.status;
|
|
125
123
|
const txLink = done?.txHash && explorerTx ? explorerTx(done.txHash, done.chain) : null;
|
|
126
|
-
return (_jsxs("div", { style: { display: "grid", gap: 12 }, children: [
|
|
124
|
+
return (_jsxs("div", { style: { display: "grid", gap: 12 }, children: [_jsx("div", { style: { display: "flex", flexWrap: "wrap", justifyContent: "space-between", alignItems: "baseline", gap: "4px 10px" }, children: _jsx("strong", { style: { fontSize: 15.5, letterSpacing: "-0.01em", whiteSpace: "nowrap" }, children: "Swap" }) }), balanceError ? (_jsx("div", { style: s.error, role: "alert", children: balanceError.message })) : null, !balances && !balanceError ? _jsx("p", { style: s.muted, children: "Loading\u2026" }) : null, balances && list.length < 2 ? _jsx("p", { style: s.muted, children: "Not available yet." }) : null, balances && list.length >= 2 ? (_jsxs(_Fragment, { children: [_jsxs("div", { style: { display: "grid", gap: 6 }, children: [_jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [_jsx("label", { htmlFor: "theoo-money-amount", style: label, children: "You pay" }), sell ? (_jsxs("span", { style: s.tiny, children: ["Balance ", sell.display, " ", sell.token.symbol, " ", _jsx("button", { type: "button", style: link, onClick: () => setAmount(formatUnits(sell.amount, sell.token.decimals, sell.token.decimals)), children: "Max" })] })) : null] }), _jsxs("div", { style: row, children: [_jsx("input", { id: "theoo-money-amount", inputMode: "decimal", autoComplete: "off", placeholder: "0.00", value: amount, onChange: (e) => setAmount(e.target.value), style: { ...s.input, fontFamily: t.mono, fontSize: 17 }, "aria-invalid": parsed.problem ? true : undefined }), _jsx("select", { id: "theoo-money-sell", "aria-label": "Token to pay with", value: sellAddr, onChange: (e) => setSellAddr(e.target.value), style: select, children: list.map((b) => (_jsx("option", { value: b.token.address, disabled: b.token.address === buyAddr, children: b.token.symbol }, b.token.address))) })] }), parsed.problem ? _jsx("p", { style: { ...s.tiny, color: t.alert }, children: parsed.problem }) : null] }), _jsx("div", { style: { display: "flex", justifyContent: "center" }, children: _jsx("button", { type: "button", onClick: flip, "aria-label": "Swap direction", style: { ...s.secondary, padding: "4px 12px", fontSize: 15 }, children: "\u21C5" }) }), _jsxs("div", { style: { display: "grid", gap: 6 }, children: [_jsx("label", { htmlFor: "theoo-money-buy", style: label, children: "You get" }), _jsxs("div", { style: row, children: [_jsx("div", { style: { ...s.input, fontFamily: t.mono, fontSize: 17, color: q ? t.ink : t.ink3 }, "aria-live": "polite", children: q ? q.buy.display : "—" }), _jsx("select", { id: "theoo-money-buy", value: buyAddr, onChange: (e) => setBuyAddr(e.target.value), style: select, children: list.map((b) => (_jsx("option", { value: b.token.address, disabled: b.token.address === sellAddr, children: b.token.symbol }, b.token.address))) })] })] }), q && !swap.expired ? (_jsxs("div", { style: { display: "grid", gap: 6, padding: 12, borderRadius: t.radius, background: t.accentSoft, fontSize: 13.5, lineHeight: 1.5 }, "data-theoo-quote": "", children: [_jsx("span", { children: q.summary }), q.warning ? _jsx("span", { style: { color: t.alert }, children: q.warning }) : null, _jsxs("span", { style: s.tiny, children: [swap.secondsLeft, "s"] })] })) : null, q && swap.expired ? _jsx("p", { style: s.muted, children: "Quote expired." }) : null, swap.error ? (_jsx("div", { style: s.error, role: "alert", children: swap.error.message })) : null, !q || swap.expired ? (_jsx("button", { type: "button", style: { ...s.button, opacity: !parsed.units || swap.busy ? 0.6 : 1 }, disabled: !parsed.units || swap.busy || !sell || !buy, onClick: () => sell && buy && parsed.units && swap.ask({ sellToken: sell.token.address, buyToken: buy.token.address, sellAmount: parsed.units }), children: swap.busy ? "Getting a quote…" : q ? "Refresh quote" : "Get a quote" })) : (_jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr auto", gap: 8 }, children: [_jsx("button", { type: "button", style: { ...s.button, opacity: swap.busy ? 0.6 : 1 }, disabled: swap.busy, onClick: () => void swap.confirm(), children: swap.busy ? "Confirming…" : "Confirm" }), _jsx("button", { type: "button", style: s.secondary, disabled: swap.busy, onClick: swap.reset, children: "Cancel" })] })), done ? (_jsxs("p", { style: { ...s.muted, color: done.state === "failed" ? t.alert : t.ink2 }, role: "status", children: [done.state === "included" ? "Swapped. " : done.state === "failed" ? "Swap failed. " : "Processing. ", txLink ? (_jsx("a", { href: txLink, target: "_blank", rel: "noopener noreferrer", style: { color: t.accent }, children: "View transaction" })) : null] })) : null] })) : null, orders && orders.length ? _jsx(Recent, { orders: orders, explorerTx: explorerTx }) : null] }));
|
|
127
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* A buy, in a few words. Behind them: the payment sits in an escrow on chain,
|
|
128
|
+
* not with the desk. The desk sends ESPEES by hand; the buyer confirms it
|
|
129
|
+
* arrived or disputes it, and a dispute goes to the arbiter. If the desk
|
|
130
|
+
* delivers nothing by the deadline, the escrow refunds the buyer on its own.
|
|
131
|
+
*/
|
|
128
132
|
const ESPEES_WORDS = {
|
|
129
|
-
awaiting_signature: "
|
|
130
|
-
awaiting_delivery: "Paid
|
|
131
|
-
delivered: "
|
|
132
|
-
disputed: "
|
|
133
|
-
completed: "Done
|
|
134
|
-
refunded: "Refunded
|
|
135
|
-
failed: "
|
|
136
|
-
expired: "
|
|
133
|
+
awaiting_signature: "Awaiting confirmation.",
|
|
134
|
+
awaiting_delivery: "Paid. Your ESPEES is on its way.",
|
|
135
|
+
delivered: "Sent. Did it arrive?",
|
|
136
|
+
disputed: "Under review.",
|
|
137
|
+
completed: "Done.",
|
|
138
|
+
refunded: "Refunded.",
|
|
139
|
+
failed: "Failed. Nothing was charged.",
|
|
140
|
+
expired: "Expired. Nothing was charged.",
|
|
137
141
|
};
|
|
138
142
|
/**
|
|
139
143
|
* ESPEES from the desk. The payment goes into an escrow on chain, the desk
|
|
@@ -160,13 +164,13 @@ function EspeesPanel({ chain, onDone, explorerTx }) {
|
|
|
160
164
|
try {
|
|
161
165
|
const units = parseUnits(amount, chosen.token.decimals);
|
|
162
166
|
if (BigInt(units) === 0n)
|
|
163
|
-
return { problem: "Enter an amount
|
|
167
|
+
return { problem: "Enter an amount." };
|
|
164
168
|
if (held && BigInt(units) > BigInt(held.amount))
|
|
165
|
-
return { problem:
|
|
169
|
+
return { problem: "Not enough balance." };
|
|
166
170
|
return { units };
|
|
167
171
|
}
|
|
168
172
|
catch {
|
|
169
|
-
return { problem: "
|
|
173
|
+
return { problem: "Invalid amount." };
|
|
170
174
|
}
|
|
171
175
|
}, [amount, chosen, held]);
|
|
172
176
|
const estimate = chosen && parsed.units
|
|
@@ -203,82 +207,156 @@ function EspeesPanel({ chain, onDone, explorerTx }) {
|
|
|
203
207
|
await reloadOrders();
|
|
204
208
|
};
|
|
205
209
|
if (!e.info)
|
|
206
|
-
return _jsx("p", { style: s.muted, children: "Loading
|
|
210
|
+
return _jsx("p", { style: s.muted, children: "Loading\u2026" });
|
|
207
211
|
if (!e.info.open)
|
|
208
|
-
return _jsx("p", { style: s.muted, children: "
|
|
212
|
+
return _jsx("p", { style: s.muted, children: "Not available right now." });
|
|
209
213
|
const q = e.quote;
|
|
210
214
|
const minutes = Math.round(e.info.deliverWithinSecs / 60);
|
|
211
|
-
return (_jsxs("div", { style: { display: "grid", gap: 12 }, "data-theoo-espees": "", children: [_jsxs("div", { style: { display: "flex", flexWrap: "wrap", justifyContent: "space-between", alignItems: "baseline", gap: "4px 10px" }, children: [_jsx("strong", { style: { fontSize: 15.5, letterSpacing: "-0.01em", whiteSpace: "nowrap" }, children: "Buy ESPEES" }), _jsxs("span", { style: s.tiny, children: ["
|
|
215
|
+
return (_jsxs("div", { style: { display: "grid", gap: 12 }, "data-theoo-espees": "", children: [_jsxs("div", { style: { display: "flex", flexWrap: "wrap", justifyContent: "space-between", alignItems: "baseline", gap: "4px 10px" }, children: [_jsx("strong", { style: { fontSize: 15.5, letterSpacing: "-0.01em", whiteSpace: "nowrap" }, children: "Buy ESPEES" }), _jsxs("span", { style: s.tiny, children: ["Arrives within ", minutes, " min"] })] }), _jsxs("div", { style: { display: "grid", gap: 6 }, children: [_jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [_jsx("label", { htmlFor: "theoo-espees-amount", style: label, children: "You pay" }), held && chosen ? (_jsxs("span", { style: s.tiny, children: ["Balance ", held.display, " ", chosen.token.symbol] })) : null] }), _jsxs("div", { style: row, children: [_jsx("input", { id: "theoo-espees-amount", inputMode: "decimal", autoComplete: "off", placeholder: "0.00", value: amount, onChange: (ev) => setAmount(ev.target.value), style: { ...s.input, fontFamily: t.mono, fontSize: 17 }, "aria-invalid": parsed.problem ? true : undefined }), _jsx("select", { "aria-label": "Pay ESPEES with", value: token, onChange: (ev) => setToken(ev.target.value), style: select, children: tokens.map((d) => (_jsx("option", { value: d.token.address, children: d.token.symbol }, d.token.address))) })] }), parsed.problem ? _jsx("p", { style: { ...s.tiny, color: t.alert }, children: parsed.problem }) : null, chosen ? (_jsxs("p", { style: s.tiny, children: [estimate ? `You get about ${estimate} ESPEES · ` : "", chosen.price, " ESPEES per ", chosen.token.symbol] })) : null] }), _jsxs("div", { style: { display: "grid", gap: 6 }, children: [_jsx("label", { htmlFor: "theoo-espees-address", style: label, children: "Espees address" }), _jsx("input", { id: "theoo-espees-address", autoComplete: "off", value: address, onChange: (ev) => setAddress(ev.target.value), style: s.input }), _jsx("label", { htmlFor: "theoo-espees-again", style: label, children: "Confirm address" }), _jsx("input", { id: "theoo-espees-again", autoComplete: "off", value: again, onChange: (ev) => setAgain(ev.target.value), style: s.input, "aria-invalid": mismatch ? true : undefined }), mismatch ? _jsx("p", { style: { ...s.tiny, color: t.alert }, children: "The two addresses don't match." }) : null] }), q && !e.expired ? (_jsxs("div", { style: { display: "grid", gap: 6, padding: 12, borderRadius: t.radius, background: t.accentSoft, fontSize: 13.5, lineHeight: 1.5 }, "data-theoo-quote": "", children: [_jsx("span", { children: q.summary }), _jsxs("span", { style: s.tiny, children: [e.secondsLeft, "s"] })] })) : null, q && e.expired ? _jsx("p", { style: s.muted, children: "Price expired." }) : null, e.error ? (_jsx("div", { style: s.error, role: "alert", children: e.error.message })) : null, !q || e.expired ? (_jsx("button", { type: "button", style: { ...s.button, opacity: !parsed.units || e.busy ? 0.6 : 1 }, disabled: !parsed.units || e.busy || !chosen || !address.trim() || address.trim() !== again.trim(), onClick: () => chosen &&
|
|
212
216
|
parsed.units &&
|
|
213
|
-
e.ask({ payToken: chosen.token.address, payAmount: parsed.units, espeesAddress: address, espeesAddressConfirm: again }), children: e.busy ? "
|
|
217
|
+
e.ask({ payToken: chosen.token.address, payAmount: parsed.units, espeesAddress: address, espeesAddressConfirm: again }), children: e.busy ? "Getting a price…" : q ? "Refresh price" : "Get a price" })) : (_jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr auto", gap: 8 }, children: [_jsx("button", { type: "button", style: { ...s.button, opacity: e.busy ? 0.6 : 1 }, disabled: e.busy, onClick: () => void e.confirm(), children: e.busy ? "Paying…" : "Pay" }), _jsx("button", { type: "button", style: s.secondary, disabled: e.busy, onClick: e.reset, children: "Cancel" })] })), latest ? (_jsxs("div", { style: { display: "grid", gap: 8, borderTop: `1px solid ${t.line}`, paddingTop: 10 }, "data-espees-order": latest.state, children: [_jsxs("span", { style: label, children: ["Latest: ", latest.pay ? `${latest.pay.display} ${latest.pay.symbol}` : "", " \u2192 ", latest.get ? `${latest.get.display} ESPEES` : ""] }), _jsx("span", { style: { fontSize: 13.5 }, children: ESPEES_WORDS[latest.state] ?? latest.state }), latest.state === "delivered" ? (_jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsx("button", { type: "button", style: s.button, disabled: e.busy, onClick: () => void answer(e.release), children: "It arrived" }), _jsx("button", { type: "button", style: s.secondary, disabled: e.busy, onClick: () => void answer(e.dispute), children: "Report a problem" })] })) : null, latest.txHash && explorerTx && latest.chain ? (_jsx("a", { href: explorerTx(latest.txHash, latest.chain) ?? undefined, target: "_blank", rel: "noopener noreferrer", style: { color: t.accent, fontSize: 12.5 }, children: "View transaction" })) : null] })) : null] }));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* A sale, in a few words. Behind them: the desk first locks the payout in an
|
|
221
|
+
* escrow on chain, naming this account; only then is the seller told where
|
|
222
|
+
* to send. If the seller sends nothing in time the desk takes its payout
|
|
223
|
+
* back; if the desk says nothing arrived, the arbiter decides. A desk that
|
|
224
|
+
* never locks a payout lets the order lapse, and nothing moves.
|
|
225
|
+
*/
|
|
226
|
+
const SELL_WORDS = {
|
|
227
|
+
awaiting_desk: "Pending. Don't send yet.",
|
|
228
|
+
awaiting_espees: "Ready. Send the ESPEES now.",
|
|
229
|
+
espees_sent: "Confirming receipt.",
|
|
230
|
+
disputed: "Under review.",
|
|
231
|
+
completed: "Paid.",
|
|
232
|
+
expired: "Expired.",
|
|
233
|
+
rejected: "Closed. The ESPEES didn't arrive.",
|
|
234
|
+
cancelled: "Cancelled.",
|
|
235
|
+
lapsed: "Expired.",
|
|
236
|
+
};
|
|
237
|
+
const when = (secs) => (secs ? new Date(secs * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "");
|
|
238
|
+
/**
|
|
239
|
+
* Selling ESPEES to the desk. The desk goes first: it locks the payout in an
|
|
240
|
+
* escrow on chain, naming this account, and only then is the seller shown
|
|
241
|
+
* where to send. Nothing leaves the account at any step.
|
|
242
|
+
*/
|
|
243
|
+
function SellPanel({ chain }) {
|
|
244
|
+
const e = useEspeesSell(chain);
|
|
245
|
+
const sell = e.info?.sell;
|
|
246
|
+
const tokens = sell?.tokens ?? [];
|
|
247
|
+
const [token, setToken] = useState("");
|
|
248
|
+
const [amount, setAmount] = useState("");
|
|
249
|
+
const [from, setFrom] = useState("");
|
|
250
|
+
const [again, setAgain] = useState("");
|
|
251
|
+
const [reference, setReference] = useState("");
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
if (!token && tokens.length)
|
|
254
|
+
setToken(tokens[0].token.address);
|
|
255
|
+
}, [tokens, token]);
|
|
256
|
+
const chosen = tokens.find((d) => d.token.address === token);
|
|
257
|
+
const parsed = useMemo(() => {
|
|
258
|
+
if (!amount.trim())
|
|
259
|
+
return {};
|
|
260
|
+
try {
|
|
261
|
+
const units = parseUnits(amount, 18);
|
|
262
|
+
if (BigInt(units) === 0n)
|
|
263
|
+
return { problem: "Enter an amount." };
|
|
264
|
+
return { units };
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return { problem: "Invalid amount." };
|
|
268
|
+
}
|
|
269
|
+
}, [amount]);
|
|
270
|
+
const estimate = chosen && parsed.units ? formatUnits((BigInt(parsed.units) * 10n ** BigInt(chosen.token.decimals)) / BigInt(chosen.priceE18), chosen.token.decimals, 6) : null;
|
|
271
|
+
const mismatch = again.trim() !== "" && from.trim() !== again.trim();
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
if (e.quote)
|
|
274
|
+
e.reset();
|
|
275
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
276
|
+
}, [token, amount, from, again]);
|
|
277
|
+
if (!e.info)
|
|
278
|
+
return _jsx("p", { style: s.muted, children: "Loading\u2026" });
|
|
279
|
+
const o = e.order;
|
|
280
|
+
const q = e.quote;
|
|
281
|
+
const error = e.error ? (_jsx("div", { style: s.error, role: "alert", children: e.error.message })) : null;
|
|
282
|
+
// A sale this session has seen: what the seller does next, or how it ended,
|
|
283
|
+
// until they choose to sell more.
|
|
284
|
+
if (o) {
|
|
285
|
+
return (_jsxs("div", { style: { display: "grid", gap: 10 }, "data-theoo-sell": o.state, children: [_jsxs("strong", { style: { fontSize: 15.5, letterSpacing: "-0.01em" }, children: ["Selling ", o.espees.display, " ESPEES for ", o.receive.display, " ", o.receive.symbol] }), _jsx("span", { style: { fontSize: 13.5, lineHeight: 1.5 }, children: SELL_WORDS[o.state] ?? o.state }), o.state === "awaiting_espees" && o.sendTo ? (_jsxs("div", { style: { display: "grid", gap: 8, padding: 12, borderRadius: t.radius, background: t.accentSoft, fontSize: 13.5, lineHeight: 1.6 }, children: [_jsxs("span", { children: ["Send ", _jsxs("strong", { children: [o.espees.display, " ESPEES"] }), " from ", _jsx("strong", { children: o.espeesFrom }), " to ", _jsx("strong", { "data-sell-send-to": "", children: o.sendTo }), " ", "with note ", _jsx("strong", { style: { fontFamily: t.mono }, "data-sell-memo": "", children: o.memo }), o.deadline ? ` by ${when(o.deadline)}` : "", "."] }), _jsx("label", { htmlFor: "theoo-sell-reference", style: label, children: "Transaction reference" }), _jsx("input", { id: "theoo-sell-reference", autoComplete: "off", value: reference, onChange: (ev) => setReference(ev.target.value), style: s.input }), _jsx("button", { type: "button", style: { ...s.button, opacity: e.busy || reference.trim().length < 3 ? 0.6 : 1 }, disabled: e.busy || reference.trim().length < 3, onClick: () => void e.sent(reference.trim()), children: e.busy ? "Confirming…" : "I've sent it" })] })) : null, o.state === "espees_sent" && o.disputeUntil ? (_jsxs("span", { style: s.tiny, children: ["Payout by ", when(o.disputeUntil)] })) : null, o.state === "awaiting_desk" ? (_jsx("button", { type: "button", style: s.secondary, disabled: e.busy, onClick: () => void e.cancel(), children: "Cancel" })) : null, !e.live ? (_jsx("button", { type: "button", style: s.secondary, onClick: e.dismiss, children: "Sell more" })) : null, error] }));
|
|
286
|
+
}
|
|
287
|
+
if (!sell?.open)
|
|
288
|
+
return _jsx("p", { style: s.muted, children: "Not available right now." });
|
|
289
|
+
return (_jsxs("div", { style: { display: "grid", gap: 12 }, "data-theoo-sell": "", children: [_jsx("div", { style: { display: "flex", flexWrap: "wrap", justifyContent: "space-between", alignItems: "baseline", gap: "4px 10px" }, children: _jsx("strong", { style: { fontSize: 15.5, letterSpacing: "-0.01em", whiteSpace: "nowrap" }, children: "Sell ESPEES" }) }), _jsxs("div", { style: { display: "grid", gap: 6 }, children: [_jsx("label", { htmlFor: "theoo-sell-amount", style: label, children: "You sell" }), _jsxs("div", { style: row, children: [_jsx("input", { id: "theoo-sell-amount", inputMode: "decimal", autoComplete: "off", placeholder: "0.00", value: amount, onChange: (ev) => setAmount(ev.target.value), style: { ...s.input, fontFamily: t.mono, fontSize: 17 }, "aria-invalid": parsed.problem ? true : undefined }), _jsx("select", { "aria-label": "Be paid in", value: token, onChange: (ev) => setToken(ev.target.value), style: select, children: tokens.map((d) => (_jsx("option", { value: d.token.address, children: d.token.symbol }, d.token.address))) })] }), parsed.problem ? _jsx("p", { style: { ...s.tiny, color: t.alert }, children: parsed.problem }) : null, chosen ? (_jsxs("p", { style: s.tiny, children: [estimate ? `You get about ${estimate} ${chosen.token.symbol} · ` : "", chosen.price, " ESPEES per ", chosen.token.symbol] })) : null] }), _jsxs("div", { style: { display: "grid", gap: 6 }, children: [_jsx("label", { htmlFor: "theoo-sell-from", style: label, children: "Espees address" }), _jsx("input", { id: "theoo-sell-from", autoComplete: "off", value: from, onChange: (ev) => setFrom(ev.target.value), style: s.input }), _jsx("label", { htmlFor: "theoo-sell-again", style: label, children: "Confirm address" }), _jsx("input", { id: "theoo-sell-again", autoComplete: "off", value: again, onChange: (ev) => setAgain(ev.target.value), style: s.input, "aria-invalid": mismatch ? true : undefined }), mismatch ? _jsx("p", { style: { ...s.tiny, color: t.alert }, children: "The two addresses don't match." }) : null] }), q && !e.expired ? (_jsxs("div", { style: { display: "grid", gap: 6, padding: 12, borderRadius: t.radius, background: t.accentSoft, fontSize: 13.5, lineHeight: 1.5 }, "data-theoo-quote": "", children: [_jsx("span", { children: q.summary }), _jsxs("span", { style: s.tiny, children: [e.secondsLeft, "s"] })] })) : null, q && e.expired ? _jsx("p", { style: s.muted, children: "Price expired." }) : null, error, !q || e.expired ? (_jsx("button", { type: "button", style: { ...s.button, opacity: !parsed.units || e.busy ? 0.6 : 1 }, disabled: !parsed.units || e.busy || !chosen || !from.trim() || from.trim() !== again.trim(), onClick: () => chosen && parsed.units && e.ask({ receiveToken: chosen.token.address, espeesAmount: parsed.units, espeesFrom: from, espeesFromConfirm: again }), children: e.busy ? "Getting a price…" : q ? "Refresh price" : "Get a price" })) : (_jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr auto", gap: 8 }, children: [_jsx("button", { type: "button", style: { ...s.button, opacity: e.busy ? 0.6 : 1 }, disabled: e.busy, onClick: () => void e.place(), children: e.busy ? "Placing…" : "Place order" }), _jsx("button", { type: "button", style: s.secondary, disabled: e.busy, onClick: e.reset, children: "Cancel" })] }))] }));
|
|
214
290
|
}
|
|
215
291
|
/** The newest order of a kind, with its state in words. */
|
|
216
292
|
function Latest({ order, words }) {
|
|
217
293
|
if (!order)
|
|
218
294
|
return null;
|
|
219
|
-
return (_jsxs("div", { style: { display: "grid", gap: 4, borderTop: `1px solid ${t.line}`, paddingTop: 10 }, "data-money-latest": order.state, children: [_jsx("span", { style: label, children: "
|
|
295
|
+
return (_jsxs("div", { style: { display: "grid", gap: 4, borderTop: `1px solid ${t.line}`, paddingTop: 10 }, "data-money-latest": order.state, children: [_jsx("span", { style: label, children: "Latest" }), _jsx("span", { style: { fontSize: 13.5 }, children: words[order.state] ?? STATE_WORDS[order.state] ?? order.state })] }));
|
|
220
296
|
}
|
|
221
|
-
|
|
222
|
-
|
|
297
|
+
const CHAIN_NAMES = { 1: "Ethereum", 56: "BNB Chain", 97: "BNB testnet", 8453: "Base", 84532: "Base Sepolia", 11155111: "Sepolia" };
|
|
298
|
+
const chainLabel = (c) => CHAIN_NAMES[c] ?? `chain ${c}`;
|
|
299
|
+
// `received`: the naira (or dollars) reached the user's Bachs balance.
|
|
300
|
+
// `processing`: Bachs is paying it on. `awaiting_payout`: Bachs refused the
|
|
301
|
+
// payout for now (a limit, a balance); the settler tries again.
|
|
302
|
+
const BACHS_WORDS = {
|
|
303
|
+
received: "Received",
|
|
304
|
+
processing: "Processing",
|
|
305
|
+
awaiting_payout: "Pending",
|
|
306
|
+
completed: "Completed",
|
|
307
|
+
failed: "Failed",
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
310
|
+
* Naira in and out through the user's own Bachs account. Bachs does the KYC
|
|
311
|
+
* on its own page and holds the naira in the user's own balance there; Theoo
|
|
312
|
+
* only passes it on, to this user's own account or their own bank.
|
|
313
|
+
*
|
|
314
|
+
* None of that is said on screen, Bachs's name included: the user sees an
|
|
315
|
+
* identity check, then an account number. Bachs's fees (1% of a deposit, at
|
|
316
|
+
* most ₦300, and 1% with a minimum of 1 to pay it on as a stablecoin) show
|
|
317
|
+
* up only as "Fees apply"; the project's own terms carry the detail.
|
|
318
|
+
*/
|
|
319
|
+
function BachsPanel({ mode }) {
|
|
223
320
|
const theoo = useTheoo();
|
|
224
|
-
const {
|
|
225
|
-
const
|
|
226
|
-
const stables = (balances?.balances ?? []).filter((b) => b.token.stable);
|
|
227
|
-
const [token, setToken] = useState("");
|
|
228
|
-
const [fiat, setFiat] = useState("ngn");
|
|
229
|
-
const [amount, setAmount] = useState("");
|
|
230
|
-
const [busy, setBusy] = useState(false);
|
|
321
|
+
const { orders, reload: reloadOrders } = useMoneyOrders(10);
|
|
322
|
+
const [st, setSt] = useState(null);
|
|
231
323
|
const [error, setError] = useState(null);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
324
|
+
const [busy, setBusy] = useState(false);
|
|
325
|
+
const [email, setEmail] = useState("");
|
|
326
|
+
const [first, setFirst] = useState("");
|
|
327
|
+
const [last, setLast] = useState("");
|
|
328
|
+
const [choice, setChoice] = useState("");
|
|
329
|
+
const [banks, setBanks] = useState([]);
|
|
330
|
+
const [bankCode, setBankCode] = useState("");
|
|
331
|
+
const [accountNumber, setAccountNumber] = useState("");
|
|
332
|
+
const [changing, setChanging] = useState(false);
|
|
333
|
+
const load = useCallback(async () => {
|
|
239
334
|
try {
|
|
240
|
-
|
|
241
|
-
window.open(start.url, "_blank", "noopener");
|
|
242
|
-
await reload();
|
|
335
|
+
setSt(await theoo.me.money.bachs.status());
|
|
243
336
|
}
|
|
244
337
|
catch (e) {
|
|
245
338
|
setError(e instanceof Error ? e.message : String(e));
|
|
246
339
|
}
|
|
247
|
-
finally {
|
|
248
|
-
setBusy(false);
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
return (_jsxs("div", { style: { display: "grid", gap: 12 }, "data-theoo-buy": "", children: [_jsx("strong", { style: { fontSize: 15.5, letterSpacing: "-0.01em", whiteSpace: "nowrap" }, children: "Add money" }), _jsxs("div", { style: row, children: [_jsx("input", { "aria-label": "Amount to pay", inputMode: "decimal", placeholder: "20000", value: amount, onChange: (e) => setAmount(e.target.value), style: { ...s.input, fontFamily: t.mono, fontSize: 17 } }), _jsxs("select", { "aria-label": "Pay in", value: fiat, onChange: (e) => setFiat(e.target.value), style: select, children: [_jsx("option", { value: "ngn", children: "NGN" }), _jsx("option", { value: "usd", children: "USD" })] })] }), _jsx("select", { "aria-label": "Token to receive", value: token, onChange: (e) => setToken(e.target.value), style: { ...select, width: "100%" }, children: stables.map((b) => (_jsxs("option", { value: b.token.address, children: ["Receive ", b.token.symbol] }, b.token.address))) }), _jsx("p", { style: s.tiny, children: "MoonPay checks your identity and takes the payment, by card or bank transfer. The tokens arrive in this account: its address is fixed in the link, so nobody can change it." }), error ? (_jsx("div", { style: s.error, role: "alert", children: error })) : null, _jsx("button", { type: "button", style: { ...s.button, opacity: busy || !token ? 0.6 : 1 }, disabled: busy || !token, onClick: () => void go(), children: busy ? "Opening MoonPay…" : "Continue to MoonPay" }), _jsx(Latest, { order: (orders ?? []).find((o) => o.kind === "onramp"), words: { awaiting_payment: "Waiting for your payment on MoonPay.", processing: "Paid. MoonPay is sending your tokens.", completed: "Arrived in your account.", failed: "MoonPay couldn't complete it; nothing was taken from this account." } })] }));
|
|
252
|
-
}
|
|
253
|
-
/** Cashing out to a Nigerian bank account through Yellow Card. */
|
|
254
|
-
function CashoutPanel({ chain, sender, onDone }) {
|
|
255
|
-
const theoo = useTheoo();
|
|
256
|
-
const { balances, reload } = useBalances(chain);
|
|
257
|
-
const { orders, reload: reloadOrders } = useMoneyOrders(10);
|
|
258
|
-
const [banks, setBanks] = useState(null);
|
|
259
|
-
const [bank, setBank] = useState("");
|
|
260
|
-
const [number, setNumber] = useState("");
|
|
261
|
-
const [name, setName] = useState(null);
|
|
262
|
-
const [confirmed, setConfirmed] = useState(false);
|
|
263
|
-
const [token, setToken] = useState("");
|
|
264
|
-
const [amount, setAmount] = useState("");
|
|
265
|
-
const [quote, setQuote] = useState(null);
|
|
266
|
-
const [busy, setBusy] = useState(false);
|
|
267
|
-
const [error, setError] = useState(null);
|
|
268
|
-
const stables = (balances?.balances ?? []).filter((b) => b.token.stable);
|
|
269
|
-
const chosen = stables.find((b) => b.token.address === token);
|
|
270
|
-
useEffect(() => {
|
|
271
|
-
theoo.money.banks().then(setBanks, (e) => setError(e instanceof Error ? e.message : String(e)));
|
|
272
340
|
}, [theoo]);
|
|
273
341
|
useEffect(() => {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
342
|
+
void load();
|
|
343
|
+
}, [load]);
|
|
344
|
+
// While Bachs is still deciding, or money is moving, keep it current.
|
|
345
|
+
const waiting = !!st?.linked && (mode === "buy" ? !st.depositsActive : !st.payoutsActive);
|
|
346
|
+
const moving = (orders ?? []).some((o) => (o.kind === "onramp" || o.kind === "offramp") && ["received", "processing", "awaiting_payout"].includes(o.state));
|
|
277
347
|
useEffect(() => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
348
|
+
if (!waiting && !moving)
|
|
349
|
+
return;
|
|
350
|
+
const id = setInterval(() => {
|
|
351
|
+
void load();
|
|
352
|
+
void reloadOrders();
|
|
353
|
+
}, 8000);
|
|
354
|
+
return () => clearInterval(id);
|
|
355
|
+
}, [waiting, moving, load, reloadOrders]);
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
if (mode === "cashout" && st?.payoutsActive && banks.length === 0)
|
|
358
|
+
theoo.money.bachsBanks().then(setBanks, () => undefined);
|
|
359
|
+
}, [mode, st?.payoutsActive, banks.length, theoo]);
|
|
282
360
|
const act = async (f) => {
|
|
283
361
|
setBusy(true);
|
|
284
362
|
setError(null);
|
|
@@ -292,25 +370,45 @@ function CashoutPanel({ chain, sender, onDone }) {
|
|
|
292
370
|
setBusy(false);
|
|
293
371
|
}
|
|
294
372
|
};
|
|
295
|
-
|
|
296
|
-
|
|
373
|
+
const verify = () => act(async () => {
|
|
374
|
+
const { url } = await theoo.me.money.bachs.onboard({ email, firstName: first || undefined, lastName: last || undefined, returnUrl: window.location.href.split("#")[0] });
|
|
375
|
+
window.open(url, "_blank", "noopener");
|
|
376
|
+
await load();
|
|
377
|
+
});
|
|
378
|
+
const errorBox = error ? (_jsx("div", { style: s.error, role: "alert", children: error })) : null;
|
|
379
|
+
if (!st)
|
|
380
|
+
return _jsx("p", { style: s.muted, children: "Loading\u2026" });
|
|
381
|
+
// Either the server has no Bachs key, or this project hasn't turned the flow on.
|
|
382
|
+
if (!st.available || (mode === "buy" ? !st.buyEnabled : !st.cashoutEnabled))
|
|
383
|
+
return _jsx("p", { style: s.muted, children: "Not available yet." });
|
|
384
|
+
const head = _jsx("strong", { style: { fontSize: 15.5, letterSpacing: "-0.01em" }, children: mode === "buy" ? "Add money" : "Cash out" });
|
|
385
|
+
if (!st.linked) {
|
|
386
|
+
return (
|
|
387
|
+
// Bachs opens an account in the user's name and checks their ID and BVN on its own page.
|
|
388
|
+
_jsxs("div", { style: { display: "grid", gap: 10 }, "data-theoo-bachs": "unlinked", children: [head, _jsx("label", { htmlFor: "theoo-bachs-email", style: label, children: "Email" }), _jsx("input", { id: "theoo-bachs-email", type: "email", autoComplete: "email", value: email, onChange: (e) => setEmail(e.target.value), style: s.input }), _jsxs("div", { style: row, children: [_jsx("input", { "aria-label": "First name", placeholder: "First name", autoComplete: "given-name", value: first, onChange: (e) => setFirst(e.target.value), style: s.input }), _jsx("input", { "aria-label": "Last name", placeholder: "Last name", autoComplete: "family-name", value: last, onChange: (e) => setLast(e.target.value), style: s.input })] }), errorBox, _jsx("button", { type: "button", style: { ...s.button, opacity: busy || !email.includes("@") ? 0.6 : 1 }, disabled: busy || !email.includes("@"), onClick: () => void verify(), children: busy ? "Opening…" : "Verify identity" })] }));
|
|
297
389
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
units = chosen && amount.trim() ? parseUnits(amount, chosen.token.decimals) : null;
|
|
390
|
+
if (waiting) {
|
|
391
|
+
return (_jsxs("div", { style: { display: "grid", gap: 10 }, "data-theoo-bachs": "verifying", children: [head, _jsx("p", { style: s.muted, children: "Verification in progress." }), errorBox, _jsx("button", { type: "button", style: s.secondary, disabled: busy, onClick: () => void verify(), children: "Continue verification" })] }));
|
|
301
392
|
}
|
|
302
|
-
|
|
303
|
-
|
|
393
|
+
const mine = (orders ?? []).filter((o) => o.kind === (mode === "buy" ? "onramp" : "offramp")).slice(0, 4);
|
|
394
|
+
const history = mine.length ? (_jsx("div", { style: { display: "grid", gap: 6, borderTop: `1px solid ${t.line}`, paddingTop: 10 }, children: mine.map((o) => (_jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: 8, fontSize: 13 }, "data-bachs-order": o.state, children: [_jsxs("span", { children: [o.pay ? `${o.pay.display} ${o.pay.symbol}` : "", o.get ? ` → ${o.get.display} ${o.get.symbol}` : ""] }), _jsx("span", { style: s.tiny, children: BACHS_WORDS[o.state] ?? o.state })] }, o.id))) })) : null;
|
|
395
|
+
if (mode === "buy") {
|
|
396
|
+
if (!st.receive || changing) {
|
|
397
|
+
const options = st.receiveOptions;
|
|
398
|
+
const selected = choice || (options[0] ? `${options[0].chain}:${options[0].asset}` : "");
|
|
399
|
+
return (_jsxs("div", { style: { display: "grid", gap: 10 }, "data-theoo-bachs": "choose", children: [head, _jsx("label", { htmlFor: "theoo-bachs-receive", style: label, children: "Receive as" }), options.length === 0 ? (_jsx("p", { style: s.muted, children: "Not available yet." })) : (_jsx("select", { id: "theoo-bachs-receive", value: selected, onChange: (e) => setChoice(e.target.value), style: { ...select, width: "100%" }, children: options.map((o) => (_jsxs("option", { value: `${o.chain}:${o.asset}`, children: [o.asset, " on ", chainLabel(o.chain)] }, `${o.chain}:${o.asset}`))) })), errorBox, _jsx("button", { type: "button", style: { ...s.button, opacity: busy || !selected ? 0.6 : 1 }, disabled: busy || !selected, onClick: () => void act(async () => {
|
|
400
|
+
const [c, a] = selected.split(":");
|
|
401
|
+
setSt(await theoo.me.money.bachs.setReceive(Number(c), a));
|
|
402
|
+
setChanging(false);
|
|
403
|
+
}), children: busy ? "Saving…" : "Continue" })] }));
|
|
404
|
+
}
|
|
405
|
+
return (_jsxs("div", { style: { display: "grid", gap: 10 }, "data-theoo-bachs": "ready", children: [head, st.nairaAccount ? (_jsxs("div", { style: { display: "grid", gap: 4, padding: 12, borderRadius: t.radius, background: t.accentSoft }, children: [_jsx("span", { style: label, children: "Send naira to" }), _jsx("strong", { style: { fontFamily: t.mono, fontSize: 20, letterSpacing: "0.04em" }, "data-bachs-account-number": "", children: st.nairaAccount.accountNumber }), _jsx("span", { style: { fontSize: 13.5 }, children: st.nairaAccount.bankName })] })) : (_jsx("p", { style: s.muted, children: "Setting up your account number\u2026" })), _jsxs("p", { style: s.tiny, children: ["Arrives as ", st.receive.asset, " on ", chainLabel(st.receive.chain), ". Fees apply.", " ", _jsx("button", { type: "button", style: link, onClick: () => setChanging(true), children: "Change" })] }), errorBox, history] }));
|
|
304
406
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
onDone?.(status);
|
|
311
|
-
await reloadOrders();
|
|
312
|
-
[0, 2000, 5000].forEach((ms) => setTimeout(() => void reload(), ms));
|
|
313
|
-
}), children: busy ? "Sending…" : "Send with passkey" })), _jsx(Latest, { order: (orders ?? []).find((o) => o.kind === "offramp"), words: { awaiting_signature: "Waiting for your passkey.", processing: "Sent. Yellow Card is paying the bank.", completed: "Paid into the bank account.", refunded: "The payout failed; your tokens came back to this account.", failed: "The transfer failed on chain; nothing was taken." } })] }));
|
|
407
|
+
// Cashing out.
|
|
408
|
+
return (_jsxs("div", { style: { display: "grid", gap: 10 }, "data-theoo-bachs": "cashout", children: [head, st.bank && !changing ? (_jsxs("div", { style: { display: "grid", gap: 4, padding: 12, borderRadius: t.radius, background: t.accentSoft }, children: [_jsx("span", { style: label, children: "Paid to" }), _jsx("strong", { style: { fontSize: 15 }, "data-bachs-account-name": "", children: st.bank.accountName }), _jsxs("span", { style: { fontSize: 13.5 }, children: [st.bank.bankName, " \u2022\u2022", st.bank.last4] }), _jsx("button", { type: "button", style: { ...link, justifySelf: "start" }, onClick: () => setChanging(true), children: "Change" })] })) : (_jsxs(_Fragment, { children: [_jsx("label", { htmlFor: "theoo-bachs-bank", style: label, children: "Your bank" }), _jsxs("select", { id: "theoo-bachs-bank", value: bankCode, onChange: (e) => setBankCode(e.target.value), style: { ...select, width: "100%" }, children: [_jsx("option", { value: "", children: "Choose your bank" }), banks.map((b) => (_jsx("option", { value: b.code, children: b.name }, b.code)))] }), _jsx("label", { htmlFor: "theoo-bachs-account", style: label, children: "Account number" }), _jsx("input", { id: "theoo-bachs-account", inputMode: "numeric", autoComplete: "off", maxLength: 10, value: accountNumber, onChange: (e) => setAccountNumber(e.target.value.replace(/\D/g, "")), style: { ...s.input, fontFamily: t.mono } }), _jsx("p", { style: s.tiny, children: "Must be in your name." }), _jsx("button", { type: "button", style: { ...s.button, opacity: busy || !bankCode || accountNumber.length !== 10 ? 0.6 : 1 }, disabled: busy || !bankCode || accountNumber.length !== 10, onClick: () => void act(async () => {
|
|
409
|
+
setSt(await theoo.me.money.bachs.setBank(bankCode, accountNumber));
|
|
410
|
+
setChanging(false);
|
|
411
|
+
}), children: busy ? "Checking…" : "Save" })] })), !st.cashoutPayInReady ? _jsx("p", { style: s.muted, children: "Coming soon." }) : null, errorBox, history] }));
|
|
314
412
|
}
|
|
315
413
|
function Recent({ orders, explorerTx }) {
|
|
316
414
|
return (_jsxs("div", { style: { display: "grid", gap: 6, borderTop: `1px solid ${t.line}`, paddingTop: 10 }, children: [_jsx("span", { style: label, children: "Recent" }), orders.map((o) => {
|