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

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

210 lines 14.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 TextControl = wp.components.TextControl;
13 var ToggleControl = wp.components.ToggleControl;
14
15 var _tc, _tv;
16 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
17 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
18
19 function calcROI(investment, returned, years) {
20 var netProfit = returned - investment;
21 var roi = investment > 0 ? (netProfit / investment) * 100 : 0;
22 var annualized = investment > 0 && years > 0
23 ? (Math.pow(returned / investment, 1 / years) - 1) * 100
24 : 0;
25 var payback = netProfit > 0 && investment > 0
26 ? investment / (netProfit / years)
27 : null;
28 return { netProfit: netProfit, roi: roi, annualized: annualized, payback: payback };
29 }
30
31 function fmt(n, cur) {
32 return cur + n.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 });
33 }
34 function fmtPct(n) {
35 return (n >= 0 ? '+' : '') + n.toFixed(2) + '%';
36 }
37
38 function ROIPreview(props) {
39 var a = props.attributes;
40 var accent = a.accentColor || '#6c3fb5';
41 var cur = a.currency || '$';
42 var posClr = a.positiveColor || '#10b981';
43 var negClr = a.negativeColor || '#ef4444';
44
45 var _inv = useState(a.defaultInvestment || 10000); var inv = _inv[0]; var setInv = _inv[1];
46 var _ret = useState(a.defaultReturn || 15000); var ret = _ret[0]; var setRet = _ret[1];
47 var _yrs = useState(a.defaultYears || 3); var yrs = _yrs[0]; var setYrs = _yrs[1];
48
49 var r = calcROI(parseFloat(inv)||0, parseFloat(ret)||0, parseFloat(yrs)||1);
50 var positive = r.netProfit >= 0;
51 var resultBg = positive ? (a.resultBg || accent) : negClr;
52
53 var inputStyle = {
54 padding:'10px 12px', borderRadius:(a.inputRadius||8)+'px',
55 border:'1.5px solid '+(a.inputBorder||'#e5e7eb'),
56 fontSize:'15px', fontFamily:'inherit', outline:'none', width:'100%'
57 };
58 var lblStyle = { display:'block', fontSize:'12px', fontWeight:600, color:a.labelColor||'#374151', marginBottom:'5px', textTransform:'uppercase', letterSpacing:'.05em' };
59 var statStyle = {
60 background:a.statBg||'#f3f4f6', border:'1px solid '+(a.statBorder||'#e5e7eb'),
61 borderRadius:'10px', padding:'14px 12px', textAlign:'center', flex:'1'
62 };
63
64 return el('div', {style:{paddingTop:(a.paddingTop||60)+'px',paddingBottom:(a.paddingBottom||60)+'px',background:a.sectionBg||undefined}},
65 el('div', {style:{background:a.cardBg,borderRadius:(a.cardRadius||16)+'px',padding:'36px 32px',maxWidth:(a.maxWidth||560)+'px',margin:'0 auto',boxShadow:'0 4px 24px rgba(0,0,0,.09)'}},
66 (a.showTitle||a.showSubtitle) && el('div', {style:{marginBottom:'24px'}},
67 a.showTitle && el('div', {className:'bkbg-roi-title',style:{color:a.titleColor}}, a.title),
68 a.showSubtitle && el('div', {className:'bkbg-roi-subtitle',style:{color:a.subtitleColor}}, a.subtitle)
69 ),
70
71 // Inputs
72 el('div', {style:{display:'grid',gridTemplateColumns:'1fr 1fr',gap:'14px',marginBottom:'14px'}},
73 el('div', null,
74 el('label', {style:lblStyle}, 'Initial Investment'),
75 el('div', {style:{position:'relative'}},
76 el('span', {style:{position:'absolute',left:'10px',top:'50%',transform:'translateY(-50%)',fontSize:'15px',color:'#6b7280',pointerEvents:'none'}}, cur),
77 el('input', {type:'number',value:inv,min:0,style:{...inputStyle,paddingLeft:'26px'},onChange:function(e){setInv(e.target.value);}})
78 )
79 ),
80 el('div', null,
81 el('label', {style:lblStyle}, 'Total Return'),
82 el('div', {style:{position:'relative'}},
83 el('span', {style:{position:'absolute',left:'10px',top:'50%',transform:'translateY(-50%)',fontSize:'15px',color:'#6b7280',pointerEvents:'none'}}, cur),
84 el('input', {type:'number',value:ret,min:0,style:{...inputStyle,paddingLeft:'26px'},onChange:function(e){setRet(e.target.value);}})
85 )
86 )
87 ),
88 el('div', {style:{marginBottom:'20px'}},
89 el('label', {style:lblStyle}, 'Investment Period (Years)'),
90 el('input', {type:'number',value:yrs,min:0.1,step:0.5,style:inputStyle,onChange:function(e){setYrs(e.target.value);}})
91 ),
92
93 // ROI result card
94 el('div', {style:{background:resultBg,borderRadius:'12px',padding:'22px 24px',marginBottom:'18px',textAlign:'center',color:a.resultColor||'#fff'}},
95 el('div', {style:{fontSize:'13px',fontWeight:600,opacity:.8,marginBottom:'4px',textTransform:'uppercase',letterSpacing:'.06em'}}, 'Return on Investment'),
96 el('div', {style:{fontSize:(a.resultSize||48)+'px',fontWeight:800,lineHeight:1}}, fmtPct(r.roi)),
97 el('div', {style:{fontSize:'15px',opacity:.85,marginTop:'6px'}}, 'Net Profit: '+fmt(r.netProfit,cur))
98 ),
99
100 // Stat cards
101 a.showBreakdown && el('div', {style:{display:'flex',gap:'10px',marginBottom:'14px',flexWrap:'wrap'}},
102 el('div', {style:statStyle},
103 el('div', {style:{fontSize:'22px',fontWeight:700,color:a.statValueColor||'#111827'}}, fmt(parseFloat(inv)||0,cur)),
104 el('div', {style:{fontSize:'11px',color:a.statLabelColor||'#6b7280',marginTop:'3px'}}, 'Invested')
105 ),
106 el('div', {style:statStyle},
107 el('div', {style:{fontSize:'22px',fontWeight:700,color:positive?posClr:negClr}}, fmt(r.netProfit,cur)),
108 el('div', {style:{fontSize:'11px',color:a.statLabelColor||'#6b7280',marginTop:'3px'}}, 'Net Profit')
109 ),
110 el('div', {style:statStyle},
111 el('div', {style:{fontSize:'22px',fontWeight:700,color:a.statValueColor||'#111827'}}, fmt(parseFloat(ret)||0,cur)),
112 el('div', {style:{fontSize:'11px',color:a.statLabelColor||'#6b7280',marginTop:'3px'}}, 'Total Return')
113 )
114 ),
115
116 el('div', {style:{display:'flex',gap:'10px',flexWrap:'wrap'}},
117 a.showAnnualized && el('div', {style:{...statStyle,flex:'1'}},
118 el('div', {style:{fontSize:'22px',fontWeight:700,color:positive?posClr:negClr}}, fmtPct(r.annualized)),
119 el('div', {style:{fontSize:'11px',color:a.statLabelColor||'#6b7280',marginTop:'3px'}}, 'Annualized ROI')
120 ),
121 a.showPayback && el('div', {style:{...statStyle,flex:'1'}},
122 el('div', {style:{fontSize:'22px',fontWeight:700,color:a.statValueColor||'#111827'}},
123 r.payback != null ? r.payback.toFixed(1)+' yr' : '—'),
124 el('div', {style:{fontSize:'11px',color:a.statLabelColor||'#6b7280',marginTop:'3px'}}, 'Payback Period')
125 )
126 )
127 )
128 );
129 }
130
131 registerBlockType('blockenberg/roi-calculator', {
132 edit: function(props) {
133 var a = props.attributes; var set = props.setAttributes;
134 var blockProps = useBlockProps((function () {
135 var _tv = getTypoCssVars();
136 var s = {};
137 if (_tv) {
138 Object.assign(s, _tv(a.titleTypo, '--bkroi-tt-'));
139 Object.assign(s, _tv(a.subtitleTypo, '--bkroi-st-'));
140 }
141 return { className: 'bkbg-roi-wrap', style: s };
142 })());
143 var colorSettings = [
144 {value:a.accentColor, onChange:function(v){set({accentColor:v});}, label:'Accent Color'},
145 {value:a.cardBg, onChange:function(v){set({cardBg:v});}, label:'Card Background'},
146 {value:a.resultBg, onChange:function(v){set({resultBg:v});}, label:'Result Card Background'},
147 {value:a.resultColor, onChange:function(v){set({resultColor:v});}, label:'Result Card Text'},
148 {value:a.positiveColor, onChange:function(v){set({positiveColor:v});}, label:'Positive / Profit Color'},
149 {value:a.negativeColor, onChange:function(v){set({negativeColor:v});}, label:'Negative / Loss Color'},
150 {value:a.statBg, onChange:function(v){set({statBg:v});}, label:'Stat Card Background'},
151 {value:a.statBorder, onChange:function(v){set({statBorder:v});}, label:'Stat Card Border'},
152 {value:a.statValueColor,onChange:function(v){set({statValueColor:v});}, label:'Stat Value Color'},
153 {value:a.statLabelColor,onChange:function(v){set({statLabelColor:v});}, label:'Stat Label Color'},
154 {value:a.inputBorder, onChange:function(v){set({inputBorder:v});}, label:'Input Border'},
155 {value:a.labelColor, onChange:function(v){set({labelColor:v});}, label:'Label Color'},
156 {value:a.titleColor, onChange:function(v){set({titleColor:v});}, label:'Title Color'},
157 {value:a.subtitleColor, onChange:function(v){set({subtitleColor:v});}, label:'Subtitle Color'},
158 {value:a.sectionBg, onChange:function(v){set({sectionBg:v});}, label:'Section Background'}
159 ];
160 return el(Fragment, null,
161 el(InspectorControls, null,
162 el(PanelBody,{title:'Header',initialOpen:false},
163 el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Title', checked:a.showTitle, onChange:function(v){set({showTitle:v});}}),
164 el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Subtitle',checked:a.showSubtitle,onChange:function(v){set({showSubtitle:v});}}),
165 el(TextControl,{label:'Title', value:a.title, onChange:function(v){set({title:v});}}),
166 el(TextControl,{label:'Subtitle',value:a.subtitle,onChange:function(v){set({subtitle:v});}})
167 ),
168 el(PanelBody,{title:'Calculator Defaults',initialOpen:true},
169 el(TextControl,{label:'Currency Symbol',value:a.currency,onChange:function(v){set({currency:v});}}),
170 el(TextControl,{label:'Default Investment',value:String(a.defaultInvestment),type:'number',onChange:function(v){set({defaultInvestment:parseFloat(v)||0});}}),
171 el(TextControl,{label:'Default Total Return',value:String(a.defaultReturn),type:'number',onChange:function(v){set({defaultReturn:parseFloat(v)||0});}}),
172 el(TextControl,{label:'Default Years',value:String(a.defaultYears),type:'number',onChange:function(v){set({defaultYears:parseFloat(v)||1});}}),
173 el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Payback Period', checked:a.showPayback, onChange:function(v){set({showPayback:v});}}),
174 el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Annualized Return', checked:a.showAnnualized, onChange:function(v){set({showAnnualized:v});}}),
175 el(ToggleControl,{__nextHasNoMarginBottom:true,label:'Show Breakdown Cards', checked:a.showBreakdown, onChange:function(v){set({showBreakdown:v});}})
176 ),
177
178 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
179 getTypoControl() && el(getTypoControl(), {
180 label: __('Title Typography', 'blockenberg'),
181 value: a.titleTypo || {},
182 onChange: function (v) { set({ titleTypo: v }); }
183 }),
184 getTypoControl() && el(getTypoControl(), {
185 label: __('Subtitle Typography', 'blockenberg'),
186 value: a.subtitleTypo || {},
187 onChange: function (v) { set({ subtitleTypo: v }); }
188 }),
189 el(RangeControl, { label: 'ROI Result Size', value: a.resultSize, min: 24, max: 80, step: 2, onChange: function (v) { set({ resultSize: v }); } })
190 ),
191 el(PanelColorSettings,{title:'Colors',initialOpen:false,colorSettings:colorSettings}),
192 el(PanelBody,{title:'Sizing & Layout',initialOpen:false},
193 el(RangeControl,{label:'Card Border Radius',value:a.cardRadius, min:0,max:40,step:1, onChange:function(v){set({cardRadius:v});}}),
194 el(RangeControl,{label:'Input Border Radius',value:a.inputRadius,min:0,max:20,step:1, onChange:function(v){set({inputRadius:v});}}),
195 el(RangeControl,{label:'Max Width (px)', value:a.maxWidth, min:340,max:960,step:10,onChange:function(v){set({maxWidth:v});}}),
196 el(RangeControl,{label:'Padding Top (px)', value:a.paddingTop, min:0,max:160,step:4, onChange:function(v){set({paddingTop:v});}}),
197 el(RangeControl,{label:'Padding Bottom (px)',value:a.paddingBottom,min:0,max:160,step:4,onChange:function(v){set({paddingBottom:v});}})
198 )
199 ),
200 el('div', blockProps, el(ROIPreview, {attributes:a}))
201 );
202 },
203 save: function(props) {
204 var a = props.attributes;
205 return el('div', wp.blockEditor.useBlockProps.save(),
206 el('div', {className:'bkbg-roi-app','data-opts':JSON.stringify(a)}));
207 }
208 });
209 }() );
210