Enhance ticker search and validation in "Convert to Trade" form (#688)

- Updated resolution logic to support combobox-based ticker selection and validation.
- Added market price display with validation against entered prices to detect significant mismatches.
- Improved messaging and UI for custom ticker input and market price warnings.

Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
This commit is contained in:
LPW
2026-01-17 16:46:15 -05:00
committed by GitHub
parent 47e0185409
commit 0f6dd536df
5 changed files with 184 additions and 32 deletions

View File

@@ -1,7 +1,11 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["customWrapper", "customField", "tickerSelect"]
static targets = ["customWrapper", "customField", "tickerSelect", "qtyField", "priceField", "priceWarning", "priceWarningMessage"]
static values = {
amountCents: Number,
currency: String
}
toggleCustomTicker(event) {
const value = event.target.value
@@ -18,4 +22,76 @@ export default class extends Controller {
this.customFieldTarget.value = ""
}
}
validatePrice() {
// Get the selected security's market price (in cents)
const selectedOption = this.tickerSelectTarget.selectedOptions[0]
if (!selectedOption || selectedOption.value === "" || selectedOption.value === "__custom__") {
this.hidePriceWarning()
return
}
const marketPriceCents = selectedOption.dataset.priceCents
const ticker = selectedOption.dataset.ticker
// If no market price data, can't validate
if (!marketPriceCents || marketPriceCents === "null") {
this.hidePriceWarning()
return
}
// Calculate the implied/entered price
let enteredPriceCents = null
const qty = Number.parseFloat(this.qtyFieldTarget?.value)
const enteredPrice = Number.parseFloat(this.priceFieldTarget?.value)
if (enteredPrice && enteredPrice > 0) {
// User entered a price directly
enteredPriceCents = enteredPrice * 100
} else if (qty && qty > 0 && this.amountCentsValue > 0) {
// Calculate price from amount / qty
enteredPriceCents = this.amountCentsValue / qty
}
if (!enteredPriceCents || enteredPriceCents <= 0) {
this.hidePriceWarning()
return
}
// Compare prices - warn if they differ by more than 50%
const marketPrice = Number.parseFloat(marketPriceCents)
const ratio = enteredPriceCents / marketPrice
if (ratio < 0.5 || ratio > 2.0) {
this.showPriceWarning(ticker, enteredPriceCents, marketPrice)
} else {
this.hidePriceWarning()
}
}
showPriceWarning(ticker, enteredPriceCents, marketPriceCents) {
if (!this.hasPriceWarningTarget) return
const enteredPrice = this.formatMoney(enteredPriceCents)
const marketPrice = this.formatMoney(marketPriceCents)
// Build warning message
const message = `Your price (${enteredPrice}/share) differs significantly from ${ticker}'s current market price (${marketPrice}). If this seems wrong, you may have selected the wrong security — try using "Enter custom ticker" to specify the correct one.`
this.priceWarningMessageTarget.textContent = message
this.priceWarningTarget.classList.remove("hidden")
}
hidePriceWarning() {
if (!this.hasPriceWarningTarget) return
this.priceWarningTarget.classList.add("hidden")
}
formatMoney(cents) {
const dollars = cents / 100
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: this.currencyValue || 'USD'
}).format(dollars)
}
}