PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.10
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.10
2.0.14 2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / mortgage-calculator / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.10, at blocks/mortgage-calculator/index.js

264 lines 17.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 var el = wp.element.createElement;
3 var useState = wp.element.useState;
4 var Fragment = wp.element.Fragment;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var __ = wp.i18n.__;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var RangeControl = wp.components.RangeControl;
12 var SelectControl = wp.components.SelectControl;
13 var TextControl = wp.components.TextControl;
14 var ToggleControl = wp.components.ToggleControl;
15
16 var _tc, _tv;
17 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
18 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
19
20 var TERM_OPTIONS = [
21 { value: 10, label: '10 years' }, { value: 15, label: '15 years' },
22 { value: 20, label: '20 years' }, { value: 25, label: '25 years' },
23 { value: 30, label: '30 years' },
24 ];
25
26 function calcMortgage(price, downPct, rate, years) {
27 var down = price * downPct / 100;
28 var principal= price - down;
29 var n = years * 12;
30 var r = rate / 100 / 12;
31 var monthly;
32 if (r === 0) {
33 monthly = principal / n;
34 } else {
35 monthly = principal * r * Math.pow(1+r, n) / (Math.pow(1+r, n) - 1);
36 }
37 var totalPaid = monthly * n;
38 var totalInterest = totalPaid - principal;
39
40 /* Amortization by year */
41 var rows = [];
42 var balance = principal;
43 for (var y = 1; y <= years; y++) {
44 var yearPrincipal = 0, yearInterest = 0;
45 for (var m = 0; m < 12; m++) {
46 var iPayment = balance * r;
47 var pPayment = monthly - iPayment;
48 yearInterest += iPayment;
49 yearPrincipal += pPayment;
50 balance -= pPayment;
51 }
52 rows.push({ year: y, principal: yearPrincipal, interest: yearInterest, balance: Math.max(0, balance) });
53 }
54 return { monthly: monthly, totalPaid: totalPaid, totalInterest: totalInterest, principal: principal, down: down, rows: rows };
55 }
56
57 function fmtMoney(val, cur, pos) {
58 return pos === 'after' ? Math.round(val).toLocaleString('en-US') + ' ' + cur : cur + Math.round(val).toLocaleString('en-US');
59 }
60
61 function MortgagePreview(props) {
62 var a = props.attributes;
63 var setAttr = props.setAttributes;
64 var _s = useState({ price: a.defaultPrice, down: a.defaultDown, rate: a.defaultRate, term: a.defaultTerm });
65 var state = _s[0], setState = _s[1];
66 function set(k, v) { setState(function(p){ var o=Object.assign({},p); o[k]=v; return o; }); }
67
68 var accent = a.accentColor || '#6c3fb5';
69 var cRadius = (a.cardRadius || 16) + 'px';
70 var iRadius = (a.inputRadius|| 8) + 'px';
71 var maxW = (a.maxWidth || 680) + 'px';
72 var ptop = (a.paddingTop || 60) + 'px';
73 var pbot = (a.paddingBottom || 60) + 'px';
74 var cur = a.currency || '$';
75 var curPos = a.currencyPos || 'before';
76 var fmt = function (v) { return fmtMoney(v, cur, curPos); };
77
78 var calc = calcMortgage(state.price, state.down, state.rate, state.term);
79 var ltv = Math.round(calc.principal / state.price * 100);
80 var showPMI = a.showPMI && state.down < 20;
81 var pmi = showPMI ? (calc.principal * (a.pmiRate||0.5) / 100 / 12) : 0;
82 var totalMonthly = calc.monthly + pmi;
83
84 var inpStyle = { width:'100%', padding:'10px 12px', border:'1.5px solid #e5e7eb', borderRadius: iRadius, fontSize:'15px', boxSizing:'border-box', outline:'none', background:'#fff', color: a.labelColor||'#374151' };
85 var lblStyle = { fontSize:'13px', fontWeight:600, color: a.labelColor||'#374151', display:'block', marginBottom:'4px' };
86
87 function numInput(val, onCh, min, max, step) {
88 return el('input', { type:'number', value:val, min:min, max:max, step:step, style:inpStyle, onChange: function(e){ onCh(parseFloat(e.target.value)||0); } });
89 }
90 function row(label, input) {
91 return el('div', { style:{ marginBottom:'14px' } }, el('label', {style:lblStyle}, label), input);
92 }
93
94 /* Breakdown cards */
95 function statCard(label, value, color) {
96 return el('div', { style:{ background:'#fff', borderRadius:'10px', padding:'14px 12px', border:'1.5px solid #e5e7eb', textAlign:'center' } },
97 el('div', { style:{ fontSize:'11px', textTransform:'uppercase', letterSpacing:'0.05em', color:'#9ca3af', marginBottom:'4px' } }, label),
98 el('div', { style:{ fontSize:'20px', fontWeight:800, color: color } }, value)
99 );
100 }
101
102 var tableRows = (a.tableRows || 10);
103
104 return el('div', { style:{ background:a.cardBg||'#fff', borderRadius:cRadius, maxWidth:maxW, margin:'0 auto', paddingTop:ptop, paddingBottom:pbot, paddingLeft:'32px', paddingRight:'32px' } },
105 a.showTitle !== false && a.title && el('h2', { className: 'bkbg-mtg-title', style:{ color:a.titleColor||'#1e1b4b', textAlign:'center', marginTop:0, marginBottom:'8px' } }, a.title),
106 a.showSubtitle !== false && a.subtitle && el('p', { className: 'bkbg-mtg-subtitle', style:{ color:a.subtitleColor||'#6b7280', textAlign:'center', marginTop:0, marginBottom:'24px' } }, a.subtitle),
107
108 /* Inputs */
109 row('Home Price (' + cur + ')', numInput(state.price, function(v){set('price',v);}, 10000, 10000000, 1000)),
110 row('Down Payment (%)', numInput(state.down, function(v){set('down',Math.min(99,Math.max(0,v)));}, 0, 99, 0.5)),
111 row('Annual Interest Rate (%)', numInput(state.rate, function(v){set('rate',v);}, 0.1, 30, 0.1)),
112 row('Loan Term',
113 el('select', { value:state.term, style:Object.assign({},inpStyle,{appearance:'auto'}), onChange:function(e){set('term',parseInt(e.target.value));}},
114 TERM_OPTIONS.map(function(o){ return el('option',{key:o.value,value:o.value},o.label); })
115 )
116 ),
117
118 /* Result */
119 el('div', { style:{ background:a.resultBg||'#f5f3ff', border:'1.5px solid '+(a.resultBorder||'#ede9fe'), borderRadius:cRadius, padding:'24px', textAlign:'center', marginBottom:'20px' } },
120 el('div', { style:{ fontSize:'13px', color:'#9ca3af', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:'4px' } }, 'Monthly Payment'),
121 el('div', { className: 'bkbg-mtg-monthly', style:{ color:a.totalColor||accent, marginBottom:'4px' } }, fmt(totalMonthly)),
122 el('div', { style:{ fontSize:'13px', color:'#9ca3af' } }, 'per month' + (showPMI?' (incl. PMI)':'')),
123 showPMI && el('div', { style:{ marginTop:'8px', fontSize:'13px', color:a.pmiColor||'#f59e0b' } }, 'PMI: ' + fmt(pmi) + '/mo (down < 20%)')
124 ),
125
126 /* Breakdown stats */
127 a.showBreakdown !== false && el('div', { style:{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:'10px', marginBottom:'20px' } },
128 statCard('Loan Amount', fmt(calc.principal), a.principalColor||'#3b82f6'),
129 statCard('Total Interest', fmt(calc.totalInterest), a.interestColor||'#ef4444'),
130 statCard('Total Cost', fmt(calc.totalPaid), a.totalColor||accent)
131 ),
132
133 /* LTV */
134 a.showLTV !== false && el('div', { style:{ textAlign:'center', marginBottom:'20px', fontSize:'14px', color:a.labelColor||'#374151' } },
135 'Loan-to-Value (LTV): ',
136 el('strong', { style:{ color: ltv >= 80 ? (a.interestColor||'#ef4444') : (a.principalColor||'#3b82f6') } }, ltv + '%'),
137 ltv >= 80 ? ' — PMI may apply' : ' — no PMI required'
138 ),
139
140 /* Year table */
141 a.showYearTable !== false && el('div', { style:{ overflowX:'auto' } },
142 el('table', { style:{ width:'100%', borderCollapse:'collapse', fontSize:'13px' } },
143 el('thead', null,
144 el('tr', { style:{ background:a.tableBg||'#fafafa' } },
145 ['Year','Balance','Principal Paid','Interest Paid'].map(function(h,i){
146 return el('th',{key:i,style:{padding:'10px 8px',textAlign:i===0?'left':'right',fontWeight:700,color:a.labelColor||'#374151',borderBottom:'2px solid #e5e7eb'}},h);
147 })
148 )
149 ),
150 el('tbody', null,
151 calc.rows.slice(0, tableRows).map(function(row, idx){
152 var last = idx === Math.min(calc.rows.length, tableRows) - 1;
153 return el('tr', { key:idx, style:{ background:idx%2===0?'#fff':a.tableBg||'#fafafa', fontWeight:last?700:'normal', borderBottom:'1px solid #f0f0f0' } },
154 el('td',{style:{padding:'9px 8px',color:a.labelColor||'#374151'}},'Year '+row.year),
155 el('td',{style:{padding:'9px 8px',textAlign:'right',color:a.totalColor||accent}},fmt(row.balance)),
156 el('td',{style:{padding:'9px 8px',textAlign:'right',color:a.principalColor||'#3b82f6'}},fmt(row.principal)),
157 el('td',{style:{padding:'9px 8px',textAlign:'right',color:a.interestColor||'#ef4444'}},fmt(row.interest))
158 );
159 })
160 )
161 )
162 )
163 );
164 }
165
166 registerBlockType('blockenberg/mortgage-calculator', {
167 edit: function (props) {
168 var a = props.attributes;
169 var setAttr = props.setAttributes;
170 var blockProps = useBlockProps((function () {
171 var _tvf = getTypoCssVars();
172 var s = {};
173 Object.assign(s, _tvf(a.titleTypo, '--bkbg-mtg-tt-'));
174 Object.assign(s, _tvf(a.subtitleTypo, '--bkbg-mtg-st-'));
175 Object.assign(s, _tvf(a.resultTypo, '--bkbg-mtg-rs-'));
176 return { className: 'bkbg-mortgage-calculator-block bkbg-mtg-app', style: s };
177 })());
178
179 return el('div', blockProps,
180 el(InspectorControls, null,
181 el(PanelBody, { title: __('Content', 'blockenberg'), initialOpen: true },
182 el(TextControl, {label:'Title', value:a.title, onChange:function(v){setAttr({title:v}); } }),
183 el(ToggleControl, {label:'Show title', checked:a.showTitle, onChange:function(v){setAttr({showTitle:v});}, __nextHasNoMarginBottom:true }),
184 el(TextControl, {label:'Subtitle', value:a.subtitle, onChange:function(v){setAttr({subtitle:v}); } }),
185 el(ToggleControl, {label:'Show subtitle', checked:a.showSubtitle, onChange:function(v){setAttr({showSubtitle:v});}, __nextHasNoMarginBottom:true }),
186 el(TextControl, {label:'Currency symbol', value:a.currency, onChange:function(v){setAttr({currency:v}); } }),
187 el(SelectControl, {label:'Currency position', value:a.currencyPos, options:[{label:'Before',value:'before'},{label:'After',value:'after'}], onChange:function(v){setAttr({currencyPos:v}); } }),
188 ),
189 el(PanelBody, { title: __('Defaults', 'blockenberg'), initialOpen: false },
190 el(RangeControl, {label:'Default home price', value:a.defaultPrice, min:50000, max:5000000, step:10000, onChange:function(v){setAttr({defaultPrice:v});} }),
191 el(RangeControl, {label:'Default down payment %', value:a.defaultDown, min:0, max:50, step:0.5, onChange:function(v){setAttr({defaultDown:v});} }),
192 el(RangeControl, {label:'Default rate %', value:a.defaultRate, min:0.5, max:25, step:0.1, onChange:function(v){setAttr({defaultRate:v});} }),
193 el(SelectControl, {label:'Default term', value:a.defaultTerm, options:TERM_OPTIONS.map(function(o){return {label:o.label,value:o.value};}), onChange:function(v){setAttr({defaultTerm:parseInt(v)});} }),
194 ),
195 el(PanelBody, { title: __('Display', 'blockenberg'), initialOpen: false },
196 el(ToggleControl, {label:'Show PMI', checked:a.showPMI, onChange:function(v){setAttr({showPMI:v});}, __nextHasNoMarginBottom:true }),
197 a.showPMI && el(RangeControl, {label:'PMI rate %', value:a.pmiRate, min:0.1, max:2, step:0.1, onChange:function(v){setAttr({pmiRate:v});} }),
198 el(ToggleControl, {label:'Show breakdown', checked:a.showBreakdown,onChange:function(v){setAttr({showBreakdown:v});}, __nextHasNoMarginBottom:true }),
199 el(ToggleControl, {label:'Show LTV ratio', checked:a.showLTV, onChange:function(v){setAttr({showLTV:v});}, __nextHasNoMarginBottom:true }),
200 el(ToggleControl, {label:'Show year table', checked:a.showYearTable,onChange:function(v){setAttr({showYearTable:v});}, __nextHasNoMarginBottom:true }),
201 a.showYearTable && el(RangeControl, {label:'Table rows',value:a.tableRows, min:5, max:30, onChange:function(v){setAttr({tableRows:v});} }),
202 ),
203
204 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
205 el(getTypoControl(), { label: __('Title', 'blockenberg'), value: a.titleTypo, onChange: function (v) { setAttr({ titleTypo: v }); } }),
206 el(getTypoControl(), { label: __('Subtitle', 'blockenberg'), value: a.subtitleTypo, onChange: function (v) { setAttr({ subtitleTypo: v }); } }),
207 el(getTypoControl(), { label: __('Result Number', 'blockenberg'), value: a.resultTypo, onChange: function (v) { setAttr({ resultTypo: v }); } })
208 ),
209 el(PanelColorSettings, {
210 title: __('Colors', 'blockenberg'),
211 initialOpen: false, colorSettings: [
212 { label:'Accent', value:a.accentColor, onChange:function(v){setAttr({accentColor:v});} },
213 { label:'Card background', value:a.cardBg, onChange:function(v){setAttr({cardBg:v});} },
214 { label:'Result background',value:a.resultBg, onChange:function(v){setAttr({resultBg:v});} },
215 { label:'Result border', value:a.resultBorder, onChange:function(v){setAttr({resultBorder:v});} },
216 { label:'Total / main', value:a.totalColor, onChange:function(v){setAttr({totalColor:v});} },
217 { label:'Principal', value:a.principalColor, onChange:function(v){setAttr({principalColor:v});} },
218 { label:'Interest', value:a.interestColor, onChange:function(v){setAttr({interestColor:v});} },
219 { label:'PMI', value:a.pmiColor, onChange:function(v){setAttr({pmiColor:v});} },
220 { label:'Title', value:a.titleColor, onChange:function(v){setAttr({titleColor:v});} },
221 { label:'Table background',value:a.tableBg, onChange:function(v){setAttr({tableBg:v});} },
222 ],
223 }),
224 el(PanelBody, { title: __('Sizing', 'blockenberg'), initialOpen: false },
225 el(RangeControl, {label:'Max width (px)', value:a.maxWidth, min:340, max:1200, step:10, onChange:function(v){setAttr({maxWidth:v});} }),
226 el(RangeControl, {label:'Padding top', value:a.paddingTop, min:0, max:120, onChange:function(v){setAttr({paddingTop:v});} }),
227 el(RangeControl, {label:'Padding bottom', value:a.paddingBottom, min:0, max:120, onChange:function(v){setAttr({paddingBottom:v});} }),
228 el(RangeControl, {label:'Card radius (px)', value:a.cardRadius, min:0, max:32, onChange:function(v){setAttr({cardRadius:v});} }),
229 el(RangeControl, {label:'Input radius (px)',value:a.inputRadius, min:0, max:20, onChange:function(v){setAttr({inputRadius:v});} }),
230 )
231 ),
232 el(MortgagePreview, { attributes: a, setAttributes: setAttr })
233 );
234 },
235
236 deprecated: [{
237 save: function (props) {
238 var a = props.attributes;
239 var blockProps = wp.blockEditor.useBlockProps.save({ className: 'bkbg-mortgage-calculator-block' });
240 return el('div', Object.assign({}, blockProps, {
241 className: (blockProps.className || '') + ' bkbg-mtg-app',
242 'data-opts': JSON.stringify(a),
243 }));
244 }
245 }],
246
247 save: function (props) {
248 var a = props.attributes;
249 var blockProps = wp.blockEditor.useBlockProps.save((function () {
250 var _tvf = getTypoCssVars();
251 var s = {};
252 Object.assign(s, _tvf(a.titleTypo, '--bkbg-mtg-tt-'));
253 Object.assign(s, _tvf(a.subtitleTypo, '--bkbg-mtg-st-'));
254 Object.assign(s, _tvf(a.resultTypo, '--bkbg-mtg-rs-'));
255 return { className: 'bkbg-mortgage-calculator-block', style: s };
256 })());
257 return el('div', Object.assign({}, blockProps, {
258 className: (blockProps.className || '') + ' bkbg-mtg-app',
259 'data-opts': JSON.stringify(a),
260 }));
261 },
262 });
263 }() );
264