From b6328bb3e4990c122542c31415f58d632ee4541c Mon Sep 17 00:00:00 2001 From: ToneTested Date: Mon, 1 Sep 2025 20:23:27 +0100 Subject: [PATCH] Update USAGE.md --- USAGE.md | 434 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 433 insertions(+), 1 deletion(-) diff --git a/USAGE.md b/USAGE.md index e22c091..f2052cf 100755 --- a/USAGE.md +++ b/USAGE.md @@ -70,4 +70,436 @@ Now that the extension is configured, we can deploy the site again. Got to **Dep Once the build is complete, navigate to your production URL and you should see the **frameworks** that we just added to the database. -![Template with data](/public/images/guides/web-frameworks.png) \ No newline at end of file +![Template with data](/public/images/guides/web-frameworks.png) + +import React, { useEffect, useMemo, useState } from "react"; +import { Info, ChevronLeft, ChevronRight, Smartphone, Save, RefreshCcw, HelpCircle } from "lucide-react"; + +// Single-file wireframe for a mortgage calculator flow +// Focus: clarity, guidance, mobile-first, a11y, and resilience (autosave) +// Notes: This is a wireframe (no real finance maths). Replace placeholders with real logic later. + +const steps = [ + { key: "profile", label: "Your Details" }, + { key: "property", label: "Property" }, + { key: "loan", label: "Loan" }, + { key: "results", label: "Results" }, +]; + +const initialData = { + income: "", + deposit: "", + price: "", + rate: 5, + term: 25, + type: "repayment", + taxes: true, + insurance: false, +}; + +export default function MortgageCalculatorWireframe() { + const [stepIndex, setStepIndex] = useState(0); + const [data, setData] = useState(initialData); + const [errors, setErrors] = useState({}); + const [glossaryOpen, setGlossaryOpen] = useState(false); + const [saving, setSaving] = useState(false); + + // --- Autosave to localStorage (addresses: inputs lost on refresh) --- + useEffect(() => { + const raw = localStorage.getItem("mortgage-wireframe"); + if (raw) { + try { setData({ ...initialData, ...JSON.parse(raw) }); } catch {} + } + }, []); + + useEffect(() => { + setSaving(true); + const t = setTimeout(() => { + localStorage.setItem("mortgage-wireframe", JSON.stringify(data)); + setSaving(false); + }, 300); + return () => clearTimeout(t); + }, [data]); + + const canNext = useMemo(() => { + if (stepIndex === 0) return data.income && Number(data.income) > 0; + if (stepIndex === 1) return data.price && Number(data.price) > 0 && data.deposit !== ""; + if (stepIndex === 2) return data.rate >= 0 && data.term > 0; + return true; + }, [stepIndex, data]); + + const goNext = () => setStepIndex((i) => Math.min(i + 1, steps.length - 1)); + const goPrev = () => setStepIndex((i) => Math.max(i - 1, 0)); + + // --- Very rough placeholder maths for the wireframe UI --- + const monthly = useMemo(() => { + const price = Number(data.price) || 0; + const deposit = Number(data.deposit) || 0; + const principal = Math.max(price - deposit, 0); + const termMonths = (Number(data.term) || 0) * 12; + const r = (Number(data.rate) || 0) / 100 / 12; + if (!principal || !termMonths) return 0; + if (r === 0) return principal / termMonths; + return (principal * r) / (1 - Math.pow(1 + r, -termMonths)); + }, [data]); + + const affordabilityFlag = useMemo(() => { + const inc = Number(data.income) || 0; + if (!inc || !monthly) return null; + const ratio = monthly / (inc / 12); + if (ratio > 0.45) return "high"; + if (ratio > 0.30) return "medium"; + return "ok"; + }, [monthly, data.income]); + + // --- Accessible Field component --- + function Field({ id, label, hint, error, children }) { + return ( +
+ + {children} + {(hint || error) && ( +
+ {error ? ( + {error} + ) : ( + {hint} + )} +
+ )} +
+ ); + } + + // --- Stepper --- + function Stepper() { + return ( + + ); + } + + // --- Glossary Drawer --- + function Glossary() { + if (!glossaryOpen) return null; + return ( +
+
setGlossaryOpen(false)} /> +
+
+

