PluginProbe
The Innovative Form Builder – IvyForms / 0.6
The Innovative Form Builder – IvyForms v0.6
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / assets / js / admin / admin-deactivation-modal.js

admin-deactivation-modal.js in The Innovative Form Builder – IvyForms 0.6, at frontend/src/assets/js/admin/admin-deactivation-modal.js

327 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 document.addEventListener('DOMContentLoaded', () => {
2 const ivyformsRow = document.querySelector('tr[data-slug="ivyforms"]')
3 if (!ivyformsRow) return
4 const deactivateLink = ivyformsRow.querySelector('.deactivate a')
5 if (!deactivateLink) return
6 window.ivyformsDeactivateUrl = deactivateLink.href
7 deactivateLink.addEventListener('click', (e) => {
8 e.preventDefault()
9 showIvyDeactivationModal()
10 })
11
12 // Helper: create element with props
13 function el(tag, props = {}, ...children) {
14 const node = document.createElement(tag)
15 Object.entries(props).forEach(([k, v]) => {
16 if (k === 'style' && typeof v === 'object') Object.assign(node.style, v)
17 else if (k.startsWith('on') && typeof v === 'function') node[k] = v
18 else node[k] = v
19 })
20 children.forEach((child) => {
21 if (child)
22 node.appendChild(typeof child === 'string' ? document.createTextNode(child) : child)
23 })
24 return node
25 }
26
27 function showIvyDeactivationModal() {
28 document.getElementById('ivyforms-deactivation-modal-mount')?.remove()
29 const overlay = el('div', {
30 id: 'ivyforms-deactivation-modal-mount',
31 style: {
32 position: 'fixed',
33 top: 0,
34 left: 0,
35 width: '100vw',
36 height: '100vh',
37 background: 'rgba(0,0,0,0.15)',
38 display: 'flex',
39 alignItems: 'center',
40 justifyContent: 'center',
41 zIndex: 9999,
42 },
43 })
44 const modal = el('div', {
45 style: {
46 background: '#fff',
47 borderRadius: '12px',
48 boxShadow: '0 10px 25px rgba(0,0,0,0.15)',
49 width: '420px',
50 maxWidth: '95vw',
51 padding: '40px 32px',
52 position: 'relative',
53 fontFamily: 'inherit',
54 },
55 })
56 // Scoped styles
57 modal.appendChild(
58 el(
59 'style',
60 { type: 'text/css' },
61 `
62 #ivyforms-deactivation-modal-mount { --checkbox-border: #a78bfa; --checkbox-bg: #f3f0ff; --checkbox-checked: #7c3aed; --checkbox-checked-hover: #6d28d9; }
63 #ivyforms-deactivation-modal-mount .ivy-option-row { display: flex; flex-direction: row; align-items: center; gap: 12px; min-height: 24px; background: transparent !important; margin: 0; padding: 10px 0; transition: background 0.15s; }
64 #ivyforms-deactivation-modal-mount .ivy-option-row:last-child { border-bottom: none; }
65 #ivyforms-deactivation-modal-mount .ivy-option-row:hover { background: #fafafa !important; }
66 #ivyforms-deactivation-modal-mount .ivy-option-row label { display: flex; flex-direction: row; align-items: center; gap: 12px; width: 100%; font-size: 14px; color: #1f2937; font-weight: 500; cursor: pointer; margin: 0; padding: 0; height: unset; min-height: 20px; white-space: normal; position: relative; }
67 #ivyforms-deactivation-modal-mount .ivy-option-row input[type="checkbox"] { position: absolute; opacity: 0; width: 0; height: 0; cursor: pointer; margin: 0; padding: 0; }
68 #ivyforms-deactivation-modal-mount .ivy-custom-checkbox { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 4px; border: 1.5px solid #C0C5CB; box-sizing: border-box; flex-shrink: 0; cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); position: relative; -webkit-tap-highlight-color: transparent; }
69 #ivyforms-deactivation-modal-mount .ivy-option-row:hover .ivy-custom-checkbox:not(.checked) { background: var(--checkbox-hover-bg); border-color: #c4b5fd; }
70 #ivyforms-deactivation-modal-mount .ivy-custom-checkbox.checked { background: var(--checkbox-checked); border-color: var(--checkbox-checked); }
71 #ivyforms-deactivation-modal-mount .ivy-option-row:hover .ivy-custom-checkbox.checked { background: var(--checkbox-checked-hover); border-color: var(--checkbox-checked-hover); }
72 #ivyforms-deactivation-modal-mount .ivy-custom-checkbox svg { display: block; width: 12px; height: 10px; flex-shrink: 0; }
73 #ivyforms-deactivation-modal-mount .ivy-custom-checkbox:focus-visible { outline: 2px solid #7c3aed; outline-offset: 2px; }
74 #ivyforms-deactivation-modal-mount .ivyforms-modal-btn { border-radius: 6px; font-size: 15px; font-weight: 600; padding: 10px 20px; cursor: pointer; border: 1.5px solid #e5e7eb; transition: all 0.2s ease; min-height: 40px; display: inline-flex; align-items: center; justify-content: center; white-space: nowrap; -webkit-tap-highlight-color: transparent; }
75 #ivyforms-deactivation-modal-mount .ivyforms-modal-btn.skip { background: #f9fafb; color: #4b5563; border-color: #e5e7eb; }
76 #ivyforms-deactivation-modal-mount .ivyforms-modal-btn.skip:hover { background: #f3f4f6; border-color: #d1d5db; }
77 #ivyforms-deactivation-modal-mount .ivyforms-modal-btn.submit { background: #06A192; color: #fff; border-color: #06A192; }
78 #ivyforms-deactivation-modal-mount .ivyforms-modal-btn.submit:hover { background: #036a73; border-color: #036a73; }
79 `,
80 ),
81 )
82 // Close button
83 modal.appendChild(
84 el('button', {
85 innerHTML: '×',
86 style: {
87 position: 'absolute',
88 top: '16px',
89 right: '16px',
90 background: 'none',
91 border: 'none',
92 fontSize: '24px',
93 cursor: 'pointer',
94 color: '#888',
95 },
96 onclick: () => overlay.remove(),
97 }),
98 )
99 // Mascot
100 const mascotWrapper = el('div', {
101 style: { width: '120px', margin: '0 auto 20px', display: 'block' },
102 })
103 mascotWrapper.innerHTML = '<div class="mascot-placeholder" aria-hidden="true"></div>'
104 fetch('/wp-content/plugins/ivyforms/assets/mascot/IvyMascot.svg')
105 .then((r) => (r.ok ? r.text() : ''))
106 .then((svg) => {
107 mascotWrapper.innerHTML = svg
108 ? svg.replace(/xmlns=["']https?:\/\/www\.w3\.org\/2000\/svg["']/g, '')
109 : ''
110 })
111 modal.appendChild(mascotWrapper)
112 // Title & desc
113 modal.appendChild(
114 el(
115 'h2',
116 { style: { marginBottom: '10px', textAlign: 'center', fontWeight: 600, fontSize: '20px' } },
117 "We're sorry to see you go!",
118 ),
119 )
120 modal.appendChild(
121 el(
122 'p',
123 { style: { marginBottom: '20px', textAlign: 'center', fontSize: '15px', color: '#444' } },
124 "Can you tell us why you're deactivating the plugin?",
125 ),
126 )
127 // Options
128 const options = [
129 { label: 'I no longer need it', value: 'no_need' },
130 { label: 'I found a better plugin', value: 'better_plugin' },
131 { label: 'I experienced issue/bugs', value: 'bugs' },
132 { label: 'Missing Features', value: 'missing_features' },
133 { label: 'Temporary deactivation', value: 'temporary' },
134 { label: 'Other', value: 'other' },
135 ]
136 let checked = {},
137 textareaDiv = null
138 const optionsBox = el('div', {
139 style: {
140 border: 'none',
141 borderRadius: 0,
142 background: 'transparent',
143 padding: 0,
144 marginBottom: 0,
145 display: 'flex',
146 flexDirection: 'column',
147 gap: 0,
148 },
149 })
150 options.forEach((opt) => {
151 const row = el('div', { className: 'ivy-option-row' })
152 const label = el('label')
153 const input = el('input', {
154 type: 'checkbox',
155 value: opt.value,
156 style: {
157 position: 'absolute',
158 opacity: 0,
159 width: '16px',
160 height: '16px',
161 left: 0,
162 top: '50%',
163 transform: 'translateY(-50%)',
164 },
165 })
166 const customBox = el('span', {
167 className: 'ivy-custom-checkbox',
168 role: 'checkbox',
169 tabIndex: 0,
170 'aria-checked': 'false',
171 })
172 function renderCustomChecked(el, isChecked) {
173 if (isChecked) {
174 el.classList.add('checked')
175 el.setAttribute('aria-checked', 'true')
176 el.innerHTML =
177 '<svg width="12" height="10" viewBox="0 0 12 10" style="display:block;margin:2px"><path d="M1 5L4 8L11 1" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
178 } else {
179 el.classList.remove('checked')
180 el.setAttribute('aria-checked', 'false')
181 el.innerHTML = ''
182 }
183 }
184 input.addEventListener('change', () => {
185 if (input.checked) {
186 options.forEach((otherOpt) => {
187 if (otherOpt.value !== opt.value) checked[otherOpt.value] = false
188 })
189 document
190 .querySelectorAll('#ivyforms-deactivation-modal-mount .ivy-custom-checkbox')
191 .forEach((box) => {
192 if (box !== customBox && box.classList.contains('checked')) {
193 box.classList.remove('checked')
194 box.setAttribute('aria-checked', 'false')
195 box.innerHTML = ''
196 const correspondingInput = box.parentElement.querySelector('input[type="checkbox"]')
197 if (correspondingInput) correspondingInput.checked = false
198 }
199 })
200 }
201 checked[opt.value] = input.checked
202 if (opt.value === 'other' && textareaDiv)
203 textareaDiv.style.display = input.checked ? 'block' : 'none'
204 renderCustomChecked(customBox, input.checked)
205 })
206 customBox.addEventListener('click', (ev) => {
207 ev.preventDefault()
208 input.checked = !input.checked
209 input.dispatchEvent(new Event('change', { bubbles: true }))
210 })
211 customBox.addEventListener('keydown', (ev) => {
212 if (ev.key === ' ' || ev.key === 'Enter') {
213 ev.preventDefault()
214 input.checked = !input.checked
215 input.dispatchEvent(new Event('change', { bubbles: true }))
216 }
217 })
218 label.appendChild(input)
219 label.appendChild(customBox)
220 label.appendChild(el('span', {}, opt.label))
221 row.appendChild(label)
222 optionsBox.appendChild(row)
223 renderCustomChecked(customBox, !!input.checked)
224 })
225 // Textarea for 'Other'
226 textareaDiv = el(
227 'div',
228 { style: { marginTop: '16px', display: 'none' } },
229 el('textarea', {
230 placeholder: 'Write your comment here',
231 style: {
232 width: '100%',
233 height: '80px',
234 resize: 'none',
235 borderRadius: '6px',
236 border: '1px solid #e5e7eb',
237 padding: '12px',
238 fontSize: '14px',
239 boxSizing: 'border-box',
240 fontFamily: 'inherit',
241 },
242 }),
243 )
244 const groupContainer = el(
245 'div',
246 { style: { marginBottom: '20px', textAlign: 'left' } },
247 optionsBox,
248 textareaDiv,
249 )
250 modal.appendChild(groupContainer)
251 // Buttons
252 const btnRow = el('div', {
253 style: {
254 display: 'flex',
255 flexDirection: 'row',
256 alignItems: 'center',
257 justifyContent: 'flex-end',
258 gap: '12px',
259 marginTop: '24px',
260 paddingTop: '16px',
261 borderTop: '1px solid #e5e7eb',
262 },
263 })
264 btnRow.appendChild(
265 el('button', {
266 className: 'ivyforms-modal-btn skip',
267 textContent: 'Skip & Deactivate',
268 onclick: () => {
269 window.location.href = window.ivyformsDeactivateUrl
270 },
271 }),
272 )
273 btnRow.appendChild(
274 el('button', {
275 className: 'ivyforms-modal-btn submit',
276 textContent: 'Submit & Deactivate',
277 onclick: () => {
278 const selectedReasons = Object.keys(checked).filter((k) => checked[k])
279 const selectedReason = selectedReasons.length > 0 ? selectedReasons[0] : ''
280 const feedbackComment = checked['other']
281 ? textareaDiv.querySelector('textarea').value
282 : ''
283 const pluginVersion =
284 typeof window.ivyformsData !== 'undefined' && window.ivyformsData.version
285 ? window.ivyformsData.version
286 : ''
287 const restApiUrl =
288 typeof window.ivyformsData !== 'undefined' && window.ivyformsData.restApiUrl
289 ? window.ivyformsData.restApiUrl
290 : ''
291
292 // Always deactivate - helper function
293 const deactivatePlugin = () => {
294 window.location.href = window.ivyformsDeactivateUrl
295 }
296
297 // Try to send feedback, but always deactivate regardless of result
298 if (restApiUrl && selectedReason) {
299 fetch(restApiUrl, {
300 method: 'POST',
301 headers: {
302 'Content-Type': 'application/json',
303 'X-WP-Nonce':
304 typeof window.ivyformsData !== 'undefined' && window.ivyformsData.restNonce
305 ? window.ivyformsData.restNonce
306 : '',
307 },
308 body: JSON.stringify({
309 api_version: pluginVersion,
310 feedback_key: selectedReason,
311 feedback: feedbackComment,
312 }),
313 })
314 .then(() => deactivatePlugin())
315 .catch(() => deactivatePlugin()) // Always deactivate even on error
316 } else {
317 deactivatePlugin()
318 }
319 },
320 }),
321 )
322 modal.appendChild(btnRow)
323 overlay.appendChild(modal)
324 document.body.appendChild(overlay)
325 }
326 })
327