mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 15:59:02 +00:00
Stimulus converts the JS value name templateNonZero to a kebab-cased attribute by splitting on each capital letter, giving data-...-template-non-zero-value. Rails' dataset helper converts the Ruby key :goal_contribution_preview_template_nonzero_value to data-...-template-nonzero-value (no hyphen between non and zero). Result: the Stimulus controller resolved templateNonzeroValue to "" and the preview pane went blank as soon as the user typed an amount. Renaming the JS value to templateNonzero closes the conversion gap. Verified live via Playwright: at $500 the preview reads "Will bring you to 28% saved ($13,750 of $50,000)."; at $40,000 it flips to "Will reach your $50,000 target."
67 lines
2.0 KiB
JavaScript
67 lines
2.0 KiB
JavaScript
import { Controller } from "@hotwired/stimulus";
|
|
|
|
// Live impact preview for the add-contribution modal. Reads current
|
|
// balance + target amount from values and updates a preview sentence
|
|
// each keystroke. Template strings come from ERB so the wording stays
|
|
// localized.
|
|
export default class extends Controller {
|
|
static targets = ["amountInput", "preview"];
|
|
static values = {
|
|
currentBalance: Number,
|
|
targetAmount: Number,
|
|
currency: String,
|
|
templateZero: String,
|
|
templateNonzero: String,
|
|
templateReached: String,
|
|
};
|
|
|
|
connect() {
|
|
this.update();
|
|
}
|
|
|
|
update() {
|
|
if (!this.hasPreviewTarget) return;
|
|
|
|
const amount = this.#amountValue();
|
|
const newTotal = this.currentBalanceValue + amount;
|
|
const target = this.targetAmountValue;
|
|
const reached = newTotal >= target && target > 0;
|
|
const percent = target > 0 ? Math.min(100, Math.round((newTotal / target) * 100)) : 0;
|
|
|
|
let text;
|
|
if (reached) {
|
|
text = this.templateReachedValue.replace("{target}", this.#money(target));
|
|
} else if (amount === 0) {
|
|
text = this.templateZeroValue
|
|
.replaceAll("{percent}", percent.toString())
|
|
.replaceAll("{current}", this.#money(this.currentBalanceValue))
|
|
.replaceAll("{target}", this.#money(target));
|
|
} else {
|
|
text = this.templateNonzeroValue
|
|
.replaceAll("{percent}", percent.toString())
|
|
.replaceAll("{newTotal}", this.#money(newTotal))
|
|
.replaceAll("{target}", this.#money(target));
|
|
}
|
|
|
|
this.previewTarget.textContent = text;
|
|
}
|
|
|
|
#amountValue() {
|
|
if (!this.hasAmountInputTarget) return 0;
|
|
const parsed = Number.parseFloat(this.amountInputTarget.value);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
}
|
|
|
|
#money(value) {
|
|
try {
|
|
return new Intl.NumberFormat(undefined, {
|
|
style: "currency",
|
|
currency: this.currencyValue || "USD",
|
|
maximumFractionDigits: 0,
|
|
}).format(value);
|
|
} catch {
|
|
return `${this.currencyValue || "$"}${Math.round(value).toLocaleString()}`;
|
|
}
|
|
}
|
|
}
|