From 86c92a7c656c6a5cd41948dbdf6dba4655533afa Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 26 Dec 2025 23:27:33 +0100 Subject: [PATCH] small details --- frontend/app/admin/pricing/page.tsx | 324 ++++++++++-------- .../components/ExchangeDetailsStep.tsx | 57 +-- frontend/app/styles/shared.ts | 55 +++ frontend/e2e/exchange.spec.ts | 38 ++ frontend/locales/ca/admin.json | 8 +- frontend/locales/ca/exchange.json | 4 +- frontend/locales/en/admin.json | 8 +- frontend/locales/en/exchange.json | 4 +- frontend/locales/es/admin.json | 8 +- frontend/locales/es/exchange.json | 4 +- shared/constants.json | 2 +- 11 files changed, 328 insertions(+), 184 deletions(-) diff --git a/frontend/app/admin/pricing/page.tsx b/frontend/app/admin/pricing/page.tsx index 3018b48..e26d3b8 100644 --- a/frontend/app/admin/pricing/page.tsx +++ b/frontend/app/admin/pricing/page.tsx @@ -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", + }} > -
-

{t("pricing.title")}

-

{t("pricing.subtitle")}

+
+

{t("pricing.title")}

+

{t("pricing.subtitle")}

{success &&
{t("pricing.success")}
}
{ e.preventDefault(); - handleSave(); + setIsConfirming(true); }} > + {/* Premium Settings - Full Width */}

{t("pricing.premiumSettings")}

+
+
+ + 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 &&
{errors.premium_buy}
} +
-
- - handleFieldChange("premium_buy", e.target.value)} - min={-100} - max={100} - style={{ - ...formStyles.input, - ...(errors.premium_buy ? formStyles.inputError : {}), - }} - /> - {errors.premium_buy &&
{errors.premium_buy}
} -
+
+ + 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 &&
{errors.premium_sell}
} +
-
- - handleFieldChange("premium_sell", e.target.value)} - min={-100} - max={100} - style={{ - ...formStyles.input, - ...(errors.premium_sell ? formStyles.inputError : {}), - }} - /> - {errors.premium_sell &&
{errors.premium_sell}
} -
+
+ + + 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 && ( +
{errors.small_trade_threshold_eur}
+ )} +
-
- - - 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 && ( -
{errors.small_trade_threshold_eur}
- )} -
- -
- - 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 && ( -
{errors.small_trade_extra_premium}
- )} +
+ + 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 && ( +
{errors.small_trade_extra_premium}
+ )} +
-
-

{t("pricing.tradeLimitsBuy")}

+ {/* Trade Limits - Side by Side */} +
+
+

{t("pricing.tradeLimitsBuy")}

-
- - - 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 &&
{errors.eur_min_buy}
} +
+ + + 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 &&
{errors.eur_min_buy}
} +
+ +
+ + + 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 &&
{errors.eur_max_buy}
} +
-
- - - 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 &&
{errors.eur_max_buy}
} -
-
+
+

{t("pricing.tradeLimitsSell")}

-
-

{t("pricing.tradeLimitsSell")}

