PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
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 / savings-calculator / index.js

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

276 lines 19.3 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 COMPOUNDING_OPTIONS = [
21 { value: 'monthly', label: 'Monthly (12×/year)' },
22 { value: 'quarterly', label: 'Quarterly (4×/year)' },
23 { value: 'annually', label: 'Annually (1×/year)' },
24 ];
25 var COMPOUNDING_N = { monthly: 12, quarterly: 4, annually: 1 };
26
27 function calcSavings(initial, monthly, rate, years, compounding) {
28 var n = COMPOUNDING_N[compounding] || 12;
29 var r = rate / 100 / n;
30 var periods = years * n;
31 var monthsPerPeriod = 12 / n;
32
33 var balance = initial;
34 var rows = [];
35 var totalContrib = initial;
36
37 for (var p = 1; p <= periods; p++) {
38 /* Add monthly contributions for this period */
39 balance = balance * (1 + r) + monthly * monthsPerPeriod;
40 totalContrib += monthly * monthsPerPeriod;
41
42 /* Record at each full year */
43 if (p % n === 0) {
44 var year = p / n;
45 rows.push({
46 year: year,
47 balance: balance,
48 contributed: totalContrib,
49 interest: balance - totalContrib,
50 });
51 }
52 }
53
54 return {
55 finalBalance: balance,
56 totalContributed: totalContrib,
57 totalInterest: balance - totalContrib,
58 rows: rows,
59 };
60 }
61
62 function fmtMoney(val, currency, pos) {
63 var rounded = Math.round(val).toLocaleString('en-US');
64 return pos === 'after' ? rounded + ' ' + currency : currency + rounded;
65 }
66
67 /* ── Slider row helper ────────────────────────────────────────────── */
68 function SliderRow(props) {
69 var iRadius = (props.iRadius || 8) + 'px';
70 return el('div', { style: { marginBottom: '20px' } },
71 el('div', { style: { display: 'flex', justifyContent: 'space-between', marginBottom: '6px', alignItems: 'center' } },
72 el('label', { style: { fontSize: '13px', fontWeight: 600, color: props.labelColor || '#374151' } }, props.label),
73 el('span', { style: { fontWeight: 700, color: props.accent || '#6c3fb5', fontSize: '15px' } }, props.displayVal)
74 ),
75 el('input', {
76 type: 'range',
77 min: props.min, max: props.max, step: props.step || 1, value: props.value,
78 onChange: function (e) { props.onChange(parseFloat(e.target.value)); },
79 style: { width: '100%', accentColor: props.accent || '#6c3fb5', cursor: 'pointer', height: '4px' }
80 })
81 );
82 }
83
84 /* ── Preview component ────────────────────────────────────────────── */
85 function SavingsPreview(props) {
86 var a = props.attrs;
87 var accent = a.accentColor || '#6c3fb5';
88 var cRadius= (a.cardRadius || 16) + 'px';
89 var iRadius= a.inputRadius || 8;
90 var cur = a.currency || '$';
91 var curPos = a.currencyPos || 'before';
92
93 var initState = useState(a.defaultInitial || 5000);
94 var initial = initState[0]; var setInitial = initState[1];
95 var mthlyState= useState(a.defaultMonthly || 200);
96 var monthly = mthlyState[0]; var setMonthly = mthlyState[1];
97 var rateState = useState(a.defaultRate || 7);
98 var rate = rateState[0]; var setRate = rateState[1];
99 var yrsState = useState(a.defaultYears || 10);
100 var years = yrsState[0]; var setYears = yrsState[1];
101 var cmpState = useState(a.defaultCompounding|| 'monthly');
102 var compounding = cmpState[0]; var setCompounding = cmpState[1];
103
104 var calc = calcSavings(initial, monthly, rate, years, compounding);
105 var fmt = function (v) { return fmtMoney(v, cur, curPos); };
106
107 var cardStyle = {
108 background: a.cardBg || '#ffffff',
109 borderRadius: cRadius,
110 padding: '32px',
111 boxShadow: '0 4px 24px rgba(0,0,0,0.08)',
112 maxWidth: (a.maxWidth || 680) + 'px',
113 margin: '0 auto',
114 paddingTop: (a.paddingTop || 60) + 'px',
115 paddingBottom: (a.paddingBottom || 60) + 'px',
116 boxSizing: 'border-box',
117 };
118
119 return el('div', { style: cardStyle },
120 a.showTitle && el('h2', { className: 'bkbg-sav-title', style: { color: a.titleColor || '#1e1b4b', textAlign: 'center', marginTop: 0, marginBottom: 8 } }, a.title || __('Savings Calculator', 'blockenberg')),
121 a.showSubtitle && el('p', { className: 'bkbg-sav-subtitle', style: { color: a.subtitleColor || '#6b7280', textAlign: 'center', marginTop: 0, marginBottom: 28 } }, a.subtitle),
122
123 /* Sliders */
124 el(SliderRow, { label: __('Initial Deposit', 'blockenberg'), value: initial, min: 0, max: 100000, step: 500, displayVal: fmt(initial), onChange: setInitial, accent: accent, labelColor: a.labelColor, iRadius: iRadius }),
125 el(SliderRow, { label: __('Monthly Contribution', 'blockenberg'), value: monthly, min: 0, max: 5000, step: 50, displayVal: fmt(monthly), onChange: setMonthly, accent: accent, labelColor: a.labelColor, iRadius: iRadius }),
126 el(SliderRow, { label: __('Annual Interest Rate', 'blockenberg'), value: rate, min: 0.1, max: 20, step: 0.1, displayVal: rate + '%', onChange: setRate, accent: accent, labelColor: a.labelColor, iRadius: iRadius }),
127 el(SliderRow, { label: __('Time Period (Years)', 'blockenberg'), value: years, min: 1, max: 40, step: 1, displayVal: years + ' yrs', onChange: setYears, accent: accent, labelColor: a.labelColor, iRadius: iRadius }),
128
129 /* Compounding select */
130 el('div', { style: { marginBottom: '28px' } },
131 el('label', { style: { display: 'block', fontSize: '13px', fontWeight: 600, color: a.labelColor || '#374151', marginBottom: '6px' } }, __('Compounding Frequency', 'blockenberg')),
132 el('select', {
133 value: compounding,
134 onChange: function (e) { setCompounding(e.target.value); },
135 style: { width: '100%', padding: '10px 12px', borderRadius: iRadius + 'px', border: '1.5px solid #e5e7eb', fontSize: '14px', background: '#fff', outline: 'none' }
136 }, COMPOUNDING_OPTIONS.map(function (o) { return el('option', { key: o.value, value: o.value }, o.label); }))
137 ),
138
139 /* Result box */
140 el('div', {
141 style: {
142 background: a.resultBg || '#f5f3ff',
143 border: '1.5px solid ' + (a.resultBorder || '#ede9fe'),
144 borderRadius: cRadius, padding: '24px', marginBottom: '20px',
145 }
146 },
147 el('div', { style: { textAlign: 'center', marginBottom: '16px' } },
148 el('div', { style: { fontSize: '12px', color: '#9ca3af', marginBottom: '4px', textTransform: 'uppercase', letterSpacing: '0.05em' } }, __('Total Savings', 'blockenberg')),
149 el('div', { style: { fontSize: (a.resultSize || 44) + 'px', fontWeight: 800, color: a.totalColor || accent, lineHeight: 1.1 } }, fmt(calc.finalBalance))
150 ),
151 el('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px', marginTop: 0 } },
152 a.showContributedBreakdown && el('div', { style: { textAlign: 'center', padding: '12px', background: '#fff', borderRadius: '8px' } },
153 el('div', { style: { fontSize: '11px', color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '4px' } }, __('Total Contributed', 'blockenberg')),
154 el('div', { style: { fontWeight: 700, color: a.contributedColor || '#3b82f6', fontSize: '18px' } }, fmt(calc.totalContributed))
155 ),
156 a.showInterestBreakdown && el('div', { style: { textAlign: 'center', padding: '12px', background: '#fff', borderRadius: '8px' } },
157 el('div', { style: { fontSize: '11px', color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '4px' } }, __('Interest Earned', 'blockenberg')),
158 el('div', { style: { fontWeight: 700, color: a.interestColor || '#10b981', fontSize: '18px' } }, fmt(calc.totalInterest))
159 )
160 )
161 ),
162
163 /* Year-by-year table */
164 a.showYearTable && el('div', { style: { overflowX: 'auto' } },
165 el('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: '13px' } },
166 el('thead', null,
167 el('tr', { style: { background: a.tableBg || '#fafafa', borderBottom: '2px solid #e5e7eb' } },
168 el('th', { style: { padding: '10px 12px', textAlign: 'left', fontWeight: 700, color: '#374151' } }, __('Year', 'blockenberg')),
169 el('th', { style: { padding: '10px 12px', textAlign: 'right', fontWeight: 700, color: '#374151' } }, __('Balance', 'blockenberg')),
170 el('th', { style: { padding: '10px 12px', textAlign: 'right', fontWeight: 700, color: '#374151' } }, __('Contributed', 'blockenberg')),
171 el('th', { style: { padding: '10px 12px', textAlign: 'right', fontWeight: 700, color: '#374151' } }, __('Interest', 'blockenberg'))
172 )
173 ),
174 el('tbody', null,
175 calc.rows.slice(0, a.tableRows || 10).map(function (row) {
176 return el('tr', { key: row.year, style: { borderBottom: '1px solid #f3f4f6' } },
177 el('td', { style: { padding: '9px 12px', color: '#374151', fontWeight: 600 } }, 'Year ' + row.year),
178 el('td', { style: { padding: '9px 12px', textAlign: 'right', color: a.totalColor || accent, fontWeight: 700 } }, fmt(row.balance)),
179 el('td', { style: { padding: '9px 12px', textAlign: 'right', color: a.contributedColor || '#3b82f6' } }, fmt(row.contributed)),
180 el('td', { style: { padding: '9px 12px', textAlign: 'right', color: a.interestColor || '#10b981' } }, fmt(row.interest))
181 );
182 })
183 )
184 )
185 )
186 );
187 }
188
189 registerBlockType('blockenberg/savings-calculator', {
190 edit: function (props) {
191 var a = props.attributes;
192 var setAttr = props.setAttributes;
193 var blockProps = useBlockProps((function () {
194 var _tv = getTypoCssVars();
195 var s = { background: a.bgColor || undefined };
196 if (_tv) {
197 Object.assign(s, _tv(a.titleTypo, '--bksav-tt-'));
198 Object.assign(s, _tv(a.subtitleTypo, '--bksav-st-'));
199 }
200 return { style: s };
201 })());
202
203 return el(Fragment, null,
204 el(InspectorControls, null,
205
206 el(PanelBody, { title: __('Content', 'blockenberg'), initialOpen: true },
207 el(ToggleControl, { label: __('Show Title', 'blockenberg'), checked: a.showTitle, onChange: function (v) { setAttr({ showTitle: v }); }, __nextHasNoMarginBottom: true }),
208 a.showTitle && el(TextControl, { label: __('Title', 'blockenberg'), value: a.title, onChange: function (v) { setAttr({ title: v }); } }),
209 el(ToggleControl, { label: __('Show Subtitle', 'blockenberg'), checked: a.showSubtitle, onChange: function (v) { setAttr({ showSubtitle: v }); }, __nextHasNoMarginBottom: true }),
210 a.showSubtitle && el(TextControl, { label: __('Subtitle', 'blockenberg'), value: a.subtitle, onChange: function (v) { setAttr({ subtitle: v }); } }),
211 el(TextControl, { label: __('Currency Symbol', 'blockenberg'), value: a.currency, onChange: function (v) { setAttr({ currency: v }); } }),
212 el(SelectControl, { label: __('Currency Position', 'blockenberg'), value: a.currencyPos, options: [{ value: 'before', label: 'Before' }, { value: 'after', label: 'After' }], onChange: function (v) { setAttr({ currencyPos: v }); } })
213 ),
214
215 el(PanelBody, { title: __('Defaults', 'blockenberg'), initialOpen: false },
216 el(RangeControl, { label: __('Initial Deposit', 'blockenberg'), value: a.defaultInitial, min: 0, max: 100000, step: 500, onChange: function (v) { setAttr({ defaultInitial: v }); } }),
217 el(RangeControl, { label: __('Monthly Contribution', 'blockenberg'), value: a.defaultMonthly, min: 0, max: 5000, step: 50, onChange: function (v) { setAttr({ defaultMonthly: v }); } }),
218 el(RangeControl, { label: __('Annual Rate (%)', 'blockenberg'), value: a.defaultRate, min: 0.1, max: 20, step: 0.1, onChange: function (v) { setAttr({ defaultRate: v }); } }),
219 el(RangeControl, { label: __('Years', 'blockenberg'), value: a.defaultYears, min: 1, max: 40, onChange: function (v) { setAttr({ defaultYears: v }); } }),
220 el(SelectControl, { label: __('Compounding Frequency', 'blockenberg'), value: a.defaultCompounding, options: COMPOUNDING_OPTIONS, onChange: function (v) { setAttr({ defaultCompounding: v }); } })
221 ),
222
223 el(PanelBody, { title: __('Display Options', 'blockenberg'), initialOpen: false },
224 el(ToggleControl, { label: __('Show Contributed Breakdown', 'blockenberg'), checked: a.showContributedBreakdown, onChange: function (v) { setAttr({ showContributedBreakdown: v }); }, __nextHasNoMarginBottom: true }),
225 el(ToggleControl, { label: __('Show Interest Breakdown', 'blockenberg'), checked: a.showInterestBreakdown, onChange: function (v) { setAttr({ showInterestBreakdown: v }); }, __nextHasNoMarginBottom: true }),
226 el(ToggleControl, { label: __('Show Year-by-Year Table', 'blockenberg'), checked: a.showYearTable, onChange: function (v) { setAttr({ showYearTable: v }); }, __nextHasNoMarginBottom: true }),
227 a.showYearTable && el(RangeControl, { label: __('Max Table Rows', 'blockenberg'), value: a.tableRows, min: 1, max: 40, onChange: function (v) { setAttr({ tableRows: v }); } })
228 ),
229
230
231 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
232 getTypoControl() && el( getTypoControl(), { label: __('Title Typography'), value: a.titleTypo || {}, onChange: function(v){ setAttr({ titleTypo: v }); } }),
233 getTypoControl() && el( getTypoControl(), { label: __('Subtitle Typography'), value: a.subtitleTypo || {}, onChange: function(v){ setAttr({ subtitleTypo: v }); } }),
234 el(RangeControl, { label: __('Result Size (px)', 'blockenberg'), value: a.resultSize, min: 24, max: 80, onChange: function (v) { setAttr({ resultSize: v }); } })
235 ),
236 el(PanelColorSettings, {
237 title: __('Colors', 'blockenberg'), initialOpen: false,
238 colorSettings: [
239 { label: __('Accent Color', 'blockenberg'), value: a.accentColor, onChange: function (v) { setAttr({ accentColor: v || '#6c3fb5' }); } },
240 { label: __('Total Balance Color', 'blockenberg'), value: a.totalColor, onChange: function (v) { setAttr({ totalColor: v || '#6c3fb5' }); } },
241 { label: __('Contributed Color', 'blockenberg'), value: a.contributedColor, onChange: function (v) { setAttr({ contributedColor: v || '#3b82f6' }); } },
242 { label: __('Interest Earned Color', 'blockenberg'), value: a.interestColor, onChange: function (v) { setAttr({ interestColor: v || '#10b981' }); } },
243 { label: __('Result Background', 'blockenberg'), value: a.resultBg, onChange: function (v) { setAttr({ resultBg: v || '#f5f3ff' }); } },
244 { label: __('Card Background', 'blockenberg'), value: a.cardBg, onChange: function (v) { setAttr({ cardBg: v || '#ffffff' }); } },
245 { label: __('Table Background', 'blockenberg'), value: a.tableBg, onChange: function (v) { setAttr({ tableBg: v || '#fafafa' }); } },
246 { label: __('Title Color', 'blockenberg'), value: a.titleColor, onChange: function (v) { setAttr({ titleColor: v || '#1e1b4b' }); } },
247 { label: __('Section Background', 'blockenberg'), value: a.bgColor, onChange: function (v) { setAttr({ bgColor: v || '' }); } },
248 ]
249 }),
250
251 el(PanelBody, { title: __('Sizing', 'blockenberg'), initialOpen: false },
252 el(RangeControl, { label: __('Card Radius (px)', 'blockenberg'), value: a.cardRadius, min: 0, max: 40, onChange: function (v) { setAttr({ cardRadius: v }); } }),
253 el(RangeControl, { label: __('Max Width (px)', 'blockenberg'), value: a.maxWidth, min: 320,max: 1200, onChange: function (v) { setAttr({ maxWidth: v }); } }),
254 el(RangeControl, { label: __('Padding Top (px)', 'blockenberg'), value: a.paddingTop, min: 0, max: 160, onChange: function (v) { setAttr({ paddingTop: v }); } }),
255 el(RangeControl, { label: __('Padding Bottom (px)', 'blockenberg'),value:a.paddingBottom,min:0, max: 160, onChange: function (v) { setAttr({ paddingBottom:v }); } })
256 )
257 ),
258
259 el('div', blockProps,
260 el(SavingsPreview, { attrs: a })
261 )
262 );
263 },
264
265 save: function (props) {
266 var a = props.attributes;
267 var blockProps = wp.blockEditor.useBlockProps.save({ style: { background: a.bgColor || undefined } });
268 return el('div', blockProps,
269 el('div', { className: 'bkbg-sav-app', 'data-opts': JSON.stringify(a) },
270 el('p', { className: 'bkbg-sav-loading' }, __('Loading savings calculator…', 'blockenberg'))
271 )
272 );
273 }
274 });
275 }() );
276