@blocklet/payment-react
Version:
Reusable react components for payment kit v2
316 lines (315 loc) • 10.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
module.exports = SlippageConfig;
var _jsxRuntime = require("react/jsx-runtime");
var _react = require("react");
var _material = require("@mui/material");
var _context = require("@arcblock/ux/lib/Locale/context");
const PRESET_SLIPPAGE = [0.5, 1, 3, 5, 10];
function SlippageConfig({
value,
onChange,
config = void 0,
onConfigChange = void 0,
exchangeRate = null,
baseCurrency = "USD",
disabled = false,
sx = {},
onCancel = void 0,
onSave = void 0
}) {
const {
t
} = (0, _context.useLocaleContext)();
const [inputValue, setInputValue] = (0, _react.useState)("");
const [inputMode, setInputMode] = (0, _react.useState)(config?.mode || "percent");
const [error, setError] = (0, _react.useState)(null);
const [isEditing, setIsEditing] = (0, _react.useState)(false);
const percentValue = config?.percent ?? value;
const roundedRate = (0, _react.useMemo)(() => {
if (!exchangeRate) return null;
const rateNum = Number(exchangeRate);
if (Number.isNaN(rateNum) || rateNum <= 0) return null;
return Math.round(rateNum * 100) / 100;
}, [exchangeRate]);
const computeMinRateFromPercent = percent => {
if (!roundedRate) return "";
const slippageMultiplier = 1 + percent / 100;
return (roundedRate / slippageMultiplier).toFixed(2);
};
(0, _react.useEffect)(() => {
if (config?.mode && config.mode !== inputMode) {
setInputMode(config.mode);
}
}, [config?.mode, inputMode]);
(0, _react.useEffect)(() => {
if (isEditing) {
return;
}
if (inputMode === "percent") {
setInputValue(percentValue.toFixed(2));
return;
}
if (config?.min_acceptable_rate) {
setInputValue(String(config.min_acceptable_rate));
return;
}
const minRate = computeMinRateFromPercent(percentValue);
setInputValue(minRate);
}, [percentValue, inputMode, exchangeRate, config?.min_acceptable_rate, isEditing]);
const handlePresetClick = preset => {
if (disabled) return;
setInputValue(preset.toFixed(2));
setInputMode("percent");
setError(null);
onChange(preset);
const minRate = computeMinRateFromPercent(preset);
onConfigChange?.({
mode: "percent",
percent: preset,
...(minRate ? {
min_acceptable_rate: minRate
} : {})
});
};
const handleInputChange = newValue => {
setInputValue(newValue);
setError(null);
const numValue = Number(newValue);
if (!newValue || Number.isNaN(numValue)) {
setError(t("payment.checkout.quote.slippage.invalid"));
return;
}
if (numValue <= 0) {
setError(t("payment.checkout.quote.slippage.invalidPositive"));
return;
}
if (inputMode === "percent") {
onChange(numValue);
const minRate = computeMinRateFromPercent(numValue);
onConfigChange?.({
mode: "percent",
percent: numValue,
...(minRate ? {
min_acceptable_rate: minRate
} : {})
});
} else {
if (!roundedRate) {
setError(t("payment.checkout.quote.slippage.rateRequired"));
return;
}
const percent = (roundedRate - numValue) / numValue * 100;
onChange(Math.max(0, percent));
onConfigChange?.({
mode: "rate",
percent: Math.max(0, percent),
min_acceptable_rate: newValue
});
}
};
const handleModeChange = (_, newMode) => {
if (disabled || !newMode) return;
setInputMode(newMode);
setError(null);
if (newMode === "rate") {
if (!roundedRate) {
setError(t("payment.checkout.quote.slippage.rateRequired"));
return;
}
const minRate = config?.min_acceptable_rate || computeMinRateFromPercent(percentValue);
setInputValue(minRate);
onConfigChange?.({
mode: "rate",
percent: percentValue,
min_acceptable_rate: minRate
});
} else {
setInputValue(percentValue.toFixed(2));
const minRate = computeMinRateFromPercent(percentValue);
onConfigChange?.({
mode: "percent",
percent: percentValue,
...(minRate ? {
min_acceptable_rate: minRate
} : {})
});
}
};
const minAcceptableRate = (0, _react.useMemo)(() => {
if (!roundedRate) return null;
const slippageMultiplier = 1 + percentValue / 100;
return (roundedRate / slippageMultiplier).toFixed(2);
}, [roundedRate, percentValue]);
const currentRateLabel = (0, _react.useMemo)(() => {
if (!roundedRate) {
return "\u2014";
}
return roundedRate.toFixed(2);
}, [roundedRate]);
const handleCancel = () => {
onCancel?.();
};
const handleSave = () => {
onSave?.();
};
return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
spacing: 2.5,
sx,
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
variant: "body2",
color: "text.secondary",
children: t("payment.checkout.quote.slippageLimit.description")
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.ToggleButtonGroup, {
value: inputMode,
exclusive: true,
onChange: handleModeChange,
size: "small",
fullWidth: true,
disabled,
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ToggleButton, {
value: "percent",
children: t("payment.checkout.quote.slippageLimit.configTogglePercent")
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ToggleButton, {
value: "rate",
disabled: !roundedRate,
children: t("payment.checkout.quote.slippageLimit.configToggleRate")
})]
}), inputMode === "percent" && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
spacing: 1.5,
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
direction: "row",
spacing: 1,
children: PRESET_SLIPPAGE.map(preset => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ToggleButton, {
value: preset,
selected: Math.abs(percentValue - preset) < 0.01,
onClick: () => handlePresetClick(preset),
size: "small",
disabled,
sx: {
flex: 1,
py: 1,
borderRadius: 1,
border: "1px solid",
borderColor: "divider",
"&.Mui-selected": {
bgcolor: "primary.main",
color: "primary.contrastText",
borderColor: "primary.main",
"&:hover": {
bgcolor: "primary.dark"
}
}
},
children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
variant: "body2",
children: [preset, "%"]
})
}, preset))
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.TextField, {
size: "small",
fullWidth: true,
value: inputValue,
onChange: e => handleInputChange(e.target.value),
onFocus: () => setIsEditing(true),
onBlur: () => setIsEditing(false),
error: !!error,
helperText: error,
disabled,
label: t("common.custom"),
InputProps: {
endAdornment: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
variant: "body2",
sx: {
color: "text.secondary",
mr: 1
},
children: "%"
})
},
placeholder: "0.50"
})]
}), inputMode === "rate" && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
spacing: 1.5,
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.TextField, {
size: "small",
fullWidth: true,
value: inputValue,
onChange: e => handleInputChange(e.target.value),
onFocus: () => setIsEditing(true),
onBlur: () => setIsEditing(false),
error: !!error,
helperText: error,
disabled: disabled || !roundedRate,
label: t("payment.checkout.quote.slippage.rateInputLabel", {
currency: baseCurrency
}),
InputProps: {
endAdornment: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
variant: "body2",
sx: {
color: "text.secondary",
mr: 1
},
children: baseCurrency
})
},
placeholder: roundedRate?.toFixed(2) || "0.00"
})
}), roundedRate && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
sx: {
borderRadius: 1,
p: 1.5,
bgcolor: "action.hover"
},
children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
spacing: 0.5,
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
justifyContent: "space-between",
alignItems: "center",
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
variant: "body2",
color: "text.secondary",
children: t("payment.checkout.quote.slippageLimit.derivedCurrentRate")
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
variant: "body2",
fontWeight: 500,
children: [currentRateLabel, " ", baseCurrency]
})]
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
justifyContent: "space-between",
alignItems: "center",
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
variant: "body2",
color: "text.secondary",
children: t("payment.checkout.quote.slippageLimit.derivedMinRate")
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
variant: "body2",
fontWeight: 500,
color: "primary.main",
children: [inputMode === "rate" ? inputValue : minAcceptableRate || "\u2014", " ", baseCurrency]
})]
})]
})
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 1,
justifyContent: "flex-end",
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
onClick: handleCancel,
disabled,
color: "inherit",
children: t("common.cancel")
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
variant: "contained",
onClick: handleSave,
disabled: disabled || !!error,
children: t("common.save")
})]
})]
});
}