From 1593a98397e53c7ec37d4b50f4d07c39c89a464f Mon Sep 17 00:00:00 2001 From: Tom Hall Date: Thu, 26 Mar 2026 15:49:18 -0400 Subject: [PATCH] Improve membership calculator and fix form whitespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Accept full income amounts (remove thousands conversion) - Show USD equivalent for non-USD currencies (e.g. ¥25.00 (~$1)) - Show Suggested Annual Contribution box alongside monthly when Annual Income is selected - Reduce whitespace between Qgiv form and waiver section Co-Authored-By: Claude Sonnet 4.6 --- src/components/MembershipCalculator.tsx | 247 ++++++++---------------- src/components/MembershipPage.tsx | 4 +- 2 files changed, 87 insertions(+), 164 deletions(-) diff --git a/src/components/MembershipCalculator.tsx b/src/components/MembershipCalculator.tsx index 02de775..d90c36f 100644 --- a/src/components/MembershipCalculator.tsx +++ b/src/components/MembershipCalculator.tsx @@ -13,18 +13,18 @@ import { } from "@mui/material"; const CURRENCIES = [ - { code: "USD", symbol: "$", name: "US Dollar" }, - { code: "EUR", symbol: "€", name: "Euro" }, - { code: "GBP", symbol: "£", name: "British Pound" }, - { code: "CAD", symbol: "C$", name: "Canadian Dollar" }, - { code: "AUD", symbol: "A$", name: "Australian Dollar" }, - { code: "JPY", symbol: "¥", name: "Japanese Yen" }, - { code: "CHF", symbol: "CHF", name: "Swiss Franc" }, - { code: "CNY", symbol: "¥", name: "Chinese Yuan" }, - { code: "INR", symbol: "₹", name: "Indian Rupee" }, - { code: "BRL", symbol: "R$", name: "Brazilian Real" }, - { code: "MXN", symbol: "MX$", name: "Mexican Peso" }, - { code: "ZAR", symbol: "R", name: "South African Rand" }, + { code: "USD", symbol: "$", name: "US Dollar", usdRate: 1 }, + { code: "EUR", symbol: "€", name: "Euro", usdRate: 1.08 }, + { code: "GBP", symbol: "£", name: "British Pound", usdRate: 1.27 }, + { code: "CAD", symbol: "C$", name: "Canadian Dollar", usdRate: 0.74 }, + { code: "AUD", symbol: "A$", name: "Australian Dollar", usdRate: 0.65 }, + { code: "JPY", symbol: "¥", name: "Japanese Yen", usdRate: 0.0067 }, + { code: "CHF", symbol: "CHF", name: "Swiss Franc", usdRate: 1.12 }, + { code: "CNY", symbol: "¥", name: "Chinese Yuan", usdRate: 0.14 }, + { code: "INR", symbol: "₹", name: "Indian Rupee", usdRate: 0.012 }, + { code: "BRL", symbol: "R$", name: "Brazilian Real", usdRate: 0.19 }, + { code: "MXN", symbol: "MX$", name: "Mexican Peso", usdRate: 0.057 }, + { code: "ZAR", symbol: "R", name: "South African Rand", usdRate: 0.055 }, ]; export default function MembershipCalculator() { @@ -33,41 +33,16 @@ export default function MembershipCalculator() { const [currency, setCurrency] = useState("USD"); const [suggestedAmount, setSuggestedAmount] = useState(null); - const getCurrencySymbol = () => { - return CURRENCIES.find((c) => c.code === currency)?.symbol || "$"; - }; - - const getCurrencyName = () => { - const curr = CURRENCIES.find((c) => c.code === currency); - return curr ? `${curr.symbol} ${curr.name} (${curr.code})` : "$ US Dollar (USD)"; - }; + const getCurrencyData = () => CURRENCIES.find((c) => c.code === currency) || CURRENCIES[0]; - const getIncomeLabel = () => { - const currSymbol = getCurrencySymbol(); - return incomeType === "annual" - ? `Annual Income (in thousands ${currSymbol})` - : `Monthly Income (in thousands ${currSymbol})`; - }; - - const calculateSuggestion = (value: string) => { + const calculateSuggestion = (value: string, type: "annual" | "monthly" = incomeType) => { const numericValue = parseFloat(value); - if (isNaN(numericValue) || numericValue <= 0) { setSuggestedAmount(null); return; } - - // Convert from thousands to actual value - const actualValue = numericValue * 1000; - - let suggestion: number; - if (incomeType === "annual") { - suggestion = actualValue / 2000; - } else { - suggestion = actualValue / 167; - } - - setSuggestedAmount(Math.round(suggestion * 100) / 100); // Round to 2 decimal places + const suggestion = type === "annual" ? numericValue / 2000 : numericValue / 167; + setSuggestedAmount(Math.round(suggestion * 100) / 100); }; const handleIncomeChange = (e: React.ChangeEvent) => { @@ -79,15 +54,27 @@ export default function MembershipCalculator() { const handleIncomeTypeChange = (e: React.ChangeEvent) => { const newType = e.target.value as "annual" | "monthly"; setIncomeType(newType); - if (income) { - calculateSuggestion(income); - } + if (income) calculateSuggestion(income, newType); }; const handleCurrencyChange = (e: any) => { setCurrency(e.target.value); }; + const currencyData = getCurrencyData(); + const isUSD = currency === "USD"; + const usdMonthly = suggestedAmount !== null ? Math.round(suggestedAmount * currencyData.usdRate) : null; + const annualAmount = suggestedAmount !== null ? Math.round(suggestedAmount * 12 * 100) / 100 : null; + const usdAnnual = annualAmount !== null ? Math.round(annualAmount * currencyData.usdRate) : null; + + const resultBoxSx = { + flex: 1, + p: 2.5, + backgroundColor: "#f0fdf4", + borderRadius: 2, + border: "2px solid #168039", + }; + return ( Calculate Your Suggested Contribution {/* Currency */} - + Currency @@ -132,15 +106,9 @@ export default function MembershipCalculator() { displayEmpty sx={{ backgroundColor: "white", - "& .MuiOutlinedInput-notchedOutline": { - borderColor: "#d1d5db", - }, - "&:hover .MuiOutlinedInput-notchedOutline": { - borderColor: "#9ca3af", - }, - "&.Mui-focused .MuiOutlinedInput-notchedOutline": { - borderColor: "#168039", - }, + "& .MuiOutlinedInput-notchedOutline": { borderColor: "#d1d5db" }, + "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: "#9ca3af" }, + "&.Mui-focused .MuiOutlinedInput-notchedOutline": { borderColor: "#168039" }, }} > {CURRENCIES.map((curr) => ( @@ -156,13 +124,7 @@ export default function MembershipCalculator() { Income Period @@ -173,16 +135,8 @@ export default function MembershipCalculator() { onChange={handleIncomeTypeChange} sx={{ gap: 1, - "& .MuiFormControlLabel-label": { - fontSize: "0.95rem", - color: "#374151", - }, - "& .MuiRadio-root": { - color: "#9ca3af", - "&.Mui-checked": { - color: "#168039", - }, - }, + "& .MuiFormControlLabel-label": { fontSize: "0.95rem", color: "#374151" }, + "& .MuiRadio-root": { color: "#9ca3af", "&.Mui-checked": { color: "#168039" } }, }} > } label="Annual Income" /> @@ -198,96 +152,65 @@ export default function MembershipCalculator() { type="number" value={income} onChange={handleIncomeChange} - placeholder="e.g., 50 for 50k" + placeholder={incomeType === "annual" ? "e.g., 60000" : "e.g., 5000"} sx={{ "& .MuiOutlinedInput-root": { backgroundColor: "white", - "& fieldset": { - borderColor: "#d1d5db", - }, - "&:hover fieldset": { - borderColor: "#9ca3af", - }, - "&.Mui-focused fieldset": { - borderColor: "#168039", - }, + "& fieldset": { borderColor: "#d1d5db" }, + "&:hover fieldset": { borderColor: "#9ca3af" }, + "&.Mui-focused fieldset": { borderColor: "#168039" }, }, }} - InputProps={{ - inputProps: { min: 0, step: "any" }, - }} + InputProps={{ inputProps: { min: 0, step: "any" } }} /> - - Enter your income in thousands (e.g., 50 = 50,000) - - {/* Suggested Amount Result */} + {/* Results */} {suggestedAmount !== null && ( - - - Suggested Monthly Contribution: - - - {getCurrencySymbol()} - {suggestedAmount.toFixed(2)} - - - This is a suggested amount based on your income. Please contribute what feels meaningful - to you. - + + {/* Monthly */} + + + Suggested Monthly Contribution: + + + {currencyData.symbol}{suggestedAmount.toFixed(2)} + {!isUSD && usdMonthly !== null && ( + + (~${usdMonthly}) + + )} + + + + {/* Annual — only when user selected annual income */} + {incomeType === "annual" && annualAmount !== null && ( + + + Suggested Annual Contribution: + + + {currencyData.symbol}{annualAmount.toFixed(2)} + {!isUSD && usdAnnual !== null && ( + + (~${usdAnnual}) + + )} + + + )} )} + {suggestedAmount !== null && ( + + This is a suggested amount based on your income. Please contribute what feels meaningful to you. + {!isUSD && " USD equivalent is approximate."} + + )} + {income && suggestedAmount === null && ( - + Please enter a valid positive number )} diff --git a/src/components/MembershipPage.tsx b/src/components/MembershipPage.tsx index 844309c..f45dfb7 100644 --- a/src/components/MembershipPage.tsx +++ b/src/components/MembershipPage.tsx @@ -45,7 +45,7 @@ function QgivEmbed() { }, []); return ( - +