Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 85 additions & 162 deletions src/components/MembershipCalculator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -33,41 +33,16 @@ export default function MembershipCalculator() {
const [currency, setCurrency] = useState<string>("USD");
const [suggestedAmount, setSuggestedAmount] = useState<number | null>(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<HTMLInputElement>) => {
Expand All @@ -79,15 +54,27 @@ export default function MembershipCalculator() {
const handleIncomeTypeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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 (
<Box
sx={{
Expand All @@ -102,27 +89,14 @@ export default function MembershipCalculator() {
<Typography
variant="h5"
component="h2"
sx={{
fontWeight: "bold",
color: "#1f2937",
mb: 2.5,
fontSize: "1.75rem",
}}
sx={{ fontWeight: "bold", color: "#1f2937", mb: 2.5, fontSize: "1.75rem" }}
>
Calculate Your Suggested Contribution
</Typography>

{/* Currency */}
<Box sx={{ mb: 2 }}>
<FormLabel
sx={{
display: "block",
mb: 1,
fontWeight: 600,
color: "#1f2937",
fontSize: "0.95rem",
}}
>
<FormLabel sx={{ display: "block", mb: 1, fontWeight: 600, color: "#1f2937", fontSize: "0.95rem" }}>
Currency
</FormLabel>
<FormControl fullWidth>
Expand All @@ -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) => (
Expand All @@ -156,13 +124,7 @@ export default function MembershipCalculator() {
<Box sx={{ mb: 2 }}>
<FormLabel
component="legend"
sx={{
display: "block",
mb: 1,
fontWeight: 600,
color: "#1f2937",
fontSize: "0.95rem",
}}
sx={{ display: "block", mb: 1, fontWeight: 600, color: "#1f2937", fontSize: "0.95rem" }}
>
Income Period
</FormLabel>
Expand All @@ -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" } },
}}
>
<FormControlLabel value="annual" control={<Radio />} label="Annual Income" />
Expand All @@ -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" } }}
/>
<Typography
variant="caption"
sx={{
display: "block",
mt: 0.5,
color: "#6b7280",
fontStyle: "italic",
fontSize: "0.875rem",
}}
>
Enter your income in thousands (e.g., 50 = 50,000)
</Typography>
</Box>

{/* Suggested Amount Result */}
{/* Results */}
{suggestedAmount !== null && (
<Box
sx={{
mt: 2.5,
p: 2.5,
backgroundColor: "#f0fdf4",
borderRadius: 2,
border: "2px solid #168039",
}}
>
<Typography
variant="h6"
sx={{
fontWeight: 600,
color: "#166534",
mb: 0.5,
fontSize: "1.125rem",
}}
>
Suggested Monthly Contribution:
</Typography>
<Typography
variant="h4"
sx={{
fontWeight: "bold",
color: "#168039",
fontSize: "2rem",
}}
>
{getCurrencySymbol()}
{suggestedAmount.toFixed(2)}
</Typography>
<Typography
variant="body2"
sx={{
mt: 1.5,
color: "#4b5563",
fontStyle: "italic",
fontSize: "0.875rem",
}}
>
This is a suggested amount based on your income. Please contribute what feels meaningful
to you.
</Typography>
<Box sx={{ mt: 2.5, display: "flex", gap: 2, flexWrap: "wrap" }}>
{/* Monthly */}
<Box sx={resultBoxSx}>
<Typography variant="h6" sx={{ fontWeight: 600, color: "#166534", mb: 0.5, fontSize: "1rem" }}>
Suggested Monthly Contribution:
</Typography>
<Typography variant="h4" sx={{ fontWeight: "bold", color: "#168039", fontSize: "1.75rem" }}>
{currencyData.symbol}{suggestedAmount.toFixed(2)}
{!isUSD && usdMonthly !== null && (
<Typography component="span" sx={{ fontWeight: 400, color: "#6b7280", fontSize: "1rem", ml: 1 }}>
(~${usdMonthly})
</Typography>
)}
</Typography>
</Box>

{/* Annual — only when user selected annual income */}
{incomeType === "annual" && annualAmount !== null && (
<Box sx={resultBoxSx}>
<Typography variant="h6" sx={{ fontWeight: 600, color: "#166534", mb: 0.5, fontSize: "1rem" }}>
Suggested Annual Contribution:
</Typography>
<Typography variant="h4" sx={{ fontWeight: "bold", color: "#168039", fontSize: "1.75rem" }}>
{currencyData.symbol}{annualAmount.toFixed(2)}
{!isUSD && usdAnnual !== null && (
<Typography component="span" sx={{ fontWeight: 400, color: "#6b7280", fontSize: "1rem", ml: 1 }}>
(~${usdAnnual})
</Typography>
)}
</Typography>
</Box>
)}
</Box>
)}

{suggestedAmount !== null && (
<Typography variant="body2" sx={{ mt: 1.5, color: "#4b5563", fontStyle: "italic", fontSize: "0.875rem" }}>
This is a suggested amount based on your income. Please contribute what feels meaningful to you.
{!isUSD && " USD equivalent is approximate."}
</Typography>
)}

{income && suggestedAmount === null && (
<Typography
variant="body2"
sx={{
mt: 1.5,
color: "#dc2626",
fontSize: "0.875rem",
}}
>
<Typography variant="body2" sx={{ mt: 1.5, color: "#dc2626", fontSize: "0.875rem" }}>
Please enter a valid positive number
</Typography>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/MembershipPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function QgivEmbed() {
}, []);

return (
<Box sx={{ mt: 3 }}>
<Box sx={{ mt: 3, overflow: "hidden" }}>
<div
className="qgiv-embed-container"
data-qgiv-embed="true"
Expand Down Expand Up @@ -137,7 +137,7 @@ export default function MembershipPage() {
{/* Inclusivity / waiver note */}
<Box
sx={{
mt: 4,
mt: 1,
p: 3,
borderRadius: 2,
backgroundColor: "#f9fafb",
Expand Down
Loading