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

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

387 lines 21.2 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 var Button = wp.components.Button;
16
17 var _discTC, _discTV;
18 function _tc() { return _discTC || (_discTC = window.bkbgTypographyControl); }
19 function _tv(t, p) { return (_discTV || (_discTV = window.bkbgTypoCssVars)) ? _discTV(t, p) : {}; }
20
21 var MODE_LABELS = {
22 sale_price: 'Find Sale Price',
23 discount_pct: 'Find Discount %',
24 orig_price: 'Find Original Price'
25 };
26
27 function calcSalePrice(orig, pct) {
28 var savings = orig * (pct / 100);
29 var sale = orig - savings;
30 return { orig: orig, sale: sale, savings: savings, pct: pct };
31 }
32 function calcDiscountPct(orig, sale) {
33 if (orig <= 0) return { orig: orig, sale: sale, savings: 0, pct: 0 };
34 var savings = orig - sale;
35 var pct = (savings / orig) * 100;
36 return { orig: orig, sale: sale, savings: savings, pct: pct };
37 }
38 function calcOrigPrice(sale, pct) {
39 if (pct >= 100) return { orig: sale, sale: sale, savings: 0, pct: pct };
40 var orig = sale / (1 - pct / 100);
41 var savings = orig - sale;
42 return { orig: orig, sale: sale, savings: savings, pct: pct };
43 }
44
45 function fmtM(val, cur, pos) {
46 var s = parseFloat(val).toFixed(2);
47 return pos === 'after' ? s + cur : cur + s;
48 }
49
50 function DiscountPreview(props) {
51 var a = props.attributes;
52 var setAttributes = props.setAttributes;
53
54 var _s = useState(a.mode);
55 var mode = _s[0]; var setMode = _s[1];
56 var _orig = useState(a.defaultOriginal);
57 var orig = _orig[0]; var setOrig = _orig[1];
58 var _pct = useState(a.defaultDiscount);
59 var pct = _pct[0]; var setPct = _pct[1];
60 var _sale = useState(a.defaultSale);
61 var sale = _sale[0]; var setSale = _sale[1];
62
63 function getResult() {
64 if (mode === 'sale_price') return calcSalePrice(parseFloat(orig) || 0, parseFloat(pct) || 0);
65 if (mode === 'discount_pct') return calcDiscountPct(parseFloat(orig) || 0, parseFloat(sale) || 0);
66 return calcOrigPrice(parseFloat(sale) || 0, parseFloat(pct) || 0);
67 }
68 var r = getResult();
69
70 var badgePct = Math.round(r.pct * 10) / 10;
71 var savingsFrac = r.orig > 0 ? Math.min(1, r.savings / r.orig) : 0;
72
73 var containerStyle = {
74 background: a.sectionBg || undefined,
75 paddingTop: a.paddingTop + 'px',
76 paddingBottom: a.paddingBottom + 'px',
77 fontFamily: 'inherit'
78 };
79 var cardStyle = {
80 background: a.cardBg,
81 borderRadius: a.cardRadius + 'px',
82 padding: '36px 32px',
83 maxWidth: a.maxWidth + 'px',
84 margin: '0 auto',
85 boxShadow: '0 4px 24px rgba(0,0,0,0.09)'
86 };
87
88 // Mode tabs
89 var modes = ['sale_price', 'discount_pct', 'orig_price'];
90
91 return el('div', { className: 'bkbg-disc-editor', style: containerStyle },
92 el('div', { style: cardStyle },
93
94 // Title / subtitle
95 (a.showTitle || a.showSubtitle) && el('div', { style: { marginBottom: '20px' } },
96 a.showTitle && el('div', {
97 className: 'bkbg-disc-title',
98 style: { color: a.titleColor, contentEditable: true, suppressContentEditableWarning: true, outline: 'none' },
99 onBlur: function(e) { setAttributes({ title: e.target.innerText }); },
100 dangerouslySetInnerHTML: { __html: a.title }
101 }),
102 a.showSubtitle && el('div', {
103 className: 'bkbg-disc-subtitle',
104 style: { color: a.subtitleColor, contentEditable: true, suppressContentEditableWarning: true, outline: 'none' },
105 onBlur: function(e) { setAttributes({ subtitle: e.target.innerText }); },
106 dangerouslySetInnerHTML: { __html: a.subtitle }
107 })
108 ),
109
110 // Mode tabs
111 el('div', { style: { display: 'flex', gap: '8px', marginBottom: '24px', flexWrap: 'wrap' } },
112 modes.map(function(m) {
113 var active = mode === m;
114 return el('button', {
115 key: m,
116 className: 'bkbg-disc-mode-btn' + (active ? ' active' : ''),
117 style: {
118 flex: '1 1 auto',
119 padding: '9px 14px',
120 borderRadius: '8px',
121 border: '2px solid ' + (active ? a.accentColor : '#e5e7eb'),
122 background: active ? a.accentColor : 'transparent',
123 color: active ? '#fff' : a.accentColor,
124 fontWeight: 600,
125 fontSize: '13px',
126 cursor: 'pointer',
127 transition: 'all .15s'
128 },
129 onClick: function() { setMode(m); }
130 }, MODE_LABELS[m]);
131 })
132 ),
133
134 // Inputs based on mode
135 el('div', { style: { display: 'flex', flexDirection: 'column', gap: '14px', marginBottom: '24px' } },
136
137 // Original price (not shown in orig_price mode)
138 mode !== 'orig_price' && el('div', { className: 'bkbg-disc-input-row' },
139 el('label', { style: { display: 'block', fontSize: '13px', fontWeight: 600, color: a.labelColor, marginBottom: '5px' } },
140 'Original Price'
141 ),
142 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
143 a.currencyPos === 'before' && el('span', { style: { color: a.labelColor, fontWeight: 700, fontSize: '16px' } }, a.currency),
144 el('input', {
145 type: 'number', value: orig, min: 0, step: 0.01,
146 style: { flex: 1, padding: '10px 14px', borderRadius: a.inputRadius + 'px', border: '1.5px solid #e5e7eb', fontSize: '16px', outline: 'none' },
147 onChange: function(e) { setOrig(e.target.value); }
148 }),
149 a.currencyPos === 'after' && el('span', { style: { color: a.labelColor, fontWeight: 700, fontSize: '16px' } }, a.currency)
150 )
151 ),
152
153 // Discount % (not shown in discount_pct mode)
154 mode !== 'discount_pct' && el('div', { className: 'bkbg-disc-input-row' },
155 el('label', { style: { display: 'block', fontSize: '13px', fontWeight: 600, color: a.labelColor, marginBottom: '5px' } },
156 'Discount Percentage'
157 ),
158 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
159 el('input', {
160 type: 'number', value: pct, min: 0, max: 100, step: 0.1,
161 style: { flex: 1, padding: '10px 14px', borderRadius: a.inputRadius + 'px', border: '1.5px solid #e5e7eb', fontSize: '16px', outline: 'none' },
162 onChange: function(e) { setPct(e.target.value); }
163 }),
164 el('span', { style: { color: a.labelColor, fontWeight: 700, fontSize: '16px' } }, '%')
165 )
166 ),
167
168 // Sale price (only for discount_pct and orig_price modes)
169 mode !== 'sale_price' && el('div', { className: 'bkbg-disc-input-row' },
170 el('label', { style: { display: 'block', fontSize: '13px', fontWeight: 600, color: a.labelColor, marginBottom: '5px' } },
171 'Sale Price'
172 ),
173 el('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
174 a.currencyPos === 'before' && el('span', { style: { color: a.labelColor, fontWeight: 700, fontSize: '16px' } }, a.currency),
175 el('input', {
176 type: 'number', value: sale, min: 0, step: 0.01,
177 style: { flex: 1, padding: '10px 14px', borderRadius: a.inputRadius + 'px', border: '1.5px solid #e5e7eb', fontSize: '16px', outline: 'none' },
178 onChange: function(e) { setSale(e.target.value); }
179 }),
180 a.currencyPos === 'after' && el('span', { style: { color: a.labelColor, fontWeight: 700, fontSize: '16px' } }, a.currency)
181 )
182 )
183 ),
184
185 // Result card
186 el('div', {
187 style: {
188 background: a.resultBg,
189 border: '2px solid ' + a.resultBorder,
190 borderRadius: a.cardRadius + 'px',
191 padding: '24px 28px',
192 textAlign: 'center'
193 }
194 },
195 // Badge
196 a.showBadge && r.pct > 0 && el('div', {
197 style: {
198 display: 'inline-block',
199 background: a.badgeBg,
200 color: a.badgeColor,
201 fontWeight: 800,
202 fontSize: '14px',
203 padding: '4px 14px',
204 borderRadius: '100px',
205 marginBottom: '14px',
206 letterSpacing: '0.03em'
207 }
208 }, badgePct + '% OFF'),
209
210 // Sale price big
211 el('div', {
212 style: {
213 fontSize: a.salePriceSize + 'px',
214 fontWeight: 800,
215 color: a.salePriceColor,
216 lineHeight: 1.1,
217 marginBottom: '6px'
218 }
219 }, fmtM(r.sale, a.currency, a.currencyPos)),
220
221 // Cross-out original
222 r.orig !== r.sale && el('div', {
223 style: {
224 fontSize: Math.round(a.salePriceSize * 0.4) + 'px',
225 color: a.origPriceColor,
226 textDecoration: 'line-through',
227 marginBottom: '10px'
228 }
229 }, fmtM(r.orig, a.currency, a.currencyPos)),
230
231 // Savings label
232 r.savings > 0 && el('div', {
233 style: {
234 fontSize: '16px',
235 fontWeight: 700,
236 color: a.savingsColor,
237 marginBottom: '16px'
238 }
239 }, 'You Save: ' + fmtM(r.savings, a.currency, a.currencyPos)),
240
241 // Savings bar
242 a.showSavingsBar && r.orig > 0 && el(Fragment, null,
243 el('div', { style: { fontSize: '12px', color: a.labelColor, marginBottom: '6px', display: 'flex', justifyContent: 'space-between' } },
244 el('span', null, 'Original'),
245 el('span', null, 'Savings')
246 ),
247 el('div', {
248 style: {
249 height: '10px',
250 borderRadius: '100px',
251 background: a.barBg,
252 overflow: 'hidden'
253 }
254 },
255 el('div', {
256 style: {
257 height: '100%',
258 width: (savingsFrac * 100).toFixed(1) + '%',
259 background: a.barFill,
260 borderRadius: '100px',
261 transition: 'width .3s'
262 }
263 })
264 )
265 )
266 )
267 )
268 );
269 }
270
271 registerBlockType('blockenberg/discount-calculator', {
272 edit: function(props) {
273 var a = props.attributes;
274 var set = props.setAttributes;
275 var blockProps = useBlockProps({ style: Object.assign(
276 { '--bkbg-disc-ttl-fs': (a.titleSize || 28) + 'px' },
277 _tv(a.typoTitle || {}, '--bkbg-disc-ttl-'),
278 _tv(a.typoSubtitle || {}, '--bkbg-disc-sub-')
279 ) });
280
281 var colorSettings = [
282 { value: a.accentColor, onChange: function(v) { set({ accentColor: v }); }, label: 'Accent / Mode Tabs' },
283 { value: a.cardBg, onChange: function(v) { set({ cardBg: v }); }, label: 'Card Background' },
284 { value: a.resultBg, onChange: function(v) { set({ resultBg: v }); }, label: 'Result Background' },
285 { value: a.resultBorder, onChange: function(v) { set({ resultBorder: v }); }, label: 'Result Border' },
286 { value: a.salePriceColor, onChange: function(v) { set({ salePriceColor: v }); }, label: 'Sale Price Color' },
287 { value: a.origPriceColor, onChange: function(v) { set({ origPriceColor: v }); }, label: 'Original Price (strikethrough)' },
288 { value: a.savingsColor, onChange: function(v) { set({ savingsColor: v }); }, label: 'Savings Color' },
289 { value: a.badgeBg, onChange: function(v) { set({ badgeBg: v }); }, label: 'Badge Background' },
290 { value: a.badgeColor, onChange: function(v) { set({ badgeColor: v }); }, label: 'Badge Text' },
291 { value: a.barFill, onChange: function(v) { set({ barFill: v }); }, label: 'Savings Bar Fill' },
292 { value: a.barBg, onChange: function(v) { set({ barBg: v }); }, label: 'Savings Bar Background' },
293 { value: a.titleColor, onChange: function(v) { set({ titleColor: v }); }, label: 'Title Color' },
294 { value: a.subtitleColor, onChange: function(v) { set({ subtitleColor: v }); }, label: 'Subtitle Color' },
295 { value: a.labelColor, onChange: function(v) { set({ labelColor: v }); }, label: 'Label / Text Color' },
296 { value: a.sectionBg, onChange: function(v) { set({ sectionBg: v }); }, label: 'Section Background' }
297 ];
298
299 return el(Fragment, null,
300 el(InspectorControls, null,
301
302 // Header
303 el(PanelBody, { title: 'Header', initialOpen: false },
304 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Title', checked: a.showTitle, onChange: function(v) { set({ showTitle: v }); } }),
305 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Subtitle', checked: a.showSubtitle, onChange: function(v) { set({ showSubtitle: v }); } }),
306 el(TextControl, { label: 'Title', value: a.title, onChange: function(v) { set({ title: v }); } }),
307 el(TextControl, { label: 'Subtitle', value: a.subtitle, onChange: function(v) { set({ subtitle: v }); } })
308 ),
309
310 // Calculator Settings
311 el(PanelBody, { title: 'Calculator Settings', initialOpen: true },
312 el(SelectControl, {
313 label: 'Default Mode',
314 value: a.mode,
315 options: [
316 { label: 'Find Sale Price', value: 'sale_price' },
317 { label: 'Find Discount %', value: 'discount_pct' },
318 { label: 'Find Original Price', value: 'orig_price' }
319 ],
320 onChange: function(v) { set({ mode: v }); }
321 }),
322 el(TextControl, { label: 'Currency Symbol', value: a.currency, onChange: function(v) { set({ currency: v }); } }),
323 el(SelectControl, {
324 label: 'Currency Position',
325 value: a.currencyPos,
326 options: [
327 { label: 'Before ($100)', value: 'before' },
328 { label: 'After (100$)', value: 'after' }
329 ],
330 onChange: function(v) { set({ currencyPos: v }); }
331 }),
332 el(TextControl, {
333 label: 'Default Original Price', type: 'number',
334 value: String(a.defaultOriginal),
335 onChange: function(v) { set({ defaultOriginal: parseFloat(v) || 0 }); }
336 }),
337 el(TextControl, {
338 label: 'Default Discount %', type: 'number',
339 value: String(a.defaultDiscount),
340 onChange: function(v) { set({ defaultDiscount: parseFloat(v) || 0 }); }
341 }),
342 el(TextControl, {
343 label: 'Default Sale Price', type: 'number',
344 value: String(a.defaultSale),
345 onChange: function(v) { set({ defaultSale: parseFloat(v) || 0 }); }
346 }),
347 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Savings Bar', checked: a.showSavingsBar, onChange: function(v) { set({ showSavingsBar: v }); } }),
348 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Discount Badge', checked: a.showBadge, onChange: function(v) { set({ showBadge: v }); } })
349 ),
350
351 // Colors
352
353 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
354 _tc() && el(_tc(), { label: __('Title'), typo: a.typoTitle || {}, onChange: function(v) { set({ typoTitle: v }); }, defaultSize: a.titleSize || 28 }),
355 _tc() && el(_tc(), { label: __('Subtitle'), typo: a.typoSubtitle || {}, onChange: function(v) { set({ typoSubtitle: v }); }, defaultSize: 15 }),
356 el(RangeControl, { label: 'Sale Price Size', value: a.salePriceSize, min: 28, max: 96, step: 2, onChange: function(v) { set({ salePriceSize: v }); } })
357 ),
358 el(PanelColorSettings, {
359 title: 'Colors',
360 initialOpen: false,
361 colorSettings: colorSettings
362 }),
363
364 // Sizing
365 el(PanelBody, { title: 'Sizing & Layout', initialOpen: false },
366 el(RangeControl, { label: 'Card Border Radius', value: a.cardRadius, min: 0, max: 40, step: 1, onChange: function(v) { set({ cardRadius: v }); } }),
367 el(RangeControl, { label: 'Input Border Radius', value: a.inputRadius, min: 0, max: 24, step: 1, onChange: function(v) { set({ inputRadius: v }); } }),
368 el(RangeControl, { label: 'Max Width (px)', value: a.maxWidth, min: 300, max: 900, step: 10, onChange: function(v) { set({ maxWidth: v }); } }),
369 el(RangeControl, { label: 'Padding Top (px)', value: a.paddingTop, min: 0, max: 160, step: 4, onChange: function(v) { set({ paddingTop: v }); } }),
370 el(RangeControl, { label: 'Padding Bottom (px)', value: a.paddingBottom, min: 0, max: 160, step: 4, onChange: function(v) { set({ paddingBottom: v }); } })
371 )
372 ),
373 el('div', blockProps,
374 el(DiscountPreview, { attributes: a, setAttributes: set })
375 )
376 );
377 },
378 save: function(props) {
379 var a = props.attributes;
380 var blockProps = wp.blockEditor.useBlockProps.save();
381 return el('div', blockProps,
382 el('div', { className: 'bkbg-disc-app', 'data-opts': JSON.stringify(a) })
383 );
384 }
385 });
386 }() );
387