+
+ + + 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 &&
{errors.eur_min_sell}
} +
-
- - - 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 &&
{errors.eur_min_sell}
} -
- -
- - - 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 &&
{errors.eur_max_sell}
} +
+ + + 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 &&
{errors.eur_max_sell}
} +
diff --git a/frontend/app/exchange/components/ExchangeDetailsStep.tsx b/frontend/app/exchange/components/ExchangeDetailsStep.tsx index 8659940..7190b3a 100644 --- a/frontend/app/exchange/components/ExchangeDetailsStep.tsx +++ b/frontend/app/exchange/components/ExchangeDetailsStep.tsx @@ -345,36 +345,43 @@ export function ExchangeDetailsStep({
{/* Trade Summary */} -
+
{direction === "buy" ? (

- {t("detailsStep.summaryBuy", { sats: "", eur: "" }).split("{sats}")[0].trim()}{" "} - - - - {", "} - {t("detailsStep.summaryBuy", { sats: "", eur: "" }) - .split("{sats}")[1] - ?.split("{eur}")[0] - ?.trim()}{" "} - {formatEur(eurAmount)} + {t("detailsStep.summaryBuy", { + eur: formatEur(eurAmount), + sats: "", + }) + .replace("{eur}", formatEur(eurAmount)) + .split("{sats}") + .map((part, i) => ( + + {part} + {i === 0 && ( + + + + )} + + ))}

) : (

- {t("detailsStep.summarySell", { sats: "", eur: "" }) - .split("{sats}")[0] - ?.split("{eur}")[0] - ?.trim()}{" "} - {formatEur(eurAmount)} - {", "} - {t("detailsStep.summarySell", { sats: "", eur: "" }) - .split("{sats}")[0] - ?.split("{eur}")[1] - ?.trim()}{" "} - - - - {t("detailsStep.summarySell", { sats: "", eur: "" }).split("{sats}")[1]?.trim()} + {t("detailsStep.summarySell", { + sats: "", + eur: formatEur(eurAmount), + }) + .split("{sats}") + .map((part, i) => ( + + {i === 0 && ( + + + + )} + {part.replace("{eur}", formatEur(eurAmount))} + + ))}

)}
diff --git a/frontend/app/styles/shared.ts b/frontend/app/styles/shared.ts index 6ad0ccc..194b512 100644 --- a/frontend/app/styles/shared.ts +++ b/frontend/app/styles/shared.ts @@ -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", + }, }; // ============================================================================= diff --git a/frontend/e2e/exchange.spec.ts b/frontend/e2e/exchange.spec.ts index 26cafda..5ead442 100644 --- a/frontend/e2e/exchange.spec.ts +++ b/frontend/e2e/exchange.spec.ts @@ -148,6 +148,44 @@ test.describe("Exchange Page - Regular User Access", () => { timeout: 10000, }); }); + + test("summary card displays correct text for buy and sell directions", async ({ page }) => { + await page.goto("/exchange"); + await expect(page.getByRole("heading", { name: "Exchange Bitcoin" })).toBeVisible(); + + // Wait for price data to load + await expect(page.getByText("Market:")).toBeVisible({ timeout: 5000 }); + + // Test BUY direction summary + const buyButton = page.getByRole("button", { name: "Buy BTC" }); + await buyButton.click(); + + // Wait for summary to update + await page.waitForTimeout(1000); + + // Check that summary contains "You pay" and "you receive" with EUR amount and sats + const summaryElement = page.getByTestId("trade-summary"); + await expect(summaryElement).toBeVisible({ timeout: 5000 }); + const summaryText = await summaryElement.textContent(); + expect(summaryText).toContain("You pay"); + expect(summaryText).toContain("you receive"); + expect(summaryText).toMatch(/€\s*\d+/); // Should contain EUR amount + expect(summaryText).toMatch(/\d+\s*sats/); // Should contain sats amount + + // Test SELL direction summary + const sellButton = page.getByRole("button", { name: "Sell BTC" }); + await sellButton.click(); + + // Wait for summary to update + await page.waitForTimeout(1000); + + // Check that summary contains "You pay" and "you receive" with sats and EUR amount + const summaryTextSell = await summaryElement.textContent(); + expect(summaryTextSell).toContain("You pay"); + expect(summaryTextSell).toContain("you receive"); + expect(summaryTextSell).toMatch(/€\s*\d+/); // Should contain EUR amount + expect(summaryTextSell).toMatch(/\d+\s*sats/); // Should contain sats amount + }); }); test.describe("Exchange Page - With Availability", () => { diff --git a/frontend/locales/ca/admin.json b/frontend/locales/ca/admin.json index 69a9f1f..136f186 100644 --- a/frontend/locales/ca/admin.json +++ b/frontend/locales/ca/admin.json @@ -114,12 +114,12 @@ "title": "Configuració de Preus", "subtitle": "Configura els preus de prima i els límits d'import de les operacions", "premiumSettings": "Configuració de Prima", - "premiumBuy": "Prima per COMPRAR", - "premiumSell": "Prima per VENDRE", + "premiumBuy": "Prima quan l'Usuari Compra BTC (Usuari paga EUR, rep BTC)", + "premiumSell": "Prima quan l'Usuari Vén BTC (Usuari paga BTC, rep EUR)", "smallTradeThreshold": "Umbral d'Operacions Petites", "smallTradeExtraPremium": "Prima Extra per Operacions Petites", - "tradeLimitsBuy": "Límits d'Import d'Operacions (COMPRAR)", - "tradeLimitsSell": "Límits d'Import d'Operacions (VENDRE)", + "tradeLimitsBuy": "Límits d'Import en Comprar BTC (EUR pagat per l'usuari)", + "tradeLimitsSell": "Límits d'Import en Vendre BTC (EUR rebut per l'usuari)", "minAmount": "Import Mínim", "maxAmount": "Import Màxim", "save": "Guardar Canvis", diff --git a/frontend/locales/ca/exchange.json b/frontend/locales/ca/exchange.json index 01d275d..c4c69bf 100644 --- a/frontend/locales/ca/exchange.json +++ b/frontend/locales/ca/exchange.json @@ -31,8 +31,8 @@ "required": "*", "lightningThreshold": "Els pagaments Lightning només estan disponibles per importants fins a €{max}", "amount": "Quantitat (EUR)", - "summaryBuy": "Compres {sats}, vens {eur}", - "summarySell": "Compres {eur}, vens {sats}", + "summaryBuy": "Pagues {eur}, reps {sats}", + "summarySell": "Pagues {sats}, reps {eur}", "continueToBooking": "Continuar a reserva" }, "bookingStep": { diff --git a/frontend/locales/en/admin.json b/frontend/locales/en/admin.json index 548dc2e..3386345 100644 --- a/frontend/locales/en/admin.json +++ b/frontend/locales/en/admin.json @@ -114,12 +114,12 @@ "title": "Pricing Configuration", "subtitle": "Configure premium pricing and trade amount limits", "premiumSettings": "Premium Settings", - "premiumBuy": "Premium for BUY", - "premiumSell": "Premium for SELL", + "premiumBuy": "Premium when User Buys BTC (User pays EUR, receives BTC)", + "premiumSell": "Premium when User Sells BTC (User pays BTC, receives EUR)", "smallTradeThreshold": "Small Trade Threshold", "smallTradeExtraPremium": "Extra Premium for Small Trades", - "tradeLimitsBuy": "Trade Amount Limits (BUY)", - "tradeLimitsSell": "Trade Amount Limits (SELL)", + "tradeLimitsBuy": "Trade Amount Limits when Buying BTC (EUR paid by user)", + "tradeLimitsSell": "Trade Amount Limits when Selling BTC (EUR received by user)", "minAmount": "Minimum Amount", "maxAmount": "Maximum Amount", "save": "Save Changes", diff --git a/frontend/locales/en/exchange.json b/frontend/locales/en/exchange.json index 7e1d4ee..ff34f18 100644 --- a/frontend/locales/en/exchange.json +++ b/frontend/locales/en/exchange.json @@ -31,8 +31,8 @@ "required": "*", "lightningThreshold": "Lightning payments are only available for amounts up to €{max}", "amount": "Amount (EUR)", - "summaryBuy": "You buy {sats}, you sell {eur}", - "summarySell": "You buy {eur}, you sell {sats}", + "summaryBuy": "You pay {eur}, you receive {sats}", + "summarySell": "You pay {sats}, you receive {eur}", "continueToBooking": "Continue to Booking" }, "bookingStep": { diff --git a/frontend/locales/es/admin.json b/frontend/locales/es/admin.json index d82431d..9c2cf8d 100644 --- a/frontend/locales/es/admin.json +++ b/frontend/locales/es/admin.json @@ -114,12 +114,12 @@ "title": "Configuración de Precios", "subtitle": "Configura los precios de prima y los límites de importe de las operaciones", "premiumSettings": "Configuración de Prima", - "premiumBuy": "Prima para COMPRAR", - "premiumSell": "Prima para VENDER", + "premiumBuy": "Prima cuando el Usuario Compra BTC (Usuario paga EUR, recibe BTC)", + "premiumSell": "Prima cuando el Usuario Vende BTC (Usuario paga BTC, recibe EUR)", "smallTradeThreshold": "Umbral de Operaciones Pequeñas", "smallTradeExtraPremium": "Prima Extra para Operaciones Pequeñas", - "tradeLimitsBuy": "Límites de Importe de Operaciones (COMPRAR)", - "tradeLimitsSell": "Límites de Importe de Operaciones (VENDER)", + "tradeLimitsBuy": "Límites de Importe al Comprar BTC (EUR pagado por el usuario)", + "tradeLimitsSell": "Límites de Importe al Vender BTC (EUR recibido por el usuario)", "minAmount": "Importe Mínimo", "maxAmount": "Importe Máximo", "save": "Guardar Cambios", diff --git a/frontend/locales/es/exchange.json b/frontend/locales/es/exchange.json index ee5ad5e..0a171fb 100644 --- a/frontend/locales/es/exchange.json +++ b/frontend/locales/es/exchange.json @@ -31,8 +31,8 @@ "required": "*", "lightningThreshold": "Los pagos Lightning solo están disponibles para montos de hasta €{max}", "amount": "Cantidad (EUR)", - "summaryBuy": "Compras {sats}, vendes {eur}", - "summarySell": "Compras {eur}, vendes {sats}", + "summaryBuy": "Pagas {eur}, recibes {sats}", + "summarySell": "Pagas {sats}, recibes {eur}", "continueToBooking": "Continuar a reserva" }, "bookingStep": { diff --git a/shared/constants.json b/shared/constants.json index e930667..4d5e7b6 100644 --- a/shared/constants.json +++ b/shared/constants.json @@ -27,7 +27,7 @@ "slotDurationMinutes": 15, "maxAdvanceDays": 30, "minAdvanceDays": 1, - "eurTradeIncrement": 20, + "eurTradeIncrement": 5, "priceRefreshSeconds": 60, "priceStalenessSeconds": 300, "lightningMaxEur": 1000