Glossary

+ +
+
+
+
APR
+
Annual Percentage Rate: yearly cost of your mortgage including fees, shown as a percentage.
+
+
+
Deposit
+
Upfront amount you pay towards the property price.
+
+
+
Term
+
How long you’ll take to repay the mortgage (in years).
+
+
+
Repayment vs. Interest-only
+
Repayment pays interest and principal each month; interest-only pays interest now and principal later.
+
+
+
+
+ ); + } + + // --- Panels --- + function StepProfile() { + return ( +
+

Your Details

+ + setData({ ...data, income: e.target.value.replace(/[^\d.]/g, "") })} + /> + + +
+ {(["repayment", "interest-only"]).map((t) => ( + + ))} +
+
+
+ ); + } + + function StepProperty() { + return ( +
+

Property

+ + setData({ ...data, price: e.target.value.replace(/[^\d.]/g, "") })} + /> + + + setData({ ...data, deposit: e.target.value.replace(/[^\d.]/g, "") })} + /> + +
+ + +
+
+ ); + } + + function StepLoan() { + return ( +
+

Loan

+ + setData({ ...data, rate: Number(e.target.value) })} + className="w-full" + aria-valuemin={0} + aria-valuemax={15} + aria-valuenow={data.rate} + /> + + + setData({ ...data, term: Number(e.target.value) })} + className="w-full" + aria-valuemin={5} + aria-valuemax={40} + aria-valuenow={data.term} + /> + +
+ ); + } + + function StepResults() { + return ( +
+

Results

+
+
+
+

Estimated monthly payment

+

£{monthly ? monthly.toLocaleString(undefined, { maximumFractionDigits: 0 }) : "—"}

+
+
+

Interest rate

+

{data.rate}%

+

Term

+

{data.term} years

+
+
+ +
+
+

If rate −1%

+

£{scenarioDelta(-1, data, monthly)}

+
+
+

If rate +1%

+

£{scenarioDelta(1, data, monthly)}

+
+
+

If term +5y

+

£{termDelta(5, data, monthly)}

+
+
+ +
+

+
+
+ +
+ {affordabilityFlag === "high" &&

Warning: the payment may be high relative to your income.

} + {affordabilityFlag === "medium" &&

Note: consider a longer term or larger deposit to reduce monthly cost.

} + {affordabilityFlag === "ok" &&

This payment looks reasonable versus your income.

} +
+
+ ); + } + + return ( +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+ + +
+ {stepIndex === 0 && } + {stepIndex === 1 && } + {stepIndex === 2 && } + {stepIndex === 3 && } + +
+ +
+ {stepIndex < steps.length - 1 && ( + + )} +
+
+
+
+ + +
+ + + + +
+ ); +} + +// --- Helper fns --- +function fmtNum(n) { + const x = Number(String(n).replace(/[^\d.]/g, "")); + return x ? x.toLocaleString() : "—"; +} + +function pmt(principal, ratePct, termYears) { + const r = (Number(ratePct) || 0) / 100 / 12; + const n = (Number(termYears) || 0) * 12; + if (!principal || !n) return 0; + if (r === 0) return principal / n; + return (principal * r) / (1 - Math.pow(1 + r, -n)); +} + +function scenarioDelta(deltaPct, data, base) { + const price = Number(data.price) || 0; + const dep = Number(data.deposit) || 0; + const principal = Math.max(price - dep, 0); + const v = pmt(principal, (Number(data.rate) || 0) + deltaPct, data.term); + return v ? Math.round(v).toLocaleString() : "—"; +} + +function termDelta(deltaYears, data, base) { + const price = Number(data.price) || 0; + const dep = Number(data.deposit) || 0; + const principal = Math.max(price - dep, 0); + const v = pmt(principal, data.rate, (Number(data.term) || 0) + deltaYears); + return v ? Math.round(v).toLocaleString() : "—"; +}