small details
This commit is contained in:
parent
61ae2807de
commit
86c92a7c65
11 changed files with 328 additions and 184 deletions
|
|
@ -224,168 +224,212 @@ export default function AdminPricingPage() {
|
|||
isLoading={isLoading}
|
||||
isAuthorized={isAuthorized}
|
||||
error={displayError}
|
||||
contentStyle={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "center",
|
||||
padding: "2rem",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
<div style={cardStyles.card}>
|
||||
<h1 style={cardStyles.title}>{t("pricing.title")}</h1>
|
||||
<p style={cardStyles.subtitle}>{t("pricing.subtitle")}</p>
|
||||
<div style={{ ...cardStyles.card, width: "100%", maxWidth: "1400px" }}>
|
||||
<h1 style={cardStyles.cardTitle}>{t("pricing.title")}</h1>
|
||||
<p style={cardStyles.cardSubtitle}>{t("pricing.subtitle")}</p>
|
||||
|
||||
{success && <div style={bannerStyles.success}>{t("pricing.success")}</div>}
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
setIsConfirming(true);
|
||||
}}
|
||||
>
|
||||
{/* Premium Settings - Full Width */}
|
||||
<div style={formStyles.section}>
|
||||
<h2 style={formStyles.sectionTitle}>{t("pricing.premiumSettings")}</h2>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(4, minmax(200px, 1fr))",
|
||||
gap: "1.5rem",
|
||||
alignItems: "start",
|
||||
}}
|
||||
>
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.premiumBuy")} (%)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.premium_buy}
|
||||
onChange={(e) => handleFieldChange("premium_buy", e.target.value)}
|
||||
min={-100}
|
||||
max={100}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
width: "100%",
|
||||
maxWidth: "180px",
|
||||
...(errors.premium_buy ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.premium_buy && <div style={formStyles.error}>{errors.premium_buy}</div>}
|
||||
</div>
|
||||
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.premiumBuy")} (%)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.premium_buy}
|
||||
onChange={(e) => handleFieldChange("premium_buy", e.target.value)}
|
||||
min={-100}
|
||||
max={100}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
...(errors.premium_buy ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.premium_buy && <div style={formStyles.error}>{errors.premium_buy}</div>}
|
||||
</div>
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.premiumSell")} (%)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.premium_sell}
|
||||
onChange={(e) => handleFieldChange("premium_sell", e.target.value)}
|
||||
min={-100}
|
||||
max={100}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
width: "100%",
|
||||
maxWidth: "180px",
|
||||
...(errors.premium_sell ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.premium_sell && <div style={formStyles.error}>{errors.premium_sell}</div>}
|
||||
</div>
|
||||
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.premiumSell")} (%)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.premium_sell}
|
||||
onChange={(e) => handleFieldChange("premium_sell", e.target.value)}
|
||||
min={-100}
|
||||
max={100}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
...(errors.premium_sell ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.premium_sell && <div style={formStyles.error}>{errors.premium_sell}</div>}
|
||||
</div>
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.smallTradeThreshold")} (EUR)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.small_trade_threshold_eur / 100}
|
||||
onChange={(e) =>
|
||||
handleFieldChange(
|
||||
"small_trade_threshold_eur",
|
||||
(parseFloat(e.target.value) * 100).toString()
|
||||
)
|
||||
}
|
||||
min={1}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
width: "100%",
|
||||
maxWidth: "180px",
|
||||
...(errors.small_trade_threshold_eur ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.small_trade_threshold_eur && (
|
||||
<div style={formStyles.error}>{errors.small_trade_threshold_eur}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.smallTradeThreshold")} (EUR)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.small_trade_threshold_eur / 100}
|
||||
onChange={(e) =>
|
||||
handleFieldChange(
|
||||
"small_trade_threshold_eur",
|
||||
(parseFloat(e.target.value) * 100).toString()
|
||||
)
|
||||
}
|
||||
min={1}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
...(errors.small_trade_threshold_eur ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.small_trade_threshold_eur && (
|
||||
<div style={formStyles.error}>{errors.small_trade_threshold_eur}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.smallTradeExtraPremium")} (%)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.small_trade_extra_premium}
|
||||
onChange={(e) => handleFieldChange("small_trade_extra_premium", e.target.value)}
|
||||
min={-100}
|
||||
max={100}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
...(errors.small_trade_extra_premium ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.small_trade_extra_premium && (
|
||||
<div style={formStyles.error}>{errors.small_trade_extra_premium}</div>
|
||||
)}
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.smallTradeExtraPremium")} (%)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.small_trade_extra_premium}
|
||||
onChange={(e) => handleFieldChange("small_trade_extra_premium", e.target.value)}
|
||||
min={-100}
|
||||
max={100}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
width: "100%",
|
||||
maxWidth: "180px",
|
||||
...(errors.small_trade_extra_premium ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.small_trade_extra_premium && (
|
||||
<div style={formStyles.error}>{errors.small_trade_extra_premium}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={formStyles.section}>
|
||||
<h2 style={formStyles.sectionTitle}>{t("pricing.tradeLimitsBuy")}</h2>
|
||||
{/* Trade Limits - Side by Side */}
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(2, minmax(300px, 1fr))",
|
||||
gap: "2rem",
|
||||
marginBottom: "2rem",
|
||||
alignItems: "start",
|
||||
}}
|
||||
>
|
||||
<div style={{ ...formStyles.section, marginBottom: 0 }}>
|
||||
<h2 style={formStyles.sectionTitle}>{t("pricing.tradeLimitsBuy")}</h2>
|
||||
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.minAmount")} (EUR)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.eur_min_buy / 100}
|
||||
onChange={(e) =>
|
||||
handleFieldChange("eur_min_buy", (parseFloat(e.target.value) * 100).toString())
|
||||
}
|
||||
min={1}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
...(errors.eur_min_buy ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.eur_min_buy && <div style={formStyles.error}>{errors.eur_min_buy}</div>}
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.minAmount")} (EUR)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.eur_min_buy / 100}
|
||||
onChange={(e) =>
|
||||
handleFieldChange("eur_min_buy", (parseFloat(e.target.value) * 100).toString())
|
||||
}
|
||||
min={1}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
width: "100%",
|
||||
maxWidth: "180px",
|
||||
...(errors.eur_min_buy ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.eur_min_buy && <div style={formStyles.error}>{errors.eur_min_buy}</div>}
|
||||
</div>
|
||||
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.maxAmount")} (EUR)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.eur_max_buy / 100}
|
||||
onChange={(e) =>
|
||||
handleFieldChange("eur_max_buy", (parseFloat(e.target.value) * 100).toString())
|
||||
}
|
||||
min={1}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
width: "100%",
|
||||
maxWidth: "180px",
|
||||
...(errors.eur_max_buy ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.eur_max_buy && <div style={formStyles.error}>{errors.eur_max_buy}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.maxAmount")} (EUR)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.eur_max_buy / 100}
|
||||
onChange={(e) =>
|
||||
handleFieldChange("eur_max_buy", (parseFloat(e.target.value) * 100).toString())
|
||||
}
|
||||
min={1}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
...(errors.eur_max_buy ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.eur_max_buy && <div style={formStyles.error}>{errors.eur_max_buy}</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ ...formStyles.section, marginBottom: 0 }}>
|
||||
<h2 style={formStyles.sectionTitle}>{t("pricing.tradeLimitsSell")}</h2>
|
||||
|
||||
<div style={formStyles.section}>
|
||||
<h2 style={formStyles.sectionTitle}>{t("pricing.tradeLimitsSell")}</h2>
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.minAmount")} (EUR)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.eur_min_sell / 100}
|
||||
onChange={(e) =>
|
||||
handleFieldChange("eur_min_sell", (parseFloat(e.target.value) * 100).toString())
|
||||
}
|
||||
min={1}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
width: "100%",
|
||||
maxWidth: "180px",
|
||||
...(errors.eur_min_sell ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.eur_min_sell && <div style={formStyles.error}>{errors.eur_min_sell}</div>}
|
||||
</div>
|
||||
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.minAmount")} (EUR)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.eur_min_sell / 100}
|
||||
onChange={(e) =>
|
||||
handleFieldChange("eur_min_sell", (parseFloat(e.target.value) * 100).toString())
|
||||
}
|
||||
min={1}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
...(errors.eur_min_sell ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.eur_min_sell && <div style={formStyles.error}>{errors.eur_min_sell}</div>}
|
||||
</div>
|
||||
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.maxAmount")} (EUR)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.eur_max_sell / 100}
|
||||
onChange={(e) =>
|
||||
handleFieldChange("eur_max_sell", (parseFloat(e.target.value) * 100).toString())
|
||||
}
|
||||
min={1}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
...(errors.eur_max_sell ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.eur_max_sell && <div style={formStyles.error}>{errors.eur_max_sell}</div>}
|
||||
<div style={formStyles.field}>
|
||||
<label style={formStyles.label}>{t("pricing.maxAmount")} (EUR)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.eur_max_sell / 100}
|
||||
onChange={(e) =>
|
||||
handleFieldChange("eur_max_sell", (parseFloat(e.target.value) * 100).toString())
|
||||
}
|
||||
min={1}
|
||||
style={{
|
||||
...formStyles.input,
|
||||
width: "100%",
|
||||
maxWidth: "180px",
|
||||
...(errors.eur_max_sell ? formStyles.inputError : {}),
|
||||
}}
|
||||
/>
|
||||
{errors.eur_max_sell && <div style={formStyles.error}>{errors.eur_max_sell}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -345,36 +345,43 @@ export function ExchangeDetailsStep({
|
|||
</div>
|
||||
|
||||
{/* Trade Summary */}
|
||||
<div style={styles.tradeSummary}>
|
||||
<div style={styles.tradeSummary} data-testid="trade-summary">
|
||||
{direction === "buy" ? (
|
||||
<p style={styles.summaryText}>
|
||||
{t("detailsStep.summaryBuy", { sats: "", eur: "" }).split("{sats}")[0].trim()}{" "}
|
||||
<strong style={styles.satsValue}>
|
||||
<SatsDisplay sats={satsAmount} />
|
||||
</strong>
|
||||
{", "}
|
||||
{t("detailsStep.summaryBuy", { sats: "", eur: "" })
|
||||
.split("{sats}")[1]
|
||||
?.split("{eur}")[0]
|
||||
?.trim()}{" "}
|
||||
<strong>{formatEur(eurAmount)}</strong>
|
||||
{t("detailsStep.summaryBuy", {
|
||||
eur: formatEur(eurAmount),
|
||||
sats: "",
|
||||
})
|
||||
.replace("{eur}", formatEur(eurAmount))
|
||||
.split("{sats}")
|
||||
.map((part, i) => (
|
||||
<span key={i}>
|
||||
{part}
|
||||
{i === 0 && (
|
||||
<strong style={styles.satsValue}>
|
||||
<SatsDisplay sats={satsAmount} />
|
||||
</strong>
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
) : (
|
||||
<p style={styles.summaryText}>
|
||||
{t("detailsStep.summarySell", { sats: "", eur: "" })
|
||||
.split("{sats}")[0]
|
||||
?.split("{eur}")[0]
|
||||
?.trim()}{" "}
|
||||
<strong>{formatEur(eurAmount)}</strong>
|
||||
{", "}
|
||||
{t("detailsStep.summarySell", { sats: "", eur: "" })
|
||||
.split("{sats}")[0]
|
||||
?.split("{eur}")[1]
|
||||
?.trim()}{" "}
|
||||
<strong style={styles.satsValue}>
|
||||
<SatsDisplay sats={satsAmount} />
|
||||
</strong>
|
||||
{t("detailsStep.summarySell", { sats: "", eur: "" }).split("{sats}")[1]?.trim()}
|
||||
{t("detailsStep.summarySell", {
|
||||
sats: "",
|
||||
eur: formatEur(eurAmount),
|
||||
})
|
||||
.split("{sats}")
|
||||
.map((part, i) => (
|
||||
<span key={i}>
|
||||
{i === 0 && (
|
||||
<strong style={styles.satsValue}>
|
||||
<SatsDisplay sats={satsAmount} />
|
||||
</strong>
|
||||
)}
|
||||
{part.replace("{eur}", formatEur(eurAmount))}
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -439,6 +439,30 @@ export const formStyles: StyleRecord = {
|
|||
fontSize: "0.75rem",
|
||||
color: "#fca5a5",
|
||||
},
|
||||
error: {
|
||||
fontFamily: tokens.fontSans,
|
||||
fontSize: "0.75rem",
|
||||
color: "#fca5a5",
|
||||
marginTop: "0.25rem",
|
||||
},
|
||||
section: {
|
||||
marginBottom: "2rem",
|
||||
},
|
||||
sectionTitle: {
|
||||
fontFamily: tokens.fontSans,
|
||||
fontSize: "1rem",
|
||||
fontWeight: 600,
|
||||
color: tokens.textSecondary,
|
||||
marginBottom: "1.25rem",
|
||||
marginTop: 0,
|
||||
},
|
||||
actions: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: "2rem",
|
||||
paddingTop: "1.5rem",
|
||||
borderTop: `1px solid ${tokens.surfaceBorder}`,
|
||||
},
|
||||
charCount: {
|
||||
fontFamily: tokens.fontSans,
|
||||
fontSize: "0.75rem",
|
||||
|
|
@ -511,6 +535,26 @@ export const buttonStyles: StyleRecord = {
|
|||
cursor: "not-allowed",
|
||||
boxShadow: "none",
|
||||
},
|
||||
/** Primary button alias (for backwards compatibility) */
|
||||
primary: {
|
||||
fontFamily: tokens.fontSans,
|
||||
padding: "1rem",
|
||||
fontSize: "1rem",
|
||||
fontWeight: 600,
|
||||
background: tokens.primaryGradient,
|
||||
color: tokens.white,
|
||||
border: "none",
|
||||
borderRadius: tokens.radiusLg,
|
||||
cursor: "pointer",
|
||||
transition: "transform 0.2s, box-shadow 0.2s",
|
||||
boxShadow: tokens.buttonShadow,
|
||||
},
|
||||
/** Disabled button alias (for backwards compatibility) */
|
||||
disabled: {
|
||||
opacity: 0.5,
|
||||
cursor: "not-allowed",
|
||||
boxShadow: "none",
|
||||
},
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -575,6 +619,17 @@ export const bannerStyles: StyleRecord = {
|
|||
color: "#4ade80",
|
||||
marginBottom: "1rem",
|
||||
},
|
||||
/** Success banner alias (for backwards compatibility) */
|
||||
success: {
|
||||
fontFamily: tokens.fontSans,
|
||||
fontSize: "0.875rem",
|
||||
padding: "1rem",
|
||||
background: "rgba(34, 197, 94, 0.15)",
|
||||
border: `1px solid ${tokens.successBorder}`,
|
||||
borderRadius: tokens.radiusMd,
|
||||
color: "#4ade80",
|
||||
marginBottom: "1rem",
|
||||
},
